13
13
# limitations under the License.
14
14
import logging
15
15
import unittest
16
- from unittest .mock import Mock
16
+ from unittest .mock import Mock , MagicMock
17
+ from contextlib import contextmanager
17
18
18
19
from opentelemetry ._logs import NoOpLoggerProvider , SeverityNumber
19
20
from opentelemetry ._logs import get_logger as APIGetLogger
31
32
32
33
class TestLoggingHandler (unittest .TestCase ):
33
34
def test_handler_default_log_level (self ):
34
- processor , logger = set_up_test_logging (logging .NOTSET )
35
+ with set_up_test_logging (logging .NOTSET ) as (processor , logger ):
36
+ # Make sure debug messages are ignored by default
37
+ logger .debug ("Debug message" )
38
+ assert processor .emit_count () == 0
35
39
36
- # Make sure debug messages are ignored by default
37
- logger .debug ("Debug message" )
38
- assert processor .emit_count () == 0
39
-
40
- # Assert emit gets called for warning message
41
- with self .assertLogs (level = logging .WARNING ):
42
- logger .warning ("Warning message" )
43
- self .assertEqual (processor .emit_count (), 1 )
40
+ # Assert emit gets called for warning message
41
+ with self .assertLogs (level = logging .WARNING ):
42
+ logger .warning ("Warning message" )
43
+ self .assertEqual (processor .emit_count (), 1 )
44
44
45
45
def test_handler_custom_log_level (self ):
46
- processor , logger = set_up_test_logging (logging .ERROR )
47
-
48
- with self .assertLogs (level = logging .WARNING ):
49
- logger .warning ("Warning message test custom log level" )
50
- # Make sure any log with level < ERROR is ignored
51
- assert processor .emit_count () == 0
46
+ with set_up_test_logging (logging .ERROR ) as (processor , logger ):
47
+ with self .assertLogs (level = logging .WARNING ):
48
+ logger .warning ("Warning message test custom log level" )
49
+ # Make sure any log with level < ERROR is ignored
50
+ assert processor .emit_count () == 0
52
51
53
- with self .assertLogs (level = logging .ERROR ):
54
- logger .error ("Mumbai, we have a major problem" )
55
- with self .assertLogs (level = logging .CRITICAL ):
56
- logger .critical ("No Time For Caution" )
57
- self .assertEqual (processor .emit_count (), 2 )
52
+ with self .assertLogs (level = logging .ERROR ):
53
+ logger .error ("Mumbai, we have a major problem" )
54
+ with self .assertLogs (level = logging .CRITICAL ):
55
+ logger .critical ("No Time For Caution" )
56
+ self .assertEqual (processor .emit_count (), 2 )
58
57
59
58
# pylint: disable=protected-access
60
59
def test_log_record_emit_noop (self ):
@@ -70,6 +69,9 @@ def test_log_record_emit_noop(self):
70
69
with self .assertLogs (level = logging .WARNING ):
71
70
logger .warning ("Warning message" )
72
71
72
+ # teardown
73
+ logger .removeHandler (handler_mock )
74
+
73
75
def test_log_flush_noop (self ):
74
76
no_op_logger_provider = NoOpLoggerProvider ()
75
77
no_op_logger_provider .force_flush = Mock ()
@@ -86,173 +88,178 @@ def test_log_flush_noop(self):
86
88
logger .handlers [0 ].flush ()
87
89
no_op_logger_provider .force_flush .assert_not_called ()
88
90
89
- def test_log_record_no_span_context ( self ):
90
- processor , logger = set_up_test_logging ( logging . WARNING )
91
+ # teardown
92
+ logger . removeHandler ( handler )
91
93
92
- # Assert emit gets called for warning message
93
- with self .assertLogs (level = logging .WARNING ):
94
- logger .warning ("Warning message" )
94
+ def test_log_record_no_span_context (self ):
95
+ with set_up_test_logging (logging .WARNING ) as (processor , logger ):
96
+ # Assert emit gets called for warning message
97
+ with self .assertLogs (level = logging .WARNING ):
98
+ logger .warning ("Warning message" )
95
99
96
- log_record = processor .get_log_record (0 )
100
+ log_record = processor .get_log_record (0 )
97
101
98
- self .assertIsNotNone (log_record )
99
- self .assertEqual (log_record .trace_id , INVALID_SPAN_CONTEXT .trace_id )
100
- self .assertEqual (log_record .span_id , INVALID_SPAN_CONTEXT .span_id )
101
- self .assertEqual (
102
- log_record .trace_flags , INVALID_SPAN_CONTEXT .trace_flags
103
- )
102
+ self .assertIsNotNone (log_record )
103
+ self .assertEqual (log_record .trace_id , INVALID_SPAN_CONTEXT .trace_id )
104
+ self .assertEqual (log_record .span_id , INVALID_SPAN_CONTEXT .span_id )
105
+ self .assertEqual (
106
+ log_record .trace_flags , INVALID_SPAN_CONTEXT .trace_flags
107
+ )
104
108
105
109
def test_log_record_observed_timestamp (self ):
106
- processor , logger = set_up_test_logging (logging .WARNING )
110
+ with set_up_test_logging (logging .WARNING ) as (processor , logger ):
111
+ with self .assertLogs (level = logging .WARNING ):
112
+ logger .warning ("Warning message" )
107
113
108
- with self .assertLogs (level = logging .WARNING ):
109
- logger .warning ("Warning message" )
110
-
111
- log_record = processor .get_log_record (0 )
112
- self .assertIsNotNone (log_record .observed_timestamp )
114
+ log_record = processor .get_log_record (0 )
115
+ self .assertIsNotNone (log_record .observed_timestamp )
113
116
114
117
def test_log_record_user_attributes (self ):
115
118
"""Attributes can be injected into logs by adding them to the LogRecord"""
116
- processor , logger = set_up_test_logging (logging .WARNING )
119
+ with set_up_test_logging (logging .WARNING ) as (processor , logger ):
120
+ # Assert emit gets called for warning message
121
+ with self .assertLogs (level = logging .WARNING ):
122
+ logger .warning ("Warning message" , extra = {"http.status_code" : 200 })
117
123
118
- # Assert emit gets called for warning message
119
- with self .assertLogs (level = logging .WARNING ):
120
- logger .warning ("Warning message" , extra = {"http.status_code" : 200 })
121
-
122
- log_record = processor .get_log_record (0 )
124
+ log_record = processor .get_log_record (0 )
123
125
124
- self .assertIsNotNone (log_record )
125
- self .assertEqual (
126
- log_record .attributes ,
127
- {** log_record .attributes , ** {"http.status_code" : 200 }},
128
- )
129
- self .assertTrue (
130
- log_record .attributes [SpanAttributes .CODE_FILEPATH ].endswith (
131
- "test_handler.py"
126
+ self .assertIsNotNone (log_record )
127
+ self .assertEqual (
128
+ log_record .attributes ,
129
+ {** log_record .attributes , "http.status_code" : 200 },
132
130
)
133
- )
134
- self .assertEqual (
135
- log_record .attributes [SpanAttributes .CODE_FUNCTION ],
136
- "test_log_record_user_attributes" ,
137
- )
138
- # The line of the log statement is not a constant (changing tests may change that),
139
- # so only check that the attribute is present.
140
- self .assertTrue (SpanAttributes .CODE_LINENO in log_record .attributes )
141
- self .assertTrue (isinstance (log_record .attributes , BoundedAttributes ))
131
+ self .assertTrue (
132
+ log_record .attributes [SpanAttributes .CODE_FILEPATH ].endswith (
133
+ "test_handler.py"
134
+ )
135
+ )
136
+ self .assertEqual (
137
+ log_record .attributes [SpanAttributes .CODE_FUNCTION ],
138
+ "test_log_record_user_attributes" ,
139
+ )
140
+ # The line of the log statement is not a constant (changing tests may change that),
141
+ # so only check that the attribute is present.
142
+ self .assertTrue (SpanAttributes .CODE_LINENO in log_record .attributes )
143
+ self .assertTrue (isinstance (log_record .attributes , BoundedAttributes ))
142
144
143
145
def test_log_record_exception (self ):
144
146
"""Exception information will be included in attributes"""
145
- processor , logger = set_up_test_logging (logging .ERROR )
146
-
147
- try :
148
- raise ZeroDivisionError ("division by zero" )
149
- except ZeroDivisionError :
150
- with self .assertLogs (level = logging .ERROR ):
151
- logger .exception ("Zero Division Error" )
147
+ with set_up_test_logging (logging .ERROR ) as (processor , logger ):
148
+ try :
149
+ raise ZeroDivisionError ("division by zero" )
150
+ except ZeroDivisionError :
151
+ with self .assertLogs (level = logging .ERROR ):
152
+ logger .exception ("Zero Division Error" )
152
153
153
- log_record = processor .get_log_record (0 )
154
+ log_record = processor .get_log_record (0 )
154
155
155
- self .assertIsNotNone (log_record )
156
- self .assertIn ("Zero Division Error" , log_record .body )
157
- self .assertEqual (
158
- log_record .attributes [SpanAttributes .EXCEPTION_TYPE ],
159
- ZeroDivisionError .__name__ ,
160
- )
161
- self .assertEqual (
162
- log_record .attributes [SpanAttributes .EXCEPTION_MESSAGE ],
163
- "division by zero" ,
164
- )
165
- stack_trace = log_record .attributes [
166
- SpanAttributes .EXCEPTION_STACKTRACE
167
- ]
168
- self .assertIsInstance (stack_trace , str )
169
- self .assertTrue ("Traceback" in stack_trace )
170
- self .assertTrue ("ZeroDivisionError" in stack_trace )
171
- self .assertTrue ("division by zero" in stack_trace )
172
- self .assertTrue (__file__ in stack_trace )
156
+ self .assertIsNotNone (log_record )
157
+ self .assertIn ("Zero Division Error" , log_record .body )
158
+ self .assertEqual (
159
+ log_record .attributes [SpanAttributes .EXCEPTION_TYPE ],
160
+ ZeroDivisionError .__name__ ,
161
+ )
162
+ self .assertEqual (
163
+ log_record .attributes [SpanAttributes .EXCEPTION_MESSAGE ],
164
+ "division by zero" ,
165
+ )
166
+ stack_trace = log_record .attributes [
167
+ SpanAttributes .EXCEPTION_STACKTRACE
168
+ ]
169
+ self .assertIsInstance (stack_trace , str )
170
+ self .assertTrue ("Traceback" in stack_trace )
171
+ self .assertTrue ("ZeroDivisionError" in stack_trace )
172
+ self .assertTrue ("division by zero" in stack_trace )
173
+ self .assertTrue (__file__ in stack_trace )
173
174
174
175
def test_log_exc_info_false (self ):
175
176
"""Exception information will be included in attributes"""
176
- processor , logger = set_up_test_logging (logging .NOTSET )
177
-
178
- try :
179
- raise ZeroDivisionError ("division by zero" )
180
- except ZeroDivisionError :
181
- with self .assertLogs (level = logging .ERROR ):
182
- logger .error ("Zero Division Error" , exc_info = False )
177
+ with set_up_test_logging (logging .NOTSET ) as (processor , logger ):
178
+ try :
179
+ raise ZeroDivisionError ("division by zero" )
180
+ except ZeroDivisionError :
181
+ with self .assertLogs (level = logging .ERROR ):
182
+ logger .error ("Zero Division Error" , exc_info = False )
183
183
184
- log_record = processor .get_log_record (0 )
184
+ log_record = processor .get_log_record (0 )
185
185
186
- self .assertIsNotNone (log_record )
187
- self .assertEqual (log_record .body , "Zero Division Error" )
188
- self .assertNotIn (SpanAttributes .EXCEPTION_TYPE , log_record .attributes )
189
- self .assertNotIn (
190
- SpanAttributes .EXCEPTION_MESSAGE , log_record .attributes
191
- )
192
- self .assertNotIn (
193
- SpanAttributes .EXCEPTION_STACKTRACE , log_record .attributes
194
- )
186
+ self .assertIsNotNone (log_record )
187
+ self .assertEqual (log_record .body , "Zero Division Error" )
188
+ self .assertNotIn (SpanAttributes .EXCEPTION_TYPE , log_record .attributes )
189
+ self .assertNotIn (
190
+ SpanAttributes .EXCEPTION_MESSAGE , log_record .attributes
191
+ )
192
+ self .assertNotIn (
193
+ SpanAttributes .EXCEPTION_STACKTRACE , log_record .attributes
194
+ )
195
195
196
196
def test_log_record_trace_correlation (self ):
197
- processor , logger = set_up_test_logging (logging .WARNING )
197
+ with set_up_test_logging (logging .WARNING ) as (processor , logger ):
198
+ tracer = trace .TracerProvider ().get_tracer (__name__ )
199
+ with tracer .start_as_current_span ("test" ) as span :
200
+ with self .assertLogs (level = logging .CRITICAL ):
201
+ logger .critical ("Critical message within span" )
202
+
203
+ log_record = processor .get_log_record (0 )
204
+
205
+ self .assertEqual (log_record .body , "Critical message within span" )
206
+ self .assertEqual (log_record .severity_text , "CRITICAL" )
207
+ self .assertEqual (log_record .severity_number , SeverityNumber .FATAL )
208
+ span_context = span .get_span_context ()
209
+ self .assertEqual (log_record .trace_id , span_context .trace_id )
210
+ self .assertEqual (log_record .span_id , span_context .span_id )
211
+ self .assertEqual (log_record .trace_flags , span_context .trace_flags )
198
212
199
- tracer = trace .TracerProvider ().get_tracer (__name__ )
200
- with tracer .start_as_current_span ("test" ) as span :
201
- with self .assertLogs (level = logging .CRITICAL ):
202
- logger .critical ("Critical message within span" )
213
+ def test_log_record_args_are_translated (self ):
214
+ with set_up_test_logging (logging .WARNING ) as (processor , logger ):
215
+ logger .warning ("Test message" )
203
216
204
217
log_record = processor .get_log_record (0 )
218
+ self .assertEqual (
219
+ set (log_record .attributes ),
220
+ {
221
+ "code.filepath" ,
222
+ "code.lineno" ,
223
+ "code.function" ,
224
+ },
225
+ )
205
226
206
- self .assertEqual (log_record .body , "Critical message within span" )
207
- self .assertEqual (log_record .severity_text , "CRITICAL" )
208
- self .assertEqual (log_record .severity_number , SeverityNumber .FATAL )
209
- span_context = span .get_span_context ()
210
- self .assertEqual (log_record .trace_id , span_context .trace_id )
211
- self .assertEqual (log_record .span_id , span_context .span_id )
212
- self .assertEqual (log_record .trace_flags , span_context .trace_flags )
227
+ def test_format_is_called (self ):
228
+ with set_up_test_logging (logging .WARNING , formatter = logging .Formatter ("%(name)s - %(levelname)s - %(message)s" )) as (processor , logger ):
229
+ logger .warning ("Test message" )
213
230
214
- def test_log_record_args_are_translated (self ):
215
- processor , logger = set_up_test_logging (logging .INFO )
216
-
217
- with self .assertLogs (level = logging .INFO ):
218
- logger .info ("Test message" )
219
-
220
- log_record = processor .get_log_record (0 )
221
- self .assertEqual (
222
- set (log_record .attributes ),
223
- {
224
- "thread.id" ,
225
- "code.filepath" ,
226
- "code.lineno" ,
227
- "code.function" ,
228
- "thread.name" ,
229
- },
230
- )
231
+ log_record = processor .get_log_record (0 )
232
+ self .assertEqual (
233
+ log_record .body , "foo - WARNING - Test message"
234
+ )
231
235
232
- def test_format_is_called (self ):
233
- processor , logger = set_up_test_logging (
234
- logging .INFO ,
235
- logging .Formatter ("%(name)s - %(levelname)s - %(message)s" )
236
- )
236
+ def test_log_body_is_always_string (self ):
237
+ with set_up_test_logging (logging .WARNING ) as (processor , logger ):
238
+ logger .warning (["something" , "of" , "note" ])
237
239
238
- with self . assertLogs ( level = logging . INFO ):
239
- logger . info ( "Test message" )
240
+ log_record = processor . get_log_record ( 0 )
241
+ self . assertIsInstance ( log_record . body , str )
240
242
241
- log_record = processor .get_log_record (0 )
242
- self .assertEqual (
243
- log_record .body , "foo - INFO - Test message"
244
- )
243
+ def test_args_get_transformed_if_logged (self ):
244
+ with set_up_test_logging (logging .WARNING ) as (_ , logger ):
245
245
246
- def test_log_body_is_always_string (self ):
247
- processor , logger = set_up_test_logging (logging .INFO )
246
+ my_object = MagicMock ()
247
+ logger .warning ("%s - %d" , my_object , my_object )
248
+
249
+ self .assertTrue (my_object .__str__ .called )
250
+ self .assertTrue (my_object .__int__ .called )
251
+
252
+ def test_args_do_not_get_transformed_if_not_logged (self ):
253
+ with set_up_test_logging (logging .WARNING ) as (_ , logger ):
248
254
249
- with self . assertLogs ( level = logging . INFO ):
250
- logger .info ([ "something " , "of" , "note" ] )
255
+ my_object = MagicMock ()
256
+ logger .info ("%s - %d " , my_object , my_object )
251
257
252
- log_record = processor . get_log_record ( 0 )
253
- self .assertIsInstance ( log_record . body , str )
258
+ self . assertFalse ( my_object . __str__ . called )
259
+ self .assertFalse ( my_object . __int__ . called )
254
260
255
261
262
+ @contextmanager
256
263
def set_up_test_logging (level , formatter = None ):
257
264
logger_provider = LoggerProvider ()
258
265
processor = FakeProcessor ()
@@ -262,7 +269,8 @@ def set_up_test_logging(level, formatter=None):
262
269
if formatter :
263
270
handler .setFormatter (formatter )
264
271
logger .addHandler (handler )
265
- return processor , logger
272
+ yield processor , logger
273
+ logger .removeHandler (handler )
266
274
267
275
268
276
class FakeProcessor (LogRecordProcessor ):
0 commit comments