diff --git a/CHANGELOG.md b/CHANGELOG.md index c37861aa3b5..d5f6c7aea88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Silence events API warnings for internal users ([#4847](https://github.com/open-telemetry/opentelemetry-python/pull/4847)) - Prevent possible endless recursion from happening in `SimpleLogRecordProcessor.on_emit`, - ([#4799](https://github.com/open-telemetry/opentelemetry-python/pull/4799)). + ([#4799](https://github.com/open-telemetry/opentelemetry-python/pull/4799)) and ([#4867](https://github.com/open-telemetry/opentelemetry-python/pull/4867)). ## Version 1.39.0/0.60b0 (2025-12-03) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index cad7f951428..39772554d5a 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -160,6 +160,7 @@ def detach(token: Token[Context]) -> None: # FIXME This is a temporary location for the suppress instrumentation key. # Once the decision around how to suppress instrumentation is made in the # spec, this key should be moved accordingly. +_ON_EMIT_RECURSION_COUNT_KEY = create_key("on_emit_recursion_count") _SUPPRESS_INSTRUMENTATION_KEY = create_key("suppress_instrumentation") _SUPPRESS_HTTP_INSTRUMENTATION_KEY = create_key( "suppress_http_instrumentation" 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 ba0861c2a03..f12b9dd8a2d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -17,16 +17,17 @@ import enum import logging import sys -import traceback from os import environ, linesep from typing import IO, Callable, Optional, Sequence from typing_extensions import deprecated from opentelemetry.context import ( + _ON_EMIT_RECURSION_COUNT_KEY, _SUPPRESS_INSTRUMENTATION_KEY, attach, detach, + get_value, set_value, ) from opentelemetry.sdk._logs import ( @@ -150,28 +151,23 @@ def __init__(self, exporter: LogRecordExporter): def on_emit(self, log_record: ReadWriteLogRecord): # Prevent entering a recursive loop. - if ( - sum( - item.name == "on_emit" - 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() - ) - # 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 3 - # recursive log calls but exits after because it's likely endless. - > 3 - ): + cnt = get_value(_ON_EMIT_RECURSION_COUNT_KEY) or 0 + # 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 3 + # recursive log calls but exits after because it's likely endless. + if cnt > 3: # pyright: ignore[reportOperatorIssue] _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)) + token = attach( + set_value( + _SUPPRESS_INSTRUMENTATION_KEY, + True, + set_value(_ON_EMIT_RECURSION_COUNT_KEY, cnt + 1), # pyright: ignore[reportOperatorIssue] + ) + ) try: if self._shutdown: _logger.warning("Processor is already shutdown, ignoring call")