mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-20 00:11:50 +00:00
fix(otel): keep caller tracestate on the legacy request span
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
cd594f104a
commit
df9a87f44a
2 changed files with 61 additions and 9 deletions
|
|
@ -2932,16 +2932,13 @@ class OpenTelemetry(OTELGenAISemconvMixin, CustomLogger):
|
|||
)
|
||||
|
||||
propagator: Final = TraceContextTextMapPropagator()
|
||||
carrier: Final = {"traceparent": _traceparent}
|
||||
carrier: Final = {key: headers[key] for key in ("traceparent", "tracestate") if headers.get(key) is not None}
|
||||
_parent_context: Final = propagator.extract(carrier=carrier)
|
||||
|
||||
return _parent_context
|
||||
|
||||
def _get_span_context(self, kwargs, default_span: Span | None = None):
|
||||
from opentelemetry import context, trace
|
||||
from opentelemetry.trace.propagation.tracecontext import (
|
||||
TraceContextTextMapPropagator,
|
||||
)
|
||||
|
||||
litellm_params: Final = kwargs.get("litellm_params", {}) or {}
|
||||
proxy_server_request: Final = litellm_params.get("proxy_server_request", {}) or {}
|
||||
|
|
@ -2965,11 +2962,7 @@ class OpenTelemetry(OTELGenAISemconvMixin, CustomLogger):
|
|||
# Priority 2: HTTP traceparent header
|
||||
if traceparent is not None:
|
||||
verbose_logger.debug("OpenTelemetry: Using traceparent header for context propagation")
|
||||
carrier: Final = {"traceparent": traceparent}
|
||||
return (
|
||||
TraceContextTextMapPropagator().extract(carrier=carrier),
|
||||
None,
|
||||
)
|
||||
return self.get_traceparent_from_header(headers=headers), None
|
||||
|
||||
# Priority 3: Active span from global context (auto-detection)
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -5423,6 +5423,65 @@ class TestGetSpanContextLitellmMetadataFallback(unittest.TestCase):
|
|||
self.assertIsNone(detected_span)
|
||||
|
||||
|
||||
class TestInboundTraceContextKeepsCallerTracestate(unittest.TestCase):
|
||||
"""The request span built from inbound W3C headers must carry the caller's
|
||||
tracestate so outbound propagation (passthrough) re-emits it instead of
|
||||
dropping it alongside the stripped stale header."""
|
||||
|
||||
CALLER_TRACEPARENT = "00-" + "a" * 32 + "-" + "b" * 16 + "-01"
|
||||
CALLER_TRACESTATE = "vendor=abc,other=xyz"
|
||||
|
||||
def _otel(self):
|
||||
provider = TracerProvider()
|
||||
provider.add_span_processor(SimpleSpanProcessor(InMemorySpanExporter()))
|
||||
otel = OpenTelemetry()
|
||||
otel.tracer = provider.get_tracer(__name__)
|
||||
return otel
|
||||
|
||||
def test_request_span_propagates_caller_tracestate_downstream(self):
|
||||
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
||||
|
||||
from litellm.integrations.otel.plumbing.context import inject_trace_context
|
||||
|
||||
inbound = {"traceparent": self.CALLER_TRACEPARENT, "tracestate": self.CALLER_TRACESTATE}
|
||||
span = self._otel().create_litellm_proxy_request_started_span(
|
||||
start_time=datetime.now(timezone.utc), headers=inbound
|
||||
)
|
||||
outbound = inject_trace_context(inbound, parent_span=span)
|
||||
span.end()
|
||||
|
||||
propagated = trace.get_current_span(TraceContextTextMapPropagator().extract(outbound)).get_span_context()
|
||||
self.assertEqual(outbound["tracestate"], self.CALLER_TRACESTATE)
|
||||
self.assertEqual(propagated.trace_id, span.get_span_context().trace_id)
|
||||
self.assertEqual(propagated.span_id, span.get_span_context().span_id)
|
||||
self.assertNotEqual(outbound["traceparent"], self.CALLER_TRACEPARENT)
|
||||
|
||||
def test_request_span_without_caller_tracestate_emits_none(self):
|
||||
from litellm.integrations.otel.plumbing.context import inject_trace_context
|
||||
|
||||
inbound = {"traceparent": self.CALLER_TRACEPARENT}
|
||||
span = self._otel().create_litellm_proxy_request_started_span(
|
||||
start_time=datetime.now(timezone.utc), headers=inbound
|
||||
)
|
||||
outbound = inject_trace_context(inbound, parent_span=span)
|
||||
span.end()
|
||||
|
||||
self.assertNotIn("tracestate", outbound)
|
||||
self.assertNotEqual(outbound["traceparent"], self.CALLER_TRACEPARENT)
|
||||
|
||||
def test_span_context_from_header_keeps_caller_tracestate(self):
|
||||
kwargs = {
|
||||
"litellm_params": {
|
||||
"proxy_server_request": {
|
||||
"headers": {"traceparent": self.CALLER_TRACEPARENT, "tracestate": self.CALLER_TRACESTATE}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx, detected_span = self._otel()._get_span_context(kwargs)
|
||||
self.assertIsNone(detected_span)
|
||||
self.assertEqual(trace.get_current_span(ctx).get_span_context().trace_state.to_header(), self.CALLER_TRACESTATE)
|
||||
|
||||
|
||||
class TestEndProxySpanLitellmMetadataFallback(unittest.TestCase):
|
||||
"""
|
||||
Tests for _end_proxy_span_from_kwargs() falling back to litellm_metadata.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue