Skip to content

Commit ddec242

Browse files
committed
Implement new Propagator.inject for OTLPIntegration
* bail out of `iter_trace_propagation_headers` if we have an `external_propagation_context` * handle outgoing headers as best as possible based on just the available `SpanContext` from otel * NOTE for baggage: * is correctly passed through in the incoming case * but for the head SDK, we cannot populate it in a persistent way across the span tree (no transaction concept) * so dynamic sampling with OTLP is out of scope right now
1 parent 8417165 commit ddec242

File tree

2 files changed

+93
-7
lines changed

2 files changed

+93
-7
lines changed

sentry_sdk/integrations/otlp.py

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,45 @@
1+
from sentry_sdk import get_client
12
from sentry_sdk.integrations import Integration, DidNotEnable
23
from sentry_sdk.scope import register_external_propagation_context
34
from sentry_sdk.utils import logger, Dsn
45
from sentry_sdk.consts import VERSION, EndpointType
6+
from sentry_sdk.tracing_utils import Baggage
7+
from sentry_sdk.tracing import (
8+
BAGGAGE_HEADER_NAME,
9+
SENTRY_TRACE_HEADER_NAME,
10+
)
511

612
try:
7-
from opentelemetry import trace
813
from opentelemetry.propagate import set_global_textmap
914
from opentelemetry.sdk.trace import TracerProvider
1015
from opentelemetry.sdk.trace.export import BatchSpanProcessor
1116
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
1217

18+
from opentelemetry.trace import (
19+
get_current_span,
20+
get_tracer_provider,
21+
set_tracer_provider,
22+
format_trace_id,
23+
format_span_id,
24+
SpanContext,
25+
INVALID_SPAN_ID,
26+
INVALID_TRACE_ID,
27+
)
28+
29+
from opentelemetry.context import (
30+
Context,
31+
get_current,
32+
get_value,
33+
)
34+
35+
from opentelemetry.propagators.textmap import (
36+
CarrierT,
37+
Setter,
38+
default_setter,
39+
)
40+
1341
from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
42+
from sentry_sdk.integrations.opentelemetry.consts import SENTRY_BAGGAGE_KEY
1443
except ImportError:
1544
raise DidNotEnable("opentelemetry-distro[otlp] is not installed")
1645

@@ -25,22 +54,22 @@ def otel_propagation_context():
2554
"""
2655
Get the (trace_id, span_id) from opentelemetry if exists.
2756
"""
28-
ctx = trace.get_current_span().get_span_context()
57+
ctx = get_current_span().get_span_context()
2958

30-
if ctx.trace_id == trace.INVALID_TRACE_ID or ctx.span_id == trace.INVALID_SPAN_ID:
59+
if ctx.trace_id == INVALID_TRACE_ID or ctx.span_id == INVALID_SPAN_ID:
3160
return None
3261

33-
return (trace.format_trace_id(ctx.trace_id), trace.format_span_id(ctx.span_id))
62+
return (format_trace_id(ctx.trace_id), format_span_id(ctx.span_id))
3463

3564

3665
def setup_otlp_traces_exporter(dsn=None):
3766
# type: (Optional[str]) -> None
38-
tracer_provider = trace.get_tracer_provider()
67+
tracer_provider = get_tracer_provider()
3968

4069
if not isinstance(tracer_provider, TracerProvider):
4170
logger.debug("[OTLP] No TracerProvider configured by user, creating a new one")
4271
tracer_provider = TracerProvider()
43-
trace.set_tracer_provider(tracer_provider)
72+
set_tracer_provider(tracer_provider)
4473

4574
endpoint = None
4675
headers = None
@@ -55,6 +84,54 @@ def setup_otlp_traces_exporter(dsn=None):
5584
tracer_provider.add_span_processor(span_processor)
5685

5786

87+
class SentryOTLPPropagator(SentryPropagator):
88+
"""
89+
We need to override the inject of the older propagator since that
90+
is SpanProcessor based.
91+
92+
!!! Note regarding baggage:
93+
We cannot meaningfully populate a new baggage as a head SDK
94+
when we are using OTLP since we don't have any sort of transaction semantic to
95+
track state across a group of spans.
96+
97+
For incoming baggage, we just pass it on as is so that case is correctly handled.
98+
"""
99+
100+
def inject(self, carrier, context=None, setter=default_setter):
101+
# type: (CarrierT, Optional[Context], Setter[CarrierT]) -> None
102+
otlp_integration = get_client().get_integration(OTLPIntegration)
103+
if otlp_integration is None:
104+
return
105+
106+
if context is None:
107+
context = get_current()
108+
109+
current_span = get_current_span(context)
110+
current_span_context = current_span.get_span_context()
111+
112+
if not current_span_context.is_valid:
113+
return
114+
115+
sentry_trace = _to_traceparent(current_span_context)
116+
setter.set(carrier, SENTRY_TRACE_HEADER_NAME, sentry_trace)
117+
118+
baggage = get_value(SENTRY_BAGGAGE_KEY, context)
119+
if baggage is not None and isinstance(baggage, Baggage):
120+
setter.set(carrier, BAGGAGE_HEADER_NAME, baggage.serialize())
121+
122+
123+
def _to_traceparent(span_context):
124+
# type: (SpanContext) -> str
125+
"""
126+
Helper method to generate the sentry-trace header.
127+
"""
128+
span_id = format_span_id(span_context.span_id)
129+
trace_id = format_trace_id(span_context.trace_id)
130+
sampled = span_context.trace_flags.sampled
131+
132+
return f"{trace_id}-{span_id}-{'1' if sampled else '0'}"
133+
134+
58135
class OTLPIntegration(Integration):
59136
identifier = "otlp"
60137

@@ -79,4 +156,4 @@ def setup_once_with_options(self, options=None):
79156
if self.setup_propagator:
80157
logger.debug("[OTLP] Setting up propagator for distributed tracing")
81158
# TODO-neel better propagator support, chain with existing ones if possible instead of replacing
82-
set_global_textmap(SentryPropagator())
159+
set_global_textmap(SentryOTLPPropagator())

sentry_sdk/scope.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ def get_external_propagation_context():
165165
)
166166

167167

168+
def has_external_propagation_context():
169+
# type: () -> bool
170+
return _external_propagation_context_fn is not None
171+
172+
168173
def _attr_setter(fn):
169174
# type: (Any) -> Any
170175
return property(fset=fn, doc=fn.__doc__)
@@ -637,6 +642,10 @@ def iter_trace_propagation_headers(self, *args, **kwargs):
637642
if has_tracing_enabled(client.options) and span is not None:
638643
for header in span.iter_headers():
639644
yield header
645+
elif has_external_propagation_context():
646+
# when we have an external_propagation_context (otlp)
647+
# we leave outgoing propagation to the propagator
648+
return
640649
else:
641650
for header in self.get_active_propagation_context().iter_headers():
642651
yield header

0 commit comments

Comments
 (0)