1313# limitations under the License.
1414import logging
1515import unittest
16- from unittest .mock import Mock
16+ from unittest .mock import Mock , MagicMock
17+ from contextlib import contextmanager
1718
1819from opentelemetry ._logs import NoOpLoggerProvider , SeverityNumber
1920from opentelemetry ._logs import get_logger as APIGetLogger
3132
3233class TestLoggingHandler (unittest .TestCase ):
3334 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
3539
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 )
4444
4545 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
5251
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 )
5857
5958 # pylint: disable=protected-access
6059 def test_log_record_emit_noop (self ):
@@ -70,6 +69,9 @@ def test_log_record_emit_noop(self):
7069 with self .assertLogs (level = logging .WARNING ):
7170 logger .warning ("Warning message" )
7271
72+ # teardown
73+ logger .removeHandler (handler_mock )
74+
7375 def test_log_flush_noop (self ):
7476 no_op_logger_provider = NoOpLoggerProvider ()
7577 no_op_logger_provider .force_flush = Mock ()
@@ -86,173 +88,178 @@ def test_log_flush_noop(self):
8688 logger .handlers [0 ].flush ()
8789 no_op_logger_provider .force_flush .assert_not_called ()
8890
89- def test_log_record_no_span_context ( self ):
90- processor , logger = set_up_test_logging ( logging . WARNING )
91+ # teardown
92+ logger . removeHandler ( handler )
9193
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" )
9599
96- log_record = processor .get_log_record (0 )
100+ log_record = processor .get_log_record (0 )
97101
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+ )
104108
105109 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" )
107113
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 )
113116
114117 def test_log_record_user_attributes (self ):
115118 """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 })
117123
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 )
123125
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 },
132130 )
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 ))
142144
143145 def test_log_record_exception (self ):
144146 """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" )
152153
153- log_record = processor .get_log_record (0 )
154+ log_record = processor .get_log_record (0 )
154155
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 )
173174
174175 def test_log_exc_info_false (self ):
175176 """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 )
183183
184- log_record = processor .get_log_record (0 )
184+ log_record = processor .get_log_record (0 )
185185
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+ )
195195
196196 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 )
198212
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" )
203216
204217 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+ )
205226
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" )
213230
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+ )
231235
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" ])
237239
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 )
240242
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 ):
245245
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 ):
248254
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 )
251257
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 )
254260
255261
262+ @contextmanager
256263def set_up_test_logging (level , formatter = None ):
257264 logger_provider = LoggerProvider ()
258265 processor = FakeProcessor ()
@@ -262,7 +269,8 @@ def set_up_test_logging(level, formatter=None):
262269 if formatter :
263270 handler .setFormatter (formatter )
264271 logger .addHandler (handler )
265- return processor , logger
272+ yield processor , logger
273+ logger .removeHandler (handler )
266274
267275
268276class FakeProcessor (LogRecordProcessor ):
0 commit comments