File tree 3 files changed +56
-3
lines changed
src/opentelemetry/sdk/logs
3 files changed +56
-3
lines changed Original file line number Diff line number Diff line change @@ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
34
34
([ #1893 ] ( https://github.com/open-telemetry/opentelemetry-python/pull/1893 ) )
35
35
36
36
### Added
37
+ - Give OTLPHandler the ability to process attributes
38
+ ([ #1952 ] ( https://github.com/open-telemetry/opentelemetry-python/pull/1952 ) )
37
39
- Add global LogEmitterProvider and convenience function get_log_emitter
38
40
([ #1901 ] ( https://github.com/open-telemetry/opentelemetry-python/pull/1901 ) )
39
41
- Add OTLPHandler for standard library logging module
Original file line number Diff line number Diff line change @@ -244,20 +244,59 @@ def force_flush(self, timeout_millis: int = 30000) -> bool:
244
244
return True
245
245
246
246
247
+ # skip natural LogRecord attributes
248
+ # http://docs.python.org/library/logging.html#logrecord-attributes
249
+ _RESERVED_ATTRS = frozenset (
250
+ (
251
+ "asctime" ,
252
+ "args" ,
253
+ "created" ,
254
+ "exc_info" ,
255
+ "exc_text" ,
256
+ "filename" ,
257
+ "funcName" ,
258
+ "getMessage" ,
259
+ "levelname" ,
260
+ "levelno" ,
261
+ "lineno" ,
262
+ "module" ,
263
+ "msecs" ,
264
+ "msg" ,
265
+ "name" ,
266
+ "pathname" ,
267
+ "process" ,
268
+ "processName" ,
269
+ "relativeCreated" ,
270
+ "stack_info" ,
271
+ "thread" ,
272
+ "threadName" ,
273
+ )
274
+ )
275
+
276
+
247
277
class OTLPHandler (logging .Handler ):
248
278
"""A handler class which writes logging records, in OTLP format, to
249
279
a network destination or file.
250
280
"""
251
281
252
- def __init__ (self , level = logging .NOTSET , log_emitter = None ) -> None :
282
+ def __init__ (
283
+ self ,
284
+ level = logging .NOTSET ,
285
+ log_emitter = None ,
286
+ ) -> None :
253
287
super ().__init__ (level = level )
254
288
self ._log_emitter = log_emitter or get_log_emitter (__name__ )
255
289
290
+ @staticmethod
291
+ def _get_attributes (record : logging .LogRecord ) -> Attributes :
292
+ return {
293
+ k : v for k , v in vars (record ).items () if k not in _RESERVED_ATTRS
294
+ }
295
+
256
296
def _translate (self , record : logging .LogRecord ) -> LogRecord :
257
297
timestamp = int (record .created * 1e9 )
258
298
span_context = get_current_span ().get_span_context ()
259
- # TODO: attributes (or resource attributes?) from record metadata
260
- attributes : Attributes = {}
299
+ attributes = self ._get_attributes (record )
261
300
severity_number = std_to_otlp (record .levelno )
262
301
return LogRecord (
263
302
timestamp = timestamp ,
Original file line number Diff line number Diff line change @@ -65,6 +65,18 @@ def test_log_record_no_span_context(self):
65
65
log_record .trace_flags , INVALID_SPAN_CONTEXT .trace_flags
66
66
)
67
67
68
+ def test_log_record_user_attributes (self ):
69
+ """Attributes can be injected into logs by adding them to the LogRecord"""
70
+ emitter_mock = Mock (spec = LogEmitter )
71
+ logger = get_logger (log_emitter = emitter_mock )
72
+ # Assert emit gets called for warning message
73
+ logger .warning ("Warning message" , extra = {"http.status_code" : 200 })
74
+ args , _ = emitter_mock .emit .call_args_list [0 ]
75
+ log_record = args [0 ]
76
+
77
+ self .assertIsNotNone (log_record )
78
+ self .assertEqual (log_record .attributes , {"http.status_code" : 200 })
79
+
68
80
def test_log_record_trace_correlation (self ):
69
81
emitter_mock = Mock (spec = LogEmitter )
70
82
logger = get_logger (log_emitter = emitter_mock )
You can’t perform that action at this time.
0 commit comments