Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1ba43a4
fix tracing for operation id to flow in exception table.
Jun 8, 2026
13ecc29
Fix OTel context lifecycle in tracing middleware
Jun 8, 2026
66ae7a2
Add test for safe deferred OTel detach
Jun 8, 2026
01c74fe
Revert "Add test for safe deferred OTel detach"
Jun 8, 2026
52a9c9b
Reset test_tracing.py to main
Jun 8, 2026
f4c618e
Harden deferred OTel detach callback
Jun 8, 2026
748d9f3
Simplify deferred detach path
Jun 8, 2026
836b042
Detach deferred OTel token safely
Jun 8, 2026
04ec5af
Add fallback when call_soon scheduling fails
Jun 8, 2026
98419e4
Reuse detach_context for deferred cleanup
Jun 8, 2026
3a8134a
Enrich logs with agent and session dimensions
Jun 8, 2026
950a618
Fix log enrichment fallback and drop sessionid dimension
Jun 8, 2026
5243eb2
Fix trace context detach and agent identity resolution
Jun 8, 2026
c0e39a3
Add AZURE_AI_* agent identity fallback
Jun 8, 2026
f2ab16b
Revert agent name/version fallback changes
Jun 8, 2026
ab46ec2
Revert agent name/version enrichment changes
Jun 8, 2026
3ea1488
Remove redundant NonRecordingSpan creation in request context middleware
Jun 9, 2026
0fc985b
Merge branch 'main' of https://github.com/Azure/azure-sdk-for-python …
Jun 9, 2026
8f206f4
Detach trace context safely after request handling
Jun 9, 2026
14d291c
Restore tracing middleware to 3ea1488 behavior
Jun 9, 2026
a45875e
Revert "Restore tracing middleware to 3ea1488 behavior"
Jun 9, 2026
5ad7e00
Fix trace context detach cleanup
Jun 9, 2026
6cf096b
Remove unused asyncio import in tracing
Jun 9, 2026
48b325e
Defer trace context detach for access log correlation
Jun 9, 2026
1b312d0
Add UT for deferred trace context lifecycle
Jun 10, 2026
f2a7899
Fix pylint do-not-import-asyncio gate
Jun 10, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
OpenTelemetry is a required dependency — these functions always create
real spans. Azure Monitor export is optional (auto-configured by the distro).
"""
import asyncio
import logging
import os
from collections.abc import AsyncIterable, AsyncIterator # pylint: disable=import-error
Expand Down Expand Up @@ -294,14 +295,25 @@ async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
"x_request_id", x_request_id, context=ctx,
)

# Create a NonRecordingSpan with the extracted trace context so that
# get_current_span() returns a span carrying the correct trace_id.
# Without this, the OTel LogRecord processor sees no active span and
# sets trace_id=0, causing zeroed operation_Id in Application Insights
# for logs emitted by Hypercorn's access logger.
span = trace.get_current_span(ctx)
span_ctx = span.get_span_context()
if span_ctx and span_ctx.is_valid:
non_recording = trace.NonRecordingSpan(span_ctx)
Comment thread
harsheet-shah marked this conversation as resolved.
Outdated
ctx = trace.set_span_in_context(non_recording, ctx)
Comment thread
harsheet-shah marked this conversation as resolved.
Outdated

# Attach request context for app execution and defer detach by one
# event-loop turn so server-side access logging that runs immediately
# after app return can still observe the request trace context.
token = _otel_context.attach(ctx)
try:
await self.app(scope, receive, send)
finally:
try:
_otel_context.detach(token)
except ValueError:
pass
asyncio.get_running_loop().call_soon(detach_context, token)
Comment thread
harsheet-shah marked this conversation as resolved.
Outdated
Comment thread
harsheet-shah marked this conversation as resolved.
Outdated


def end_span(span: Any, exc: Optional[BaseException] = None) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------
"""Tests for tracing configuration — not invocation spans (those live in the invocations package)."""
import asyncio
import os
from unittest import mock

Expand All @@ -16,7 +17,7 @@
resolve_agent_version,
resolve_appinsights_connection_string,
)
from azure.ai.agentserver.core._tracing import _FoundryEnrichmentSpanProcessor
from azure.ai.agentserver.core._tracing import TraceContextMiddleware, _FoundryEnrichmentSpanProcessor


class _CollectorExporter(SpanExporter):
Expand Down Expand Up @@ -431,4 +432,69 @@ def test_agent_version_default_empty(self) -> None:
assert resolve_agent_version() == ""


class TestTraceContextMiddleware:
"""Trace context middleware lifecycle behavior."""

def test_detach_is_deferred_until_after_app_returns(self) -> None:
events = []

async def app(scope, receive, send):
events.append("app-called")

async def run_test():
middleware = TraceContextMiddleware(app)
scope = {"type": "http", "headers": []}

async def receive():
return {}

async def send(_message):
return None

Comment thread
harsheet-shah marked this conversation as resolved.
Outdated
with mock.patch("azure.ai.agentserver.core._tracing._otel_context.attach", return_value="token") as attach_mock, \
mock.patch("azure.ai.agentserver.core._tracing._otel_context.detach") as detach_mock:
await middleware(scope, receive, send)
assert events == ["app-called"]
attach_mock.assert_called_once()
detach_mock.assert_not_called()

await asyncio.sleep(0)
detach_mock.assert_called_once_with("token")
Comment thread
harsheet-shah marked this conversation as resolved.
Outdated

asyncio.run(run_test())
Comment thread
harsheet-shah marked this conversation as resolved.
Outdated
Comment thread
harsheet-shah marked this conversation as resolved.
Outdated

def test_detach_still_happens_when_app_raises(self) -> None:
class _AppError(RuntimeError):
pass

async def app(scope, receive, send):
raise _AppError("boom")

async def run_test():
middleware = TraceContextMiddleware(app)
scope = {"type": "http", "headers": []}

async def receive():
return {}

async def send(_message):
return None

with mock.patch("azure.ai.agentserver.core._tracing._otel_context.attach", return_value="token") as attach_mock, \
mock.patch("azure.ai.agentserver.core._tracing._otel_context.detach") as detach_mock:
try:
await middleware(scope, receive, send)
except _AppError:
pass
else:
raise AssertionError("expected middleware to propagate app exception")

attach_mock.assert_called_once()
detach_mock.assert_not_called()

await asyncio.sleep(0)
detach_mock.assert_called_once_with("token")

asyncio.run(run_test())
Comment thread
harsheet-shah marked this conversation as resolved.
Outdated


Loading