fix(otel): drop stale trace headers before injecting passthrough trace context

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
shivam 2026-09-16 08:34:53 +00:00
parent b82de95625
commit cd594f104a
2 changed files with 24 additions and 1 deletions

View file

@ -26,6 +26,7 @@ if TYPE_CHECKING:
from litellm.integrations.otel.model.destination import OtelDestination
_PROPAGATOR: Final = TraceContextTextMapPropagator()
_W3C_TRACE_HEADERS: Final = frozenset(("traceparent", "tracestate"))
# The request's root span — the FastAPI-owned SERVER span — captured ONCE when the
# proxy first resolves it, so request-level spans (the LLM call, guardrails) can
@ -334,7 +335,9 @@ def inject_trace_context(headers: Mapping[str, str], parent_span: object = None)
context: Final = _outgoing_trace_context(parent_span)
if context is None:
return dict(headers) # mutable-ok: OpenTelemetry propagator requires a mutable carrier
carrier: Final = dict(headers) # mutable-ok: OpenTelemetry propagator requires a mutable carrier
carrier: Final = { # mutable-ok: OpenTelemetry propagator requires a mutable carrier
key: value for key, value in headers.items() if key.lower() not in _W3C_TRACE_HEADERS
}
_PROPAGATOR.inject(carrier, context=context)
return carrier

View file

@ -507,6 +507,26 @@ def test_inject_trace_context_uses_ambient_span_without_request_root():
assert propagated.get_span_context().span_id == ambient.get_span_context().span_id
def test_inject_trace_context_replaces_stale_trace_headers():
def run():
tracer = _test_tracer()
with tracer.start_as_current_span("ambient") as ambient:
headers = {
"Traceparent": "00-" + "a" * 32 + "-" + "b" * 16 + "-01",
"Tracestate": "vendor=old",
"x-keep": "1",
}
result = ctx_mod.inject_trace_context(headers)
propagated = get_current_span(TraceContextTextMapPropagator().extract(result))
return result, ambient, propagated
result, ambient, propagated = ContextVarContext().run(run)
assert sum(key.lower() == "traceparent" for key in result) == 1
assert not any(key.lower() == "tracestate" for key in result)
assert result["x-keep"] == "1"
assert propagated.get_span_context().trace_id == ambient.get_span_context().trace_id
def test_inject_trace_context_prefers_explicit_parent_span_over_root_and_ambient():
def run():
tracer = _test_tracer()