Skip to content

Commit 7f7d432

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 7f7d432

File tree

4 files changed

+172
-12
lines changed

4 files changed

+172
-12
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

tests/integrations/otlp/test_otlp.py

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88
ProxyTracerProvider,
99
format_span_id,
1010
format_trace_id,
11+
get_current_span,
1112
)
13+
from opentelemetry.context import attach, detach
1214
from opentelemetry.propagate import get_global_textmap, set_global_textmap
1315
from opentelemetry.util._once import Once
1416
from opentelemetry.sdk.trace import TracerProvider
1517
from opentelemetry.sdk.trace.export import BatchSpanProcessor
1618
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
1719

18-
from sentry_sdk.integrations.otlp import OTLPIntegration
19-
from sentry_sdk.integrations.opentelemetry import SentryPropagator
20+
from sentry_sdk.integrations.otlp import OTLPIntegration, SentryOTLPPropagator
2021
from sentry_sdk.scope import get_external_propagation_context
2122

2223

@@ -111,7 +112,7 @@ def test_sets_propagator(sentry_init):
111112
)
112113

113114
propagator = get_global_textmap()
114-
assert isinstance(get_global_textmap(), SentryPropagator)
115+
assert isinstance(get_global_textmap(), SentryOTLPPropagator)
115116
assert propagator is not original_propagator
116117

117118

@@ -122,7 +123,7 @@ def test_does_not_set_propagator_if_disabled(sentry_init):
122123
)
123124

124125
propagator = get_global_textmap()
125-
assert not isinstance(propagator, SentryPropagator)
126+
assert not isinstance(propagator, SentryOTLPPropagator)
126127
assert propagator is original_propagator
127128

128129

@@ -152,3 +153,72 @@ def test_otel_propagation_context(sentry_init):
152153
assert trace_id == format_trace_id(root_span.get_span_context().trace_id)
153154
assert trace_id == format_trace_id(span.get_span_context().trace_id)
154155
assert span_id == format_span_id(span.get_span_context().span_id)
156+
157+
158+
def test_propagator_inject_head_of_trace(sentry_init):
159+
sentry_init(
160+
dsn="https://mysecret@bla.ingest.sentry.io/12312012",
161+
integrations=[OTLPIntegration()],
162+
)
163+
164+
tracer = trace.get_tracer(__name__)
165+
propagator = get_global_textmap()
166+
carrier = {}
167+
168+
with tracer.start_as_current_span("foo") as span:
169+
propagator.inject(carrier)
170+
171+
span_context = span.get_span_context()
172+
trace_id = format_trace_id(span_context.trace_id)
173+
span_id = format_span_id(span_context.span_id)
174+
175+
assert "sentry-trace" in carrier
176+
assert carrier["sentry-trace"] == f"{trace_id}-{span_id}-1"
177+
178+
#! we cannot populate baggage in otlp as head SDK yet
179+
assert "baggage" not in carrier
180+
181+
182+
def test_propagator_inject_continue_trace(sentry_init):
183+
sentry_init(
184+
dsn="https://mysecret@bla.ingest.sentry.io/12312012",
185+
integrations=[OTLPIntegration()],
186+
)
187+
188+
tracer = trace.get_tracer(__name__)
189+
propagator = get_global_textmap()
190+
carrier = {}
191+
192+
incoming_headers = {
193+
"sentry-trace": "771a43a4192642f0b136d5159a501700-1234567890abcdef-1",
194+
"baggage": (
195+
"sentry-trace_id=771a43a4192642f0b136d5159a501700,sentry-sampled=true"
196+
),
197+
}
198+
199+
ctx = propagator.extract(incoming_headers)
200+
token = attach(ctx)
201+
202+
parent_span_context = get_current_span().get_span_context()
203+
assert (
204+
format_trace_id(parent_span_context.trace_id)
205+
== "771a43a4192642f0b136d5159a501700"
206+
)
207+
assert format_span_id(parent_span_context.span_id) == "1234567890abcdef"
208+
209+
with tracer.start_as_current_span("foo") as span:
210+
propagator.inject(carrier)
211+
212+
span_context = span.get_span_context()
213+
trace_id = format_trace_id(span_context.trace_id)
214+
span_id = format_span_id(span_context.span_id)
215+
216+
assert trace_id == "771a43a4192642f0b136d5159a501700"
217+
218+
assert "sentry-trace" in carrier
219+
assert carrier["sentry-trace"] == f"{trace_id}-{span_id}-1"
220+
221+
assert "baggage" in carrier
222+
assert carrier["baggage"] == incoming_headers["baggage"]
223+
224+
detach(token)

tests/test_scope.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -997,11 +997,15 @@ def external_propagation_context():
997997

998998
register_external_propagation_context(external_propagation_context)
999999

1000-
trace_context = sentry_sdk.get_current_scope().get_trace_context()
1000+
scope = sentry_sdk.get_current_scope()
10011001

1002+
trace_context = scope.get_trace_context()
10021003
assert trace_context["trace_id"] == "trace_id_foo"
10031004
assert trace_context["span_id"] == "span_id_bar"
10041005

1006+
headers = list(scope.iter_trace_propagation_headers())
1007+
assert not headers
1008+
10051009
remove_external_propagation_context()
10061010

10071011

0 commit comments

Comments
 (0)