Skip to content

Commit 24d2767

Browse files
authored
Harden tracing shutdown logging against closed handlers (#2270)
1 parent 78a683c commit 24d2767

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

src/agents/tracing/provider.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import logging
34
import os
45
import threading
56
import uuid
@@ -17,7 +18,23 @@
1718

1819
def _safe_debug(message: str) -> None:
1920
"""Best-effort debug logging that tolerates closed streams during shutdown."""
21+
22+
def _has_closed_stream_handler(log: logging.Logger) -> bool:
23+
current: logging.Logger | None = log
24+
while current is not None:
25+
for handler in current.handlers:
26+
stream = getattr(handler, "stream", None)
27+
if stream is not None and getattr(stream, "closed", False):
28+
return True
29+
if not current.propagate:
30+
break
31+
current = current.parent
32+
return False
33+
2034
try:
35+
# Avoid emitting debug logs when any handler already owns a closed stream.
36+
if _has_closed_stream_handler(logger):
37+
return
2138
logger.debug(message)
2239
except Exception:
2340
# Avoid noisy shutdown errors when the underlying stream is already closed.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from __future__ import annotations
2+
3+
import io
4+
import logging
5+
6+
from agents.logger import logger
7+
from agents.tracing.provider import _safe_debug
8+
9+
10+
class _CapturingHandler(logging.Handler):
11+
def __init__(self) -> None:
12+
super().__init__()
13+
self.records: list[logging.LogRecord] = []
14+
15+
def emit(self, record: logging.LogRecord) -> None: # pragma: no cover - trivial
16+
self.records.append(record)
17+
18+
19+
def test_safe_debug_skips_logging_when_handler_stream_closed() -> None:
20+
original_handlers = logger.handlers[:]
21+
original_propagate = logger.propagate
22+
23+
closed_stream = io.StringIO()
24+
closed_handler = logging.StreamHandler(closed_stream)
25+
closed_stream.close()
26+
27+
capturing_handler = _CapturingHandler()
28+
29+
try:
30+
logger.handlers = [closed_handler, capturing_handler]
31+
logger.propagate = False
32+
33+
_safe_debug("should not log")
34+
35+
assert capturing_handler.records == []
36+
finally:
37+
logger.handlers = original_handlers
38+
logger.propagate = original_propagate

0 commit comments

Comments
 (0)