FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat(tracing): establish W3C trace context at FastACP ingress by NiteshDhanpal · Pull Request #466 · scaleapi/scale-agentex-python · GitHub

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (1) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
58 changes: 52 additions & 6 deletions src/agentex/lib/sdk/fastacp/base/base_acp_server.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,65 @@
task_message_update_adapter = TypeAdapter(TaskMessageUpdate)


def _attach_incoming_trace_context(scope: Scope) -> Any:
"""Extract the W3C trace context (traceparent/tracestate) from the inbound
request headers and attach it as the current OpenTelemetry context, so spans
created while handling this request adopt the caller's observability trace.

Returns a detach token (pass to opentelemetry.context.detach) or None if OTel
is unavailable or no context could be established. Best-effort: never raises.
"""
try:
from opentelemetry import context as otel_context
from opentelemetry.propagate import extract
except ImportError:
return None
try:
carrier = {
k.decode("latin-1"): v.decode("latin-1")
for k, v in scope.get("headers", [])
}
ctx = extract(carrier)
Comment on lines +62 to +67

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Duplicate tracestate headers silently dropped

The plain-dict carrier overwrites duplicate keys, so if an intermediary forwards multiple tracestate headers (which the W3C Trace Context spec explicitly permits), all but the last entry are silently discarded. The OTel DefaultGetter calls carrier.get(key) on a plain dict, which can only return a single string value, so combined trace-state from several systems in the propagation chain would be partially lost.

To handle multi-value headers correctly, the carrier should concatenate repeated values with a comma separator before inserting them into the dict, or use a multi-value mapping with a custom Getter.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/sdk/fastacp/base/base_acp_server.py
Line: 62-67

Comment:
**Duplicate `tracestate` headers silently dropped**

The plain-dict carrier overwrites duplicate keys, so if an intermediary forwards multiple `tracestate` headers (which the W3C Trace Context spec explicitly permits), all but the last entry are silently discarded. The OTel `DefaultGetter` calls `carrier.get(key)` on a plain dict, which can only return a single string value, so combined trace-state from several systems in the propagation chain would be partially lost.

To handle multi-value headers correctly, the carrier should concatenate repeated values with a comma separator before inserting them into the dict, or use a multi-value mapping with a custom `Getter`.

How can I resolve this? If you propose a fix, please make it concise.

return otel_context.attach(ctx)
except Exception: # pragma: no cover - propagation must never break a request
return None


def _detach_trace_context(token: Any) -> None:
if token is None:
return
try:
from opentelemetry import context as otel_context

otel_context.detach(token)
except Exception: # pragma: no cover
pass


class RequestIDMiddleware:
"""Pure ASGI middleware to set request IDs without buffering streaming responses."""

def __init__(self, app: ASGIApp) -> None:
self.app = app

async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] == "http":
headers = dict(scope.get("headers", []))
raw_request_id = headers.get(b"x-request-id", b"")
request_id = raw_request_id.decode() if raw_request_id else uuid.uuid4().hex
ctx_var_request_id.set(request_id)
await self.app(scope, receive, send)
if scope["type"] != "http":
await self.app(scope, receive, send)
return

headers = dict(scope.get("headers", []))
raw_request_id = headers.get(b"x-request-id", b"")
request_id = raw_request_id.decode() if raw_request_id else uuid.uuid4().hex
ctx_var_request_id.set(request_id)

# Establish the caller's W3C trace context for the duration of the
# request so business spans created here (and any downstream Temporal
# workflow started from this request) share the same observability trace.
token = _attach_incoming_trace_context(scope)
try:
await self.app(scope, receive, send)
finally:
_detach_trace_context(token)


class BaseACPServer(FastAPI):
Expand Down
Loading

Back | FazBrowse Home | New Git URL