Skip to content
Open
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
7 changes: 4 additions & 3 deletions python/packages/core/agent_framework/observability.py
Original file line number Diff line number Diff line change
Expand Up @@ -1355,9 +1355,10 @@ def _configure_providers(
logger_provider = LoggerProvider(resource=resource)
for log_exporter in log_exporters:
logger_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))
# Attach a handler with the provider to the root logger
handler = LoggingHandler(logger_provider=logger_provider)
logger.addHandler(handler)
# Attach a handler with the provider to the agent_framework logger, unless the root logger
# already has an OTel handler: records propagate to it and would otherwise be exported twice.
if not any(isinstance(h, LoggingHandler) for h in logging.getLogger().handlers):
logger.addHandler(LoggingHandler(logger_provider=logger_provider))
set_logger_provider(logger_provider)

# metrics
Expand Down
34 changes: 34 additions & 0 deletions python/packages/core/tests/core/test_observability.py
Original file line number Diff line number Diff line change
Expand Up @@ -7727,3 +7727,37 @@ async def test_chat_capture_messages_called_when_span_recording(
assert mock_capture_v1_36.call_count == 2
assert mock_capture_experimental.call_count == 2
assert mock_capture_response.call_count == 1


def _otel_log_handlers(target: logging.Logger) -> list[Any]:
from opentelemetry.sdk._logs import LoggingHandler

return [h for h in target.handlers if isinstance(h, LoggingHandler)]


@pytest.mark.parametrize("root_handler_first", [False, True])
def test_configure_providers_log_handler_not_duplicated_with_root_handler(root_handler_first: bool):
"""MAF records are exported once when the app already has an OTel handler on the root logger."""
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import InMemoryLogRecordExporter

from agent_framework.observability import ObservabilitySettings

exporter = InMemoryLogRecordExporter()
root = logging.getLogger()
maf_logger = logging.getLogger("agent_framework")
root_handler = LoggingHandler(logger_provider=LoggerProvider())
original_root_handlers = list(root.handlers)
original_maf_handlers = list(maf_logger.handlers)
handlers_before = len(_otel_log_handlers(maf_logger))
try:
if root_handler_first:
root.addHandler(root_handler)
with patch("opentelemetry._logs.set_logger_provider"):
ObservabilitySettings(enable_instrumentation=True)._configure_providers([exporter]) # type: ignore[reportPrivateUsage]

added = len(_otel_log_handlers(maf_logger)) - handlers_before
assert added == (0 if root_handler_first else 1)
finally:
root.handlers[:] = original_root_handlers
maf_logger.handlers[:] = original_maf_handlers
Loading