1+ import traceback
2+ from datetime import datetime , timezone
3+ from typing import Dict
4+
5+ import loguru
6+ import traceback
7+ from os import environ
8+ from time import time_ns
9+ from typing import Any , Callable , Optional , Tuple , Union # noqa
10+ from opentelemetry ._logs import (
11+ NoOpLogger ,
12+ SeverityNumber ,
13+ get_logger ,
14+ get_logger_provider ,
15+ std_to_otel ,
16+ )
17+ from opentelemetry .attributes import BoundedAttributes
18+ from opentelemetry .sdk .environment_variables import (
19+ OTEL_ATTRIBUTE_COUNT_LIMIT ,
20+ OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT ,
21+ )
22+ from opentelemetry .sdk .resources import Resource
23+ from opentelemetry .sdk .util import ns_to_iso_str
24+ from opentelemetry .sdk .util .instrumentation import InstrumentationScope
25+ from opentelemetry .semconv .trace import SpanAttributes
26+ from opentelemetry .trace import (
27+ format_span_id ,
28+ format_trace_id ,
29+ get_current_span ,
30+ )
31+ from opentelemetry .trace .span import TraceFlags
32+ from opentelemetry .util .types import Attributes
33+
34+ from opentelemetry ._logs import Logger as APILogger
35+ from opentelemetry ._logs import LoggerProvider as APILoggerProvider
36+ from opentelemetry ._logs import LogRecord as APILogRecord
37+
38+ from opentelemetry ._logs import std_to_otel
39+ from opentelemetry .sdk ._logs ._internal import LoggerProvider , LogRecord
40+ from opentelemetry .sdk ._logs ._internal .export import BatchLogRecordProcessor , LogExporter
41+ from opentelemetry .sdk .resources import Resource
42+ from opentelemetry .semconv .trace import SpanAttributes
43+ from opentelemetry .trace import get_current_span
44+
45+ from opentelemetry ._logs .severity import SeverityNumber
46+
47+ import sys
48+ import json
49+
50+ _STD_TO_OTEL = {
51+ 10 : SeverityNumber .DEBUG ,
52+ 11 : SeverityNumber .DEBUG2 ,
53+ 12 : SeverityNumber .DEBUG3 ,
54+ 13 : SeverityNumber .DEBUG4 ,
55+ 14 : SeverityNumber .DEBUG4 ,
56+ 15 : SeverityNumber .DEBUG4 ,
57+ 16 : SeverityNumber .DEBUG4 ,
58+ 17 : SeverityNumber .DEBUG4 ,
59+ 18 : SeverityNumber .DEBUG4 ,
60+ 19 : SeverityNumber .DEBUG4 ,
61+ 20 : SeverityNumber .INFO ,
62+ 21 : SeverityNumber .INFO2 ,
63+ 22 : SeverityNumber .INFO3 ,
64+ 23 : SeverityNumber .INFO4 ,
65+ 24 : SeverityNumber .INFO4 ,
66+ 25 : SeverityNumber .INFO4 ,
67+ 26 : SeverityNumber .INFO4 ,
68+ 27 : SeverityNumber .INFO4 ,
69+ 28 : SeverityNumber .INFO4 ,
70+ 29 : SeverityNumber .INFO4 ,
71+ 30 : SeverityNumber .WARN ,
72+ 31 : SeverityNumber .WARN2 ,
73+ 32 : SeverityNumber .WARN3 ,
74+ 33 : SeverityNumber .WARN4 ,
75+ 34 : SeverityNumber .WARN4 ,
76+ 35 : SeverityNumber .WARN4 ,
77+ 36 : SeverityNumber .WARN4 ,
78+ 37 : SeverityNumber .WARN4 ,
79+ 38 : SeverityNumber .WARN4 ,
80+ 39 : SeverityNumber .WARN4 ,
81+ 40 : SeverityNumber .ERROR ,
82+ 41 : SeverityNumber .ERROR2 ,
83+ 42 : SeverityNumber .ERROR3 ,
84+ 43 : SeverityNumber .ERROR4 ,
85+ 44 : SeverityNumber .ERROR4 ,
86+ 45 : SeverityNumber .ERROR4 ,
87+ 46 : SeverityNumber .ERROR4 ,
88+ 47 : SeverityNumber .ERROR4 ,
89+ 48 : SeverityNumber .ERROR4 ,
90+ 49 : SeverityNumber .ERROR4 ,
91+ 50 : SeverityNumber .FATAL ,
92+ 51 : SeverityNumber .FATAL2 ,
93+ 52 : SeverityNumber .FATAL3 ,
94+ 53 : SeverityNumber .FATAL4 ,
95+ }
96+
97+
98+ class LoguruHandler :
99+
100+ # this was largely inspired by the OpenTelemetry handler for stdlib `logging`:
101+ # https://github.com/open-telemetry/opentelemetry-python/blob/8f312c49a5c140c14d1829c66abfe4e859ad8fd7/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py#L318
102+
103+ def __init__ (
104+ self ,
105+ logger_provider = None ,
106+ ) -> None :
107+
108+ self ._logger_provider = logger_provider or get_logger_provider ()
109+ self ._logger = get_logger (
110+ __name__ , logger_provider = self ._logger_provider
111+ )
112+
113+
114+ def _get_attributes (self , record ) -> Attributes :
115+ attributes = {key :value for key , value in record .items ()}
116+
117+ # Add standard code attributes for logs.
118+ attributes [SpanAttributes .CODE_FILEPATH ] = record ['file' ] #This includes file and path -> (file, path)
119+ attributes [SpanAttributes .CODE_FUNCTION ] = record ['function' ]
120+ attributes [SpanAttributes .CODE_LINENO ] = record ['line' ]
121+
122+ if record ['exception' ] is not None :
123+
124+ attributes [SpanAttributes .EXCEPTION_TYPE ] = record ['exception' ].type
125+
126+ attributes [SpanAttributes .EXCEPTION_MESSAGE ] = record ['exception' ].value
127+
128+ attributes [SpanAttributes .EXCEPTION_STACKTRACE ] = record ['exception' ].traceback
129+
130+ return attributes
131+
132+ def _loguru_to_otel (self , levelno : int ) -> SeverityNumber :
133+ if levelno < 10 or levelno == 25 :
134+ return SeverityNumber .UNSPECIFIED
135+
136+ elif levelno > 53 :
137+ return SeverityNumber .FATAL4
138+
139+ return _STD_TO_OTEL [levelno ]
140+
141+
142+ def _translate (self , record ) -> LogRecord :
143+
144+ #Timestamp
145+ timestamp = record ["time" ]
146+
147+ #Observed timestamp
148+ observedTimestamp = time_ns ()
149+
150+ #Span context
151+ spanContext = get_current_span ().get_span_context ()
152+
153+ #Setting the level name
154+ if record ['level' ].name == 'WARNING' :
155+ levelName = 'WARN'
156+ elif record ['level' ].name == 'TRACE' or record ['level' ].name == 'SUCCESS' :
157+ levelName = 'NOTSET'
158+ else :
159+ levelName = record ['level' ].name
160+
161+ #Severity number
162+ severityNumber = self ._loguru_to_otel (int (record ["level" ].no ))
163+
164+ #Getting attributes
165+ attributes = self ._get_attributes (record )
166+
167+
168+ return LogRecord (
169+ timestamp = timestamp ,
170+ observed_timestamp = observedTimestamp ,
171+ trace_id = spanContext .trace_id ,
172+ span_id = spanContext .span_id ,
173+ trace_flags = spanContext .trace_flags ,
174+ severity_text = levelName ,
175+ severity_number = severityNumber ,
176+ body = record ['message' ],
177+ resource = self ._logger .resource ,
178+ attributes = attributes
179+ )
180+
181+ def sink (self , record ) -> None :
182+
183+ self ._logger .emit (self ._translate (record ))
184+
0 commit comments