Skip to content

Commit ea13512

Browse files
committed
opentelemetry-sdk: stop leaking handler in loggers
While at it also fix the noopprovider test case that was working because we were asserting against another handler.
1 parent 8ba2d22 commit ea13512

1 file changed

Lines changed: 68 additions & 21 deletions

File tree

opentelemetry-sdk/tests/logs/test_handler.py

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
# pylint: disable=too-many-public-methods
4040
class TestLoggingHandler(unittest.TestCase):
4141
def test_handler_default_log_level(self):
42-
processor, logger = set_up_test_logging(logging.NOTSET)
42+
processor, logger, handler = set_up_test_logging(logging.NOTSET)
4343

4444
# Make sure debug messages are ignored by default
4545
logger.debug("Debug message")
@@ -50,8 +50,10 @@ def test_handler_default_log_level(self):
5050
logger.warning("Warning message")
5151
self.assertEqual(processor.emit_count(), 1)
5252

53+
logger.removeHandler(handler)
54+
5355
def test_handler_custom_log_level(self):
54-
processor, logger = set_up_test_logging(logging.ERROR)
56+
processor, logger, handler = set_up_test_logging(logging.ERROR)
5557

5658
with self.assertLogs(level=logging.WARNING):
5759
logger.warning("Warning message test custom log level")
@@ -64,6 +66,8 @@ def test_handler_custom_log_level(self):
6466
logger.critical("No Time For Caution")
6567
self.assertEqual(processor.emit_count(), 2)
6668

69+
logger.removeHandler(handler)
70+
6771
# pylint: disable=protected-access
6872
def test_log_record_emit_noop(self):
6973
noop_logger_provder = NoOpLoggerProvider()
@@ -78,9 +82,10 @@ def test_log_record_emit_noop(self):
7882
with self.assertLogs(level=logging.WARNING):
7983
logger.warning("Warning message")
8084

85+
logger.removeHandler(handler_mock)
86+
8187
def test_log_flush_noop(self):
8288
no_op_logger_provider = NoOpLoggerProvider()
83-
no_op_logger_provider.force_flush = Mock()
8489

8590
logger = logging.getLogger("foo")
8691
handler = LoggingHandler(
@@ -91,11 +96,19 @@ def test_log_flush_noop(self):
9196
with self.assertLogs(level=logging.WARNING):
9297
logger.warning("Warning message")
9398

94-
logger.handlers[0].flush()
95-
no_op_logger_provider.force_flush.assert_not_called()
99+
# the LoggingHandler flush method will call the force_flush method of LoggerProvider in
100+
# a separate thread if present. NoOpLoggerProvider is not supposed to have that
101+
with patch(
102+
"opentelemetry.sdk._logs._internal.threading"
103+
) as threading_mock:
104+
logger.handlers[0].flush()
105+
106+
threading_mock.Thread.assert_not_called()
107+
108+
logger.removeHandler(handler)
96109

97110
def test_log_record_no_span_context(self):
98-
processor, logger = set_up_test_logging(logging.WARNING)
111+
processor, logger, handler = set_up_test_logging(logging.WARNING)
99112

100113
# Assert emit gets called for warning message
101114
with self.assertLogs(level=logging.WARNING):
@@ -115,18 +128,22 @@ def test_log_record_no_span_context(self):
115128
INVALID_SPAN_CONTEXT.trace_flags,
116129
)
117130

131+
logger.removeHandler(handler)
132+
118133
def test_log_record_observed_timestamp(self):
119-
processor, logger = set_up_test_logging(logging.WARNING)
134+
processor, logger, handler = set_up_test_logging(logging.WARNING)
120135

121136
with self.assertLogs(level=logging.WARNING):
122137
logger.warning("Warning message")
123138

124139
record = processor.get_log_record(0)
125140
self.assertIsNotNone(record.log_record.observed_timestamp)
126141

142+
logger.removeHandler(handler)
143+
127144
def test_log_record_user_attributes(self):
128145
"""Attributes can be injected into logs by adding them to the ReadWriteLogRecord"""
129-
processor, logger = set_up_test_logging(logging.WARNING)
146+
processor, logger, handler = set_up_test_logging(logging.WARNING)
130147

131148
# Assert emit gets called for warning message
132149
with self.assertLogs(level=logging.WARNING):
@@ -155,9 +172,11 @@ def test_log_record_user_attributes(self):
155172
isinstance(record.log_record.attributes, BoundedAttributes)
156173
)
157174

175+
logger.removeHandler(handler)
176+
158177
def test_log_record_exception(self):
159178
"""Exception information will be included in attributes"""
160-
processor, logger = set_up_test_logging(logging.ERROR)
179+
processor, logger, handler = set_up_test_logging(logging.ERROR)
161180

162181
try:
163182
raise ZeroDivisionError("division by zero")
@@ -189,9 +208,11 @@ def test_log_record_exception(self):
189208
self.assertTrue("division by zero" in stack_trace)
190209
self.assertTrue(__file__ in stack_trace)
191210

211+
logger.removeHandler(handler)
212+
192213
def test_log_record_recursive_exception(self):
193214
"""Exception information will be included in attributes even though it is recursive"""
194-
processor, logger = set_up_test_logging(logging.ERROR)
215+
processor, logger, handler = set_up_test_logging(logging.ERROR)
195216

196217
try:
197218
raise ZeroDivisionError(
@@ -224,9 +245,11 @@ def test_log_record_recursive_exception(self):
224245
self.assertTrue("division by zero" in stack_trace)
225246
self.assertTrue(__file__ in stack_trace)
226247

248+
logger.removeHandler(handler)
249+
227250
def test_log_exc_info_false(self):
228251
"""Exception information will not be included in attributes"""
229-
processor, logger = set_up_test_logging(logging.NOTSET)
252+
processor, logger, handler = set_up_test_logging(logging.NOTSET)
230253

231254
try:
232255
raise ZeroDivisionError("division by zero")
@@ -251,8 +274,10 @@ def test_log_exc_info_false(self):
251274
record.log_record.attributes,
252275
)
253276

277+
logger.removeHandler(handler)
278+
254279
def test_log_record_exception_with_object_payload(self):
255-
processor, logger = set_up_test_logging(logging.ERROR)
280+
processor, logger, handler = set_up_test_logging(logging.ERROR)
256281

257282
class CustomException(Exception):
258283
def __str__(self):
@@ -287,8 +312,10 @@ def __str__(self):
287312
self.assertTrue("CustomException" in stack_trace)
288313
self.assertTrue(__file__ in stack_trace)
289314

315+
logger.removeHandler(handler)
316+
290317
def test_log_record_trace_correlation(self):
291-
processor, logger = set_up_test_logging(logging.WARNING)
318+
processor, logger, handler = set_up_test_logging(logging.WARNING)
292319

293320
tracer = trace.TracerProvider().get_tracer(__name__)
294321
with tracer.start_as_current_span("test") as span:
@@ -325,8 +352,10 @@ def test_log_record_trace_correlation(self):
325352
span_context.trace_flags,
326353
)
327354

355+
logger.removeHandler(handler)
356+
328357
def test_log_record_trace_correlation_deprecated(self):
329-
processor, logger = set_up_test_logging(logging.WARNING)
358+
processor, logger, handler = set_up_test_logging(logging.WARNING)
330359

331360
tracer = trace.TracerProvider().get_tracer(__name__)
332361
with tracer.start_as_current_span("test") as span:
@@ -349,22 +378,28 @@ def test_log_record_trace_correlation_deprecated(self):
349378
record.log_record.trace_flags, span_context.trace_flags
350379
)
351380

381+
logger.removeHandler(handler)
382+
352383
def test_warning_without_formatter(self):
353-
processor, logger = set_up_test_logging(logging.WARNING)
384+
processor, logger, handler = set_up_test_logging(logging.WARNING)
354385
logger.warning("Test message")
355386

356387
record = processor.get_log_record(0)
357388
self.assertEqual(record.log_record.body, "Test message")
358389

390+
logger.removeHandler(handler)
391+
359392
def test_exception_without_formatter(self):
360-
processor, logger = set_up_test_logging(logging.WARNING)
393+
processor, logger, handler = set_up_test_logging(logging.WARNING)
361394
logger.exception("Test exception")
362395

363396
record = processor.get_log_record(0)
364397
self.assertEqual(record.log_record.body, "Test exception")
365398

399+
logger.removeHandler(handler)
400+
366401
def test_warning_with_formatter(self):
367-
processor, logger = set_up_test_logging(
402+
processor, logger, handler = set_up_test_logging(
368403
logging.WARNING,
369404
formatter=logging.Formatter(
370405
"%(name)s - %(levelname)s - %(message)s"
@@ -377,8 +412,10 @@ def test_warning_with_formatter(self):
377412
record.log_record.body, "foo - WARNING - Test message"
378413
)
379414

415+
logger.removeHandler(handler)
416+
380417
def test_log_body_is_always_string_with_formatter(self):
381-
processor, logger = set_up_test_logging(
418+
processor, logger, handler = set_up_test_logging(
382419
logging.WARNING,
383420
formatter=logging.Formatter(
384421
"%(name)s - %(levelname)s - %(message)s"
@@ -389,17 +426,21 @@ def test_log_body_is_always_string_with_formatter(self):
389426
record = processor.get_log_record(0)
390427
self.assertIsInstance(record.log_record.body, str)
391428

429+
logger.removeHandler(handler)
430+
392431
@patch.dict(os.environ, {"OTEL_SDK_DISABLED": "true"})
393432
def test_handler_root_logger_with_disabled_sdk_does_not_go_into_recursion_error(
394433
self,
395434
):
396-
processor, logger = set_up_test_logging(
435+
processor, logger, handler = set_up_test_logging(
397436
logging.NOTSET, root_logger=True
398437
)
399438
logger.warning("hello")
400439

401440
self.assertEqual(processor.emit_count(), 0)
402441

442+
logger.removeHandler(handler)
443+
403444
@patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "3"})
404445
def test_otel_attribute_count_limit_respected_in_logging_handler(self):
405446
"""Test that OTEL_ATTRIBUTE_COUNT_LIMIT is properly respected by LoggingHandler."""
@@ -439,6 +480,8 @@ def test_otel_attribute_count_limit_respected_in_logging_handler(self):
439480
f"Should have 10 dropped attributes, got {record.dropped_attributes}",
440481
)
441482

483+
logger.removeHandler(handler)
484+
442485
@patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "5"})
443486
def test_otel_attribute_count_limit_includes_code_attributes(self):
444487
"""Test that OTEL_ATTRIBUTE_COUNT_LIMIT applies to all attributes including code attributes."""
@@ -476,9 +519,11 @@ def test_otel_attribute_count_limit_includes_code_attributes(self):
476519
f"Should have 6 dropped attributes, got {record.dropped_attributes}",
477520
)
478521

522+
logger.removeHandler(handler)
523+
479524
def test_logging_handler_without_env_var_uses_default_limit(self):
480525
"""Test that without OTEL_ATTRIBUTE_COUNT_LIMIT, default limit (128) should apply."""
481-
processor, logger = set_up_test_logging(logging.WARNING)
526+
processor, logger, handler = set_up_test_logging(logging.WARNING)
482527

483528
# Create a log record with many attributes (more than default limit of 128)
484529
extra_attrs = {f"attr_{i}": f"value_{i}" for i in range(150)}
@@ -505,6 +550,8 @@ def test_logging_handler_without_env_var_uses_default_limit(self):
505550
f"Should have 25 dropped attributes, got {record.dropped_attributes}",
506551
)
507552

553+
logger.removeHandler(handler)
554+
508555

509556
def set_up_test_logging(level, formatter=None, root_logger=False):
510557
logger_provider = LoggerProvider()
@@ -515,7 +562,7 @@ def set_up_test_logging(level, formatter=None, root_logger=False):
515562
if formatter:
516563
handler.setFormatter(formatter)
517564
logger.addHandler(handler)
518-
return processor, logger
565+
return processor, logger, handler
519566

520567

521568
class FakeProcessor(LogRecordProcessor):

0 commit comments

Comments
 (0)