19
19
from opentelemetry ._logs import get_logger as APIGetLogger
20
20
from opentelemetry .attributes import BoundedAttributes
21
21
from opentelemetry .sdk import trace
22
- from opentelemetry .sdk ._logs import LoggerProvider , LoggingHandler
22
+ from opentelemetry .sdk ._logs import (
23
+ LogData ,
24
+ LoggerProvider ,
25
+ LoggingHandler ,
26
+ LogRecordProcessor ,
27
+ )
23
28
from opentelemetry .semconv .trace import SpanAttributes
24
29
from opentelemetry .trace import INVALID_SPAN_CONTEXT
25
30
26
31
27
- def get_logger (level = logging .NOTSET , logger_provider = None ):
28
- logger = logging .getLogger (__name__ )
29
- handler = LoggingHandler (level = level , logger_provider = logger_provider )
30
- logger .addHandler (handler )
31
- return logger
32
-
33
-
34
32
class TestLoggingHandler (unittest .TestCase ):
35
33
def test_handler_default_log_level (self ):
36
- emitter_provider_mock = Mock (spec = LoggerProvider )
37
- emitter_mock = APIGetLogger (
38
- __name__ , logger_provider = emitter_provider_mock
39
- )
40
- logger = get_logger (logger_provider = emitter_provider_mock )
34
+ processor , logger = set_up_test_logging (logging .NOTSET )
35
+
41
36
# Make sure debug messages are ignored by default
42
37
logger .debug ("Debug message" )
43
- self .assertEqual (emitter_mock .emit .call_count , 0 )
38
+ assert processor .emit_count () == 0
39
+
44
40
# Assert emit gets called for warning message
45
41
with self .assertLogs (level = logging .WARNING ):
46
42
logger .warning ("Warning message" )
47
- self .assertEqual (emitter_mock . emit . call_count , 1 )
43
+ self .assertEqual (processor . emit_count () , 1 )
48
44
49
45
def test_handler_custom_log_level (self ):
50
- emitter_provider_mock = Mock (spec = LoggerProvider )
51
- emitter_mock = APIGetLogger (
52
- __name__ , logger_provider = emitter_provider_mock
53
- )
54
- logger = get_logger (
55
- level = logging .ERROR , logger_provider = emitter_provider_mock
56
- )
46
+ processor , logger = set_up_test_logging (logging .ERROR )
47
+
57
48
with self .assertLogs (level = logging .WARNING ):
58
49
logger .warning ("Warning message test custom log level" )
59
50
# Make sure any log with level < ERROR is ignored
60
- self .assertEqual (emitter_mock .emit .call_count , 0 )
51
+ assert processor .emit_count () == 0
52
+
61
53
with self .assertLogs (level = logging .ERROR ):
62
54
logger .error ("Mumbai, we have a major problem" )
63
55
with self .assertLogs (level = logging .CRITICAL ):
64
56
logger .critical ("No Time For Caution" )
65
- self .assertEqual (emitter_mock . emit . call_count , 2 )
57
+ self .assertEqual (processor . emit_count () , 2 )
66
58
67
59
# pylint: disable=protected-access
68
60
def test_log_record_emit_noop (self ):
@@ -77,14 +69,16 @@ def test_log_record_emit_noop(self):
77
69
logger .addHandler (handler_mock )
78
70
with self .assertLogs (level = logging .WARNING ):
79
71
logger .warning ("Warning message" )
80
- handler_mock ._translate .assert_not_called ()
81
72
82
73
def test_log_flush_noop (self ):
83
-
84
74
no_op_logger_provider = NoOpLoggerProvider ()
85
75
no_op_logger_provider .force_flush = Mock ()
86
76
87
- logger = get_logger (logger_provider = no_op_logger_provider )
77
+ logger = logging .getLogger ("foo" )
78
+ handler = LoggingHandler (
79
+ level = logging .NOTSET , logger_provider = no_op_logger_provider
80
+ )
81
+ logger .addHandler (handler )
88
82
89
83
with self .assertLogs (level = logging .WARNING ):
90
84
logger .warning ("Warning message" )
@@ -93,16 +87,13 @@ def test_log_flush_noop(self):
93
87
no_op_logger_provider .force_flush .assert_not_called ()
94
88
95
89
def test_log_record_no_span_context (self ):
96
- emitter_provider_mock = Mock (spec = LoggerProvider )
97
- emitter_mock = APIGetLogger (
98
- __name__ , logger_provider = emitter_provider_mock
99
- )
100
- logger = get_logger (logger_provider = emitter_provider_mock )
90
+ processor , logger = set_up_test_logging (logging .WARNING )
91
+
101
92
# Assert emit gets called for warning message
102
93
with self .assertLogs (level = logging .WARNING ):
103
94
logger .warning ("Warning message" )
104
- args , _ = emitter_mock . emit . call_args_list [ 0 ]
105
- log_record = args [ 0 ]
95
+
96
+ log_record = processor . get_log_record ( 0 )
106
97
107
98
self .assertIsNotNone (log_record )
108
99
self .assertEqual (log_record .trace_id , INVALID_SPAN_CONTEXT .trace_id )
@@ -112,31 +103,23 @@ def test_log_record_no_span_context(self):
112
103
)
113
104
114
105
def test_log_record_observed_timestamp (self ):
115
- emitter_provider_mock = Mock (spec = LoggerProvider )
116
- emitter_mock = APIGetLogger (
117
- __name__ , logger_provider = emitter_provider_mock
118
- )
119
- logger = get_logger (logger_provider = emitter_provider_mock )
120
- # Assert emit gets called for warning message
106
+ processor , logger = set_up_test_logging (logging .WARNING )
107
+
121
108
with self .assertLogs (level = logging .WARNING ):
122
109
logger .warning ("Warning message" )
123
- args , _ = emitter_mock .emit .call_args_list [0 ]
124
- log_record = args [0 ]
125
110
111
+ log_record = processor .get_log_record (0 )
126
112
self .assertIsNotNone (log_record .observed_timestamp )
127
113
128
114
def test_log_record_user_attributes (self ):
129
115
"""Attributes can be injected into logs by adding them to the LogRecord"""
130
- emitter_provider_mock = Mock (spec = LoggerProvider )
131
- emitter_mock = APIGetLogger (
132
- __name__ , logger_provider = emitter_provider_mock
133
- )
134
- logger = get_logger (logger_provider = emitter_provider_mock )
116
+ processor , logger = set_up_test_logging (logging .WARNING )
117
+
135
118
# Assert emit gets called for warning message
136
119
with self .assertLogs (level = logging .WARNING ):
137
120
logger .warning ("Warning message" , extra = {"http.status_code" : 200 })
138
- args , _ = emitter_mock . emit . call_args_list [ 0 ]
139
- log_record = args [ 0 ]
121
+
122
+ log_record = processor . get_log_record ( 0 )
140
123
141
124
self .assertIsNotNone (log_record )
142
125
self .assertEqual (len (log_record .attributes ), 4 )
@@ -157,18 +140,15 @@ def test_log_record_user_attributes(self):
157
140
158
141
def test_log_record_exception (self ):
159
142
"""Exception information will be included in attributes"""
160
- emitter_provider_mock = Mock (spec = LoggerProvider )
161
- emitter_mock = APIGetLogger (
162
- __name__ , logger_provider = emitter_provider_mock
163
- )
164
- logger = get_logger (logger_provider = emitter_provider_mock )
143
+ processor , logger = set_up_test_logging (logging .ERROR )
144
+
165
145
try :
166
146
raise ZeroDivisionError ("division by zero" )
167
147
except ZeroDivisionError :
168
148
with self .assertLogs (level = logging .ERROR ):
169
149
logger .exception ("Zero Division Error" )
170
- args , _ = emitter_mock . emit . call_args_list [ 0 ]
171
- log_record = args [ 0 ]
150
+
151
+ log_record = processor . get_log_record ( 0 )
172
152
173
153
self .assertIsNotNone (log_record )
174
154
self .assertEqual (log_record .body , "Zero Division Error" )
@@ -191,18 +171,15 @@ def test_log_record_exception(self):
191
171
192
172
def test_log_exc_info_false (self ):
193
173
"""Exception information will be included in attributes"""
194
- emitter_provider_mock = Mock (spec = LoggerProvider )
195
- emitter_mock = APIGetLogger (
196
- __name__ , logger_provider = emitter_provider_mock
197
- )
198
- logger = get_logger (logger_provider = emitter_provider_mock )
174
+ processor , logger = set_up_test_logging (logging .NOTSET )
175
+
199
176
try :
200
177
raise ZeroDivisionError ("division by zero" )
201
178
except ZeroDivisionError :
202
179
with self .assertLogs (level = logging .ERROR ):
203
180
logger .error ("Zero Division Error" , exc_info = False )
204
- args , _ = emitter_mock . emit . call_args_list [ 0 ]
205
- log_record = args [ 0 ]
181
+
182
+ log_record = processor . get_log_record ( 0 )
206
183
207
184
self .assertIsNotNone (log_record )
208
185
self .assertEqual (log_record .body , "Zero Division Error" )
@@ -215,23 +192,49 @@ def test_log_exc_info_false(self):
215
192
)
216
193
217
194
def test_log_record_trace_correlation (self ):
218
- emitter_provider_mock = Mock (spec = LoggerProvider )
219
- emitter_mock = APIGetLogger (
220
- __name__ , logger_provider = emitter_provider_mock
221
- )
222
- logger = get_logger (logger_provider = emitter_provider_mock )
195
+ processor , logger = set_up_test_logging (logging .WARNING )
223
196
224
197
tracer = trace .TracerProvider ().get_tracer (__name__ )
225
198
with tracer .start_as_current_span ("test" ) as span :
226
199
with self .assertLogs (level = logging .CRITICAL ):
227
200
logger .critical ("Critical message within span" )
228
201
229
- args , _ = emitter_mock . emit . call_args_list [ 0 ]
230
- log_record = args [ 0 ]
202
+ log_record = processor . get_log_record ( 0 )
203
+
231
204
self .assertEqual (log_record .body , "Critical message within span" )
232
205
self .assertEqual (log_record .severity_text , "CRITICAL" )
233
206
self .assertEqual (log_record .severity_number , SeverityNumber .FATAL )
234
207
span_context = span .get_span_context ()
235
208
self .assertEqual (log_record .trace_id , span_context .trace_id )
236
209
self .assertEqual (log_record .span_id , span_context .span_id )
237
210
self .assertEqual (log_record .trace_flags , span_context .trace_flags )
211
+
212
+
213
+ def set_up_test_logging (level ):
214
+ logger_provider = LoggerProvider ()
215
+ processor = FakeProcessor ()
216
+ logger_provider .add_log_record_processor (processor )
217
+ logger = logging .getLogger ("foo" )
218
+ handler = LoggingHandler (level = level , logger_provider = logger_provider )
219
+ logger .addHandler (handler )
220
+ return processor , logger
221
+
222
+
223
+ class FakeProcessor (LogRecordProcessor ):
224
+ def __init__ (self ):
225
+ self .log_data_emitted = []
226
+
227
+ def emit (self , log_data : LogData ):
228
+ self .log_data_emitted .append (log_data )
229
+
230
+ def shutdown (self ):
231
+ pass
232
+
233
+ def force_flush (self , timeout_millis : int = 30000 ):
234
+ pass
235
+
236
+ def emit_count (self ):
237
+ return len (self .log_data_emitted )
238
+
239
+ def get_log_record (self , i ):
240
+ return self .log_data_emitted [i ].log_record
0 commit comments