diff --git a/src/agents/tracing/provider.py b/src/agents/tracing/provider.py index cd37d30ef2..1b91269524 100644 --- a/src/agents/tracing/provider.py +++ b/src/agents/tracing/provider.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging import os import threading import uuid @@ -17,7 +18,23 @@ def _safe_debug(message: str) -> None: """Best-effort debug logging that tolerates closed streams during shutdown.""" + + def _has_closed_stream_handler(log: logging.Logger) -> bool: + current: logging.Logger | None = log + while current is not None: + for handler in current.handlers: + stream = getattr(handler, "stream", None) + if stream is not None and getattr(stream, "closed", False): + return True + if not current.propagate: + break + current = current.parent + return False + try: + # Avoid emitting debug logs when any handler already owns a closed stream. + if _has_closed_stream_handler(logger): + return logger.debug(message) except Exception: # Avoid noisy shutdown errors when the underlying stream is already closed. diff --git a/tests/test_tracing_provider_safe_debug.py b/tests/test_tracing_provider_safe_debug.py new file mode 100644 index 0000000000..d49441171c --- /dev/null +++ b/tests/test_tracing_provider_safe_debug.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import io +import logging + +from agents.logger import logger +from agents.tracing.provider import _safe_debug + + +class _CapturingHandler(logging.Handler): + def __init__(self) -> None: + super().__init__() + self.records: list[logging.LogRecord] = [] + + def emit(self, record: logging.LogRecord) -> None: # pragma: no cover - trivial + self.records.append(record) + + +def test_safe_debug_skips_logging_when_handler_stream_closed() -> None: + original_handlers = logger.handlers[:] + original_propagate = logger.propagate + + closed_stream = io.StringIO() + closed_handler = logging.StreamHandler(closed_stream) + closed_stream.close() + + capturing_handler = _CapturingHandler() + + try: + logger.handlers = [closed_handler, capturing_handler] + logger.propagate = False + + _safe_debug("should not log") + + assert capturing_handler.records == [] + finally: + logger.handlers = original_handlers + logger.propagate = original_propagate