Skip to content

Commit 4dad82a

Browse files
aabmassDylanRussellemdneto
authored
Fix max recursion bug by removing logging.log calls in emit (#4588) (#4589)
* Fix max recursion bug by removing logging.log calls in emit * Update opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py * 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: DylanRussell <DylanRussell@users.noreply.github.com> Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com>
1 parent 65904db commit 4dad82a

File tree

3 files changed

+45
-9
lines changed

3 files changed

+45
-9
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
- Remove log messages from `BatchLogRecordProcessor.emit`, this caused the program
11+
to crash at shutdown with a max recursion error ([#4586](https://github.com/open-telemetry/opentelemetry-python/pull/4586)).
12+
813
## Version 1.33.0/0.54b0 (2025-05-09)
914

1015
- Fix intermittent `Connection aborted` error when using otlp/http exporters

opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,15 +286,16 @@ def _export(self, batch_strategy: BatchLogExportStrategy) -> None:
286286
_logger.exception("Exception while exporting logs.")
287287
detach(token)
288288

289+
# Do not add any logging.log statements to this function, they can be being routed back to this `emit` function,
290+
# resulting in endless recursive calls that crash the program.
291+
# See https://github.com/open-telemetry/opentelemetry-python/issues/4261
289292
def emit(self, log_data: LogData) -> None:
290293
if self._shutdown:
291-
_logger.info("Shutdown called, ignoring log.")
292294
return
293295
if self._pid != os.getpid():
294296
_BSP_RESET_ONCE.do_once(self._at_fork_reinit)
295297

296-
if len(self._queue) == self._max_queue_size:
297-
_logger.warning("Queue full, dropping log.")
298+
# This will drop a log from the right side if the queue is at _max_queue_length.
298299
self._queue.appendleft(log_data)
299300
if len(self._queue) >= self._max_export_batch_size:
300301
self._worker_awaken.set()

opentelemetry-sdk/tests/logs/test_export.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121
import unittest
2222
import weakref
2323
from concurrent.futures import ThreadPoolExecutor
24+
from sys import version_info
2425
from unittest.mock import Mock, patch
2526

27+
from pytest import mark
28+
2629
from opentelemetry._logs import SeverityNumber
2730
from opentelemetry.sdk import trace
2831
from opentelemetry.sdk._logs import (
@@ -347,6 +350,39 @@ def test_emit_call_log_record(self):
347350
logger.error("error")
348351
self.assertEqual(log_record_processor.emit.call_count, 1)
349352

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

520-
with self.assertLogs(level="INFO") as log:
521-
# This log should not be flushed.
522-
log_record_processor.emit(EMPTY_LOG)
523-
self.assertEqual(len(log.output), 1)
524-
self.assertEqual(len(log.records), 1)
525-
self.assertIn("Shutdown called, ignoring log.", log.output[0])
526556
exporter.export.assert_called_once()
527557

528558
# pylint: disable=no-self-use

0 commit comments

Comments
 (0)