From 2ef08f8b6e455524d70b2bafe1beccae7ddf7288 Mon Sep 17 00:00:00 2001 From: Dylan Russell Date: Thu, 6 Nov 2025 15:38:09 +0000 Subject: [PATCH 01/10] Initial commit --- opentelemetry-api/test-requirements.txt | 2 +- .../sdk/_logs/_internal/export/__init__.py | 4 ++++ .../sdk/_shared_internal/__init__.py | 8 +++++--- opentelemetry-sdk/test-requirements.txt | 2 +- opentelemetry-sdk/tests/logs/test_export.py | 20 +++++++++++++++++++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/opentelemetry-api/test-requirements.txt b/opentelemetry-api/test-requirements.txt index d13bcf6875c..360573104e6 100644 --- a/opentelemetry-api/test-requirements.txt +++ b/opentelemetry-api/test-requirements.txt @@ -12,5 +12,5 @@ wrapt==1.16.0 zipp==3.20.2 -e opentelemetry-sdk -e opentelemetry-semantic-conventions --e tests/opentelemetry-test-utils -e opentelemetry-api +-e tests/opentelemetry-test-utils diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py index e632800c8cf..6e1d1008839 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -17,6 +17,7 @@ import enum import logging import sys +import traceback from os import environ, linesep from typing import IO, Callable, Optional, Sequence @@ -118,6 +119,9 @@ def __init__(self, exporter: LogExporter): self._shutdown = False def on_emit(self, log_data: LogData): + # Prevent entering recursive loop. + if any(item[2] == "on_emit" for item in traceback.extract_stack()): + return if self._shutdown: _logger.warning("Processor is already shutdown, ignoring call") return diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py index cb617253698..aad0aaeebbc 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py @@ -81,6 +81,10 @@ def shutdown(self): raise NotImplementedError +_logger = logging.getLogger(__name__) +_logger.addFilter(DuplicateFilter()) + + class BatchProcessor(Generic[Telemetry]): """This class can be used with exporter's that implement the above Exporter interface to buffer and send telemetry in batch through @@ -111,8 +115,6 @@ def __init__( target=self.worker, daemon=True, ) - self._logger = logging.getLogger(__name__) - self._logger.addFilter(DuplicateFilter()) self._exporting = exporting self._shutdown = False @@ -189,7 +191,7 @@ def _export(self, batch_strategy: BatchExportStrategy) -> None: ] ) except Exception: # pylint: disable=broad-exception-caught - self._logger.exception( + _logger.exception( "Exception while exporting %s.", self._exporting ) detach(token) diff --git a/opentelemetry-sdk/test-requirements.txt b/opentelemetry-sdk/test-requirements.txt index 859a2196e1a..96eb7601eed 100644 --- a/opentelemetry-sdk/test-requirements.txt +++ b/opentelemetry-sdk/test-requirements.txt @@ -11,7 +11,7 @@ tomli==2.0.1 typing_extensions==4.10.0 wrapt==1.16.0 zipp==3.19.2 --e tests/opentelemetry-test-utils -e opentelemetry-api +-e tests/opentelemetry-test-utils -e opentelemetry-semantic-conventions -e opentelemetry-sdk \ No newline at end of file diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index 4b8d98693c5..30a71b620f5 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -19,6 +19,7 @@ import unittest from concurrent.futures import ThreadPoolExecutor from sys import version_info +from typing import Sequence from unittest.mock import Mock, patch from pytest import mark @@ -36,6 +37,7 @@ BatchLogRecordProcessor, ConsoleLogExporter, InMemoryLogExporter, + LogExporter, SimpleLogRecordProcessor, ) from opentelemetry.sdk.environment_variables import ( @@ -61,6 +63,24 @@ class TestSimpleLogRecordProcessor(unittest.TestCase): + def test_simple_log_record_processor_doesnt_enter_recursive_loop(self): + class Exporter(LogExporter): + def shutdown(self): + pass + + def export(self, batch: Sequence[LogData]): + raise ValueError("Exception raised !") + + exporter = Exporter() + logger_provider = LoggerProvider() + logger_provider.add_log_record_processor( + SimpleLogRecordProcessor(exporter) + ) + logger = logging.getLogger("default_level") + logger.addHandler(LoggingHandler(logger_provider=logger_provider)) + # This would cause a max recursion depth exceeded error.. + logger.warning("Something is wrong") + def test_simple_log_record_processor_default_level(self): exporter = InMemoryLogExporter() logger_provider = LoggerProvider() From 9b904ace07b14c348a0b65001e967b0b71fad1df Mon Sep 17 00:00:00 2001 From: Dylan Russell Date: Thu, 6 Nov 2025 18:49:30 +0000 Subject: [PATCH 02/10] Make changes --- .../opentelemetry/sdk/_logs/_internal/export/__init__.py | 6 +++++- opentelemetry-sdk/tests/logs/test_export.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py index 6e1d1008839..39a74029042 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -120,7 +120,11 @@ def __init__(self, exporter: LogExporter): def on_emit(self, log_data: LogData): # Prevent entering recursive loop. - if any(item[2] == "on_emit" for item in traceback.extract_stack()): + if sum( + item.name == "on_emit" + and item.filename.endswith("export/__init__.py") + for item in traceback.extract_stack() + ) > 1: return if self._shutdown: _logger.warning("Processor is already shutdown, ignoring call") diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index 30a71b620f5..a6131dab3e6 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -76,10 +76,10 @@ def export(self, batch: Sequence[LogData]): logger_provider.add_log_record_processor( SimpleLogRecordProcessor(exporter) ) - logger = logging.getLogger("default_level") - logger.addHandler(LoggingHandler(logger_provider=logger_provider)) + root_logger = logging.getLogger() + root_logger.addHandler(LoggingHandler(level=logging.NOTSET,logger_provider=logger_provider)) # This would cause a max recursion depth exceeded error.. - logger.warning("Something is wrong") + root_logger.warning("Something is wrong") def test_simple_log_record_processor_default_level(self): exporter = InMemoryLogExporter() From a48b45ab226f0df368349670c2ff50a5420392f2 Mon Sep 17 00:00:00 2001 From: Dylan Russell Date: Fri, 7 Nov 2025 20:41:21 +0000 Subject: [PATCH 03/10] Make changes to approach --- .../sdk/_logs/_internal/export/__init__.py | 39 +++++++----- .../sdk/_shared_internal/__init__.py | 19 ++++-- opentelemetry-sdk/tests/logs/test_export.py | 63 ++++++++----------- 3 files changed, 63 insertions(+), 58 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py index 39a74029042..d69af7762c3 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -17,7 +17,7 @@ import enum import logging import sys -import traceback +from contextvars import ContextVar from os import environ, linesep from typing import IO, Callable, Optional, Sequence @@ -50,6 +50,10 @@ _logger = logging.getLogger(__name__) _logger.addFilter(DuplicateFilter()) +_propagate_false_logger = logging.getLogger(__name__ + ".propagate.false") +_propagate_false_logger.addFilter(DuplicateFilter()) +_propagate_false_logger.propagate = False + class LogExportResult(enum.Enum): SUCCESS = 0 @@ -115,26 +119,31 @@ class SimpleLogRecordProcessor(LogRecordProcessor): """ def __init__(self, exporter: LogExporter): + self._emit_executing = ContextVar("var", default=False) self._exporter = exporter self._shutdown = False def on_emit(self, log_data: LogData): - # Prevent entering recursive loop. - if sum( - item.name == "on_emit" - and item.filename.endswith("export/__init__.py") - for item in traceback.extract_stack() - ) > 1: - return - if self._shutdown: - _logger.warning("Processor is already shutdown, ignoring call") + # Prevent entering a recursive loop. + if self._emit_executing.get(): + _propagate_false_logger.warning( + "SimpleLogRecordProcessor.on_emit has entered a recursive loop. Dropping log and exiting the loop." + ) return - token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True)) + emit_token = self._emit_executing.set(True) + suppress_token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True)) try: - self._exporter.export((log_data,)) - except Exception: # pylint: disable=broad-exception-caught - _logger.exception("Exception while exporting logs.") - detach(token) + if self._shutdown: + _logger.warning("Processor is already shutdown, ignoring call") + return + + try: + self._exporter.export((log_data,)) + except Exception: # pylint: disable=broad-exception-caught + _logger.exception("Exception while exporting logs.") + finally: + self._emit_executing.reset(emit_token) + detach(suppress_token) def shutdown(self): self._shutdown = True diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py index aad0aaeebbc..181524e5386 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py @@ -42,8 +42,8 @@ class DuplicateFilter(logging.Filter): """Filter that can be applied to internal `logger`'s. - Currently applied to `logger`s on the export logs path that could otherwise cause endless logging of errors or a - recursion depth exceeded issue in cases where logging itself results in an exception.""" + Currently applied to `logger`'s on the export logs path to prevent endlessly logging the same log + in cases where logging itself is failing.""" def filter(self, record): current_log = ( @@ -83,6 +83,9 @@ def shutdown(self): _logger = logging.getLogger(__name__) _logger.addFilter(DuplicateFilter()) +_propagate_false_logger = logging.getLogger(__name__ + ".propagate.false") +_propagate_false_logger.addFilter(DuplicateFilter()) +_propagate_false_logger.propagate = False class BatchProcessor(Generic[Telemetry]): @@ -196,15 +199,19 @@ def _export(self, batch_strategy: BatchExportStrategy) -> None: ) detach(token) - # Do not add any logging.log statements to this function, they can be being routed back to this `emit` function, - # resulting in endless recursive calls that crash the program. - # See https://github.com/open-telemetry/opentelemetry-python/issues/4261 def emit(self, data: Telemetry) -> None: if self._shutdown: + _logger.info( + "Shutdown called, ignoring %s.", self._exporting + ) return if self._pid != os.getpid(): self._bsp_reset_once.do_once(self._at_fork_reinit) - # This will drop a log from the right side if the queue is at _max_queue_length. + if len(self._queue) == self._max_queue_size: + _logger.warning( + "Queue full, dropping %s.", self._exporting + ) + # This will drop a log from the right side if the queue is at _max_queue_size. self._queue.appendleft(data) if len(self._queue) >= self._max_export_batch_size: self._worker_awaken.set() diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index a6131dab3e6..f6d9c4a7709 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -15,10 +15,10 @@ # pylint: disable=protected-access import logging import os +import random import time import unittest from concurrent.futures import ThreadPoolExecutor -from sys import version_info from typing import Sequence from unittest.mock import Mock, patch @@ -63,13 +63,19 @@ class TestSimpleLogRecordProcessor(unittest.TestCase): + @mark.skipif( + version_info=(3, 13), + reason="This will fail on 3.13 due to https://github.com/python/cpython/pull/131812 which prevents recursive log messages but was later rolled back.", + ) def test_simple_log_record_processor_doesnt_enter_recursive_loop(self): class Exporter(LogExporter): def shutdown(self): pass def export(self, batch: Sequence[LogData]): - raise ValueError("Exception raised !") + raise ValueError( + "Exception raised ! {}".format(random.randint(1, 10000)) + ) exporter = Exporter() logger_provider = LoggerProvider() @@ -77,9 +83,25 @@ def export(self, batch: Sequence[LogData]): SimpleLogRecordProcessor(exporter) ) root_logger = logging.getLogger() - root_logger.addHandler(LoggingHandler(level=logging.NOTSET,logger_provider=logger_provider)) + # Add the OTLP handler to the root logger like is done in auto instrumentation. + # This means logs generated from within SimpleLogRecordProcessor.on_emit are sent back to SimpleLogRecordProcessor.on_emit + handler = LoggingHandler( + level=logging.DEBUG, logger_provider=logger_provider + ) + root_logger.addHandler(handler) + propagate_false_logger = logging.getLogger( + "opentelemetry.sdk._logs._internal.export.propagate.false" + ) # This would cause a max recursion depth exceeded error.. - root_logger.warning("Something is wrong") + try: + with self.assertLogs(propagate_false_logger) as cm: + root_logger.warning("hello!") + assert ( + "SimpleLogRecordProcessor.on_emit has entered a recursive loop" + in cm.output[0] + ) + finally: + root_logger.removeHandler(handler) def test_simple_log_record_processor_default_level(self): exporter = InMemoryLogExporter() @@ -403,39 +425,6 @@ def bulk_emit(num_emit): time.sleep(2) assert len(exporter.get_finished_logs()) == total_expected_logs - @mark.skipif( - version_info < (3, 10), - reason="assertNoLogs only exists in python 3.10+.", - ) - def test_logging_lib_not_invoked_in_batch_log_record_emit(self): # pylint: disable=no-self-use - # See https://github.com/open-telemetry/opentelemetry-python/issues/4261 - exporter = Mock() - processor = BatchLogRecordProcessor(exporter) - logger_provider = LoggerProvider( - resource=SDKResource.create( - { - "service.name": "shoppingcart", - "service.instance.id": "instance-12", - } - ), - ) - logger_provider.add_log_record_processor(processor) - handler = LoggingHandler( - level=logging.INFO, logger_provider=logger_provider - ) - sdk_logger = logging.getLogger("opentelemetry.sdk") - # Attach OTLP handler to SDK logger - sdk_logger.addHandler(handler) - # If `emit` calls logging.log then this test will throw a maximum recursion depth exceeded exception and fail. - try: - with self.assertNoLogs(sdk_logger, logging.NOTSET): - processor.on_emit(EMPTY_LOG) - processor.shutdown() - with self.assertNoLogs(sdk_logger, logging.NOTSET): - processor.on_emit(EMPTY_LOG) - finally: - sdk_logger.removeHandler(handler) - def test_args(self): exporter = InMemoryLogExporter() log_record_processor = BatchLogRecordProcessor( From b8b39b35a8db283853a87eea10ad7562a5ae5a8f Mon Sep 17 00:00:00 2001 From: Dylan Russell Date: Fri, 7 Nov 2025 21:16:36 +0000 Subject: [PATCH 04/10] Make more changes --- CHANGELOG.md | 2 ++ .../sdk/_logs/_internal/export/__init__.py | 14 +++++++++----- .../opentelemetry/sdk/_shared_internal/__init__.py | 8 ++------ opentelemetry-sdk/tests/logs/test_export.py | 12 ++++++------ 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70b13589e3d..ba05da3a61f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4782](https://github.com/open-telemetry/opentelemetry-python/pull/4782)) - semantic-conventions: Bump to 1.38.0 ([#4791](https://github.com/open-telemetry/opentelemetry-python/pull/4791)) +- Prevent possible endless recursion from happening in `SimpleLogRecordProcessor.on_emit`, + ([#4799](https://github.com/open-telemetry/opentelemetry-python/pull/4799)). ## Version 1.38.0/0.59b0 (2025-10-16) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py index d69af7762c3..4b2f8a2829c 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -17,7 +17,7 @@ import enum import logging import sys -from contextvars import ContextVar +import traceback from os import environ, linesep from typing import IO, Callable, Optional, Sequence @@ -119,18 +119,23 @@ class SimpleLogRecordProcessor(LogRecordProcessor): """ def __init__(self, exporter: LogExporter): - self._emit_executing = ContextVar("var", default=False) self._exporter = exporter self._shutdown = False def on_emit(self, log_data: LogData): # Prevent entering a recursive loop. - if self._emit_executing.get(): + if ( + sum( + item.name == "on_emit" + and item.filename.endswith("export/__init__.py") + for item in traceback.extract_stack() + ) + > 3 + ): _propagate_false_logger.warning( "SimpleLogRecordProcessor.on_emit has entered a recursive loop. Dropping log and exiting the loop." ) return - emit_token = self._emit_executing.set(True) suppress_token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True)) try: if self._shutdown: @@ -142,7 +147,6 @@ def on_emit(self, log_data: LogData): except Exception: # pylint: disable=broad-exception-caught _logger.exception("Exception while exporting logs.") finally: - self._emit_executing.reset(emit_token) detach(suppress_token) def shutdown(self): diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py index 181524e5386..6901affd7e7 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py @@ -201,16 +201,12 @@ def _export(self, batch_strategy: BatchExportStrategy) -> None: def emit(self, data: Telemetry) -> None: if self._shutdown: - _logger.info( - "Shutdown called, ignoring %s.", self._exporting - ) + _logger.info("Shutdown called, ignoring %s.", self._exporting) return if self._pid != os.getpid(): self._bsp_reset_once.do_once(self._at_fork_reinit) if len(self._queue) == self._max_queue_size: - _logger.warning( - "Queue full, dropping %s.", self._exporting - ) + _logger.warning("Queue full, dropping %s.", self._exporting) # This will drop a log from the right side if the queue is at _max_queue_size. self._queue.appendleft(data) if len(self._queue) >= self._max_export_batch_size: diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index f6d9c4a7709..369edd9d263 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -15,7 +15,7 @@ # pylint: disable=protected-access import logging import os -import random +import sys import time import unittest from concurrent.futures import ThreadPoolExecutor @@ -64,7 +64,7 @@ class TestSimpleLogRecordProcessor(unittest.TestCase): @mark.skipif( - version_info=(3, 13), + sys.version_info == (3, 13), reason="This will fail on 3.13 due to https://github.com/python/cpython/pull/131812 which prevents recursive log messages but was later rolled back.", ) def test_simple_log_record_processor_doesnt_enter_recursive_loop(self): @@ -73,9 +73,8 @@ def shutdown(self): pass def export(self, batch: Sequence[LogData]): - raise ValueError( - "Exception raised ! {}".format(random.randint(1, 10000)) - ) + logger = logging.getLogger("any logger..") + logger.warning("Something happened.") exporter = Exporter() logger_provider = LoggerProvider() @@ -84,7 +83,8 @@ def export(self, batch: Sequence[LogData]): ) root_logger = logging.getLogger() # Add the OTLP handler to the root logger like is done in auto instrumentation. - # This means logs generated from within SimpleLogRecordProcessor.on_emit are sent back to SimpleLogRecordProcessor.on_emit + # This causes logs generated from within SimpleLogRecordProcessor.on_emit (such as the above log in export) + # to be sent back to SimpleLogRecordProcessor.on_emit handler = LoggingHandler( level=logging.DEBUG, logger_provider=logger_provider ) From ddd199fbea881fae55a0649a7976a8fbcf9ee83a Mon Sep 17 00:00:00 2001 From: Dylan Russell Date: Fri, 7 Nov 2025 21:36:05 +0000 Subject: [PATCH 05/10] windows is failing but not sure why --- .../src/opentelemetry/sdk/_logs/_internal/export/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py index 4b2f8a2829c..f3ea471a94e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -123,6 +123,7 @@ def __init__(self, exporter: LogExporter): self._shutdown = False def on_emit(self, log_data: LogData): + print(traceback.extract_stack()) # Prevent entering a recursive loop. if ( sum( From ff2d035456194d14be665949bab77b0a85760c69 Mon Sep 17 00:00:00 2001 From: Dylan Russell Date: Fri, 7 Nov 2025 21:42:06 +0000 Subject: [PATCH 06/10] Fix bug on windows.. --- .../opentelemetry/sdk/_logs/_internal/export/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py index f3ea471a94e..df8e07fa2ca 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -123,12 +123,16 @@ def __init__(self, exporter: LogExporter): self._shutdown = False def on_emit(self, log_data: LogData): - print(traceback.extract_stack()) # Prevent entering a recursive loop. if ( sum( item.name == "on_emit" - and item.filename.endswith("export/__init__.py") + and ( + item.filename.endswith("export/__init__.py") + or item.filename.endswith( + r"export\__init__.py" + ) # backward slash on windows.. + ) for item in traceback.extract_stack() ) > 3 From b81f94702ba4019756d8b8e46be0d76062a2005c Mon Sep 17 00:00:00 2001 From: Dylan Russell Date: Fri, 7 Nov 2025 21:46:13 +0000 Subject: [PATCH 07/10] Commit changes --- .../src/opentelemetry/sdk/_logs/_internal/export/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py index df8e07fa2ca..d8440a52710 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -141,7 +141,7 @@ def on_emit(self, log_data: LogData): "SimpleLogRecordProcessor.on_emit has entered a recursive loop. Dropping log and exiting the loop." ) return - suppress_token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True)) + token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True)) try: if self._shutdown: _logger.warning("Processor is already shutdown, ignoring call") @@ -152,7 +152,7 @@ def on_emit(self, log_data: LogData): except Exception: # pylint: disable=broad-exception-caught _logger.exception("Exception while exporting logs.") finally: - detach(suppress_token) + detach(token) def shutdown(self): self._shutdown = True From 6f1a5a70600f1c7109271a1ed3134ef5e8bd9aae Mon Sep 17 00:00:00 2001 From: DylanRussell Date: Wed, 12 Nov 2025 16:41:48 +0000 Subject: [PATCH 08/10] Update opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py Co-authored-by: Riccardo Magliocchetti --- .../src/opentelemetry/sdk/_shared_internal/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py index 6901affd7e7..2113af864f5 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py @@ -42,7 +42,7 @@ class DuplicateFilter(logging.Filter): """Filter that can be applied to internal `logger`'s. - Currently applied to `logger`'s on the export logs path to prevent endlessly logging the same log + Currently applied to `logger`s on the export logs path to prevent endlessly logging the same log in cases where logging itself is failing.""" def filter(self, record): From a5e1e86ca769f2522740e3e10e398e4ee561f021 Mon Sep 17 00:00:00 2001 From: Dylan Russell Date: Wed, 12 Nov 2025 16:52:41 +0000 Subject: [PATCH 09/10] Respond to comment --- .../src/opentelemetry/sdk/_logs/_internal/export/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py index d8440a52710..8fe0eaefe9f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -135,6 +135,10 @@ def on_emit(self, log_data: LogData): ) for item in traceback.extract_stack() ) + # Recursive depth of 3 is sort of arbitrary. It's possible that an Exporter.export call + # emits a log which returns us to this function, but when we call Exporter.export again the log + # is no longer emitted and we exit this recursive loop naturally, a depth of 3 allows some + # Exporter.export recursive log calls but exits after a few. > 3 ): _propagate_false_logger.warning( From 41e9d9561977c4270d45e6c5d7d3e39d3450382d Mon Sep 17 00:00:00 2001 From: Dylan Russell Date: Thu, 13 Nov 2025 14:23:15 +0000 Subject: [PATCH 10/10] remove duplicate filter from propagate false logger --- .../src/opentelemetry/sdk/_logs/_internal/export/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py index 8fe0eaefe9f..48d24eede62 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -51,7 +51,6 @@ _logger.addFilter(DuplicateFilter()) _propagate_false_logger = logging.getLogger(__name__ + ".propagate.false") -_propagate_false_logger.addFilter(DuplicateFilter()) _propagate_false_logger.propagate = False