Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/agents/tracing/provider.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import logging
import os
import threading
import uuid
Expand All @@ -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.
Expand Down
38 changes: 38 additions & 0 deletions tests/test_tracing_provider_safe_debug.py
Original file line number Diff line number Diff line change
@@ -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