Skip to content

Commit 71ec859

Browse files
DylanRussellemdnetoaabmass
authored
Fix max recursion bug by removing logging.log calls in emit (open-telemetry#4588)
* Fix max recursion bug by removing logging.log calls in emit * Update opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py Co-authored-by: Emídio Neto <[email protected]> * Add changelog * Fix linter * Respond to comments in PR * Remove log line * Fix lint issues * Attach handler to SDK logger instead of root logger and remote it after test is done. Fix lint issues.. * Assert no logs emitted. ADd try/except block for cleanup * Skip test on <python3.10 * Run precommit * Apply suggestions from code review * use try/finally instead of try/except --------- Co-authored-by: Emídio Neto <[email protected]> Co-authored-by: Aaron Abbott <[email protected]>
1 parent 3462204 commit 71ec859

File tree

4 files changed

+43
-7
lines changed

4 files changed

+43
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Refactor `BatchLogRecordProcessor` to simplify code and make the control flow more
1313
clear ([#4562](https://github.com/open-telemetry/opentelemetry-python/pull/4562/)
1414
and [#4535](https://github.com/open-telemetry/opentelemetry-python/pull/4535)).
15+
- Remove log messages from `BatchLogRecordProcessor.emit`, this caused the program
16+
to crash at shutdown with a max recursion error ([#4586](https://github.com/open-telemetry/opentelemetry-python/pull/4586)).
1517

1618
## Version 1.33.0/0.54b0 (2025-05-09)
1719

opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,15 @@ def _export(self, batch_strategy: BatchExportStrategy) -> None:
167167
)
168168
detach(token)
169169

170+
# Do not add any logging.log statements to this function, they can be being routed back to this `emit` function,
171+
# resulting in endless recursive calls that crash the program.
172+
# See https://github.com/open-telemetry/opentelemetry-python/issues/4261
170173
def emit(self, data: Telemetry) -> None:
171174
if self._shutdown:
172-
self._logger.info("Shutdown called, ignoring %s.", self._exporting)
173175
return
174176
if self._pid != os.getpid():
175177
self._bsp_reset_once.do_once(self._at_fork_reinit)
176-
177-
if len(self._queue) == self._max_queue_size:
178-
self._logger.warning("Queue full, dropping %s.", self._exporting)
178+
# This will drop a log from the right side if the queue is at _max_queue_length.
179179
self._queue.appendleft(data)
180180
if len(self._queue) >= self._max_export_batch_size:
181181
self._worker_awaken.set()

opentelemetry-sdk/tests/logs/test_export.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
import os
1818
import time
1919
import unittest
20+
from sys import version_info
2021
from unittest.mock import Mock, patch
2122

23+
from pytest import mark
24+
2225
from opentelemetry._logs import SeverityNumber
2326
from opentelemetry.sdk import trace
2427
from opentelemetry.sdk._logs import (
@@ -342,6 +345,39 @@ def test_emit_call_log_record(self):
342345
logger.error("error")
343346
self.assertEqual(log_record_processor.emit.call_count, 1)
344347

348+
@mark.skipif(
349+
version_info < (3, 10),
350+
reason="assertNoLogs only exists in python 3.10+.",
351+
)
352+
def test_logging_lib_not_invoked_in_batch_log_record_emit(self): # pylint: disable=no-self-use
353+
# See https://github.com/open-telemetry/opentelemetry-python/issues/4261
354+
exporter = Mock()
355+
processor = BatchLogRecordProcessor(exporter)
356+
logger_provider = LoggerProvider(
357+
resource=SDKResource.create(
358+
{
359+
"service.name": "shoppingcart",
360+
"service.instance.id": "instance-12",
361+
}
362+
),
363+
)
364+
logger_provider.add_log_record_processor(processor)
365+
handler = LoggingHandler(
366+
level=logging.INFO, logger_provider=logger_provider
367+
)
368+
sdk_logger = logging.getLogger("opentelemetry.sdk")
369+
# Attach OTLP handler to SDK logger
370+
sdk_logger.addHandler(handler)
371+
# If `emit` calls logging.log then this test will throw a maximum recursion depth exceeded exception and fail.
372+
try:
373+
with self.assertNoLogs(sdk_logger, logging.NOTSET):
374+
processor.emit(EMPTY_LOG)
375+
processor.shutdown()
376+
with self.assertNoLogs(sdk_logger, logging.NOTSET):
377+
processor.emit(EMPTY_LOG)
378+
finally:
379+
sdk_logger.removeHandler(handler)
380+
345381
def test_args(self):
346382
exporter = InMemoryLogExporter()
347383
log_record_processor = BatchLogRecordProcessor(

opentelemetry-sdk/tests/shared_internal/test_batch_processor.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def test_telemetry_exported_once_schedule_delay_reached(
8888
exporter.export.assert_called_once_with([telemetry])
8989

9090
def test_telemetry_flushed_before_shutdown_and_dropped_after_shutdown(
91-
self, batch_processor_class, telemetry, caplog
91+
self, batch_processor_class, telemetry
9292
):
9393
exporter = Mock()
9494
batch_processor = batch_processor_class(
@@ -107,8 +107,6 @@ def test_telemetry_flushed_before_shutdown_and_dropped_after_shutdown(
107107

108108
# This should not be flushed.
109109
batch_processor.emit(telemetry)
110-
assert len(caplog.records) == 1
111-
assert "Shutdown called, ignoring" in caplog.text
112110
exporter.export.assert_called_once()
113111

114112
# pylint: disable=no-self-use

0 commit comments

Comments
 (0)