Skip to content

Commit bddd120

Browse files
sentrivanasl0thentr0py
authored andcommitted
Patch TracerProvider if it already exists (#4455)
If a `TracerProvider` already exists, patch it. `TracerProvider` is a singleton, so if we aren't the first ones setting it up, we need to use the existing one. In tests, reset `TracerProvider` after each test so that we start with a clean slate and it gets set up anew on init.
1 parent 7467b19 commit bddd120

File tree

5 files changed

+132
-16
lines changed

5 files changed

+132
-16
lines changed

sentry_sdk/opentelemetry/propagator.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
SpanContext,
2121
TraceFlags,
2222
)
23+
from opentelemetry.semconv.trace import SpanAttributes
2324

25+
import sentry_sdk
2426
from sentry_sdk.consts import (
2527
BAGGAGE_HEADER_NAME,
2628
SENTRY_TRACE_HEADER_NAME,
@@ -30,7 +32,11 @@
3032
SENTRY_TRACE_KEY,
3133
SENTRY_SCOPES_KEY,
3234
)
33-
from sentry_sdk.tracing_utils import Baggage, extract_sentrytrace_data
35+
from sentry_sdk.tracing_utils import (
36+
Baggage,
37+
extract_sentrytrace_data,
38+
should_propagate_trace,
39+
)
3440

3541
from typing import TYPE_CHECKING
3642

@@ -89,18 +95,21 @@ def extract(self, carrier, context=None, getter=default_getter):
8995

9096
def inject(self, carrier, context=None, setter=default_setter):
9197
# type: (CarrierT, Optional[Context], Setter[CarrierT]) -> None
92-
if context is None:
93-
context = get_current()
94-
9598
scopes = get_value(SENTRY_SCOPES_KEY, context)
96-
if scopes:
97-
scopes = cast("tuple[scope.PotelScope, scope.PotelScope]", scopes)
98-
(current_scope, _) = scopes
99-
100-
# TODO-neel-potel check trace_propagation_targets
101-
# TODO-neel-potel test propagator works with twp
102-
for key, value in current_scope.iter_trace_propagation_headers():
103-
setter.set(carrier, key, value)
99+
if not scopes:
100+
return
101+
102+
scopes = cast("tuple[scope.PotelScope, scope.PotelScope]", scopes)
103+
(current_scope, _) = scopes
104+
105+
span = current_scope.span
106+
if span:
107+
span_url = span.get_attribute(SpanAttributes.HTTP_URL)
108+
if span_url and not should_propagate_trace(sentry_sdk.get_client(), span_url):
109+
return
110+
111+
for key, value in current_scope.iter_trace_propagation_headers():
112+
setter.set(carrier, key, value)
104113

105114
@property
106115
def fields(self):

sentry_sdk/opentelemetry/tracing.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
SentrySampler,
88
SentrySpanProcessor,
99
)
10+
from sentry_sdk.utils import logger
1011

1112

1213
def patch_readable_span():
@@ -28,8 +29,31 @@ def sentry_patched_readable_span(self):
2829

2930
def setup_sentry_tracing():
3031
# type: () -> None
31-
provider = TracerProvider(sampler=SentrySampler())
32-
provider.add_span_processor(SentrySpanProcessor())
33-
trace.set_tracer_provider(provider)
32+
# TracerProvider can only be set once. If we're the first ones setting it,
33+
# there's no issue. If it already exists, we need to patch it.
34+
from opentelemetry.trace import _TRACER_PROVIDER
35+
36+
if _TRACER_PROVIDER is not None:
37+
logger.debug("[Tracing] Detected an existing TracerProvider, patching")
38+
tracer_provider = _TRACER_PROVIDER
39+
tracer_provider.sampler = SentrySampler() # type: ignore[attr-defined]
40+
41+
else:
42+
logger.debug("[Tracing] No TracerProvider set, creating a new one")
43+
tracer_provider = TracerProvider(sampler=SentrySampler())
44+
trace.set_tracer_provider(tracer_provider)
45+
46+
try:
47+
existing_span_processors = (
48+
tracer_provider._active_span_processor._span_processors # type: ignore[attr-defined]
49+
)
50+
except Exception:
51+
existing_span_processors = []
52+
53+
for span_processor in existing_span_processors:
54+
if isinstance(span_processor, SentrySpanProcessor):
55+
break
56+
else:
57+
tracer_provider.add_span_processor(SentrySpanProcessor()) # type: ignore[attr-defined]
3458

3559
set_global_textmap(SentryPropagator())

sentry_sdk/tracing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def __init__(
188188
If otel_span is passed explicitly, just acts as a proxy.
189189
190190
If span is passed explicitly, use it. The only purpose of this param
191-
if backwards compatibility with start_transaction(transaction=...).
191+
is backwards compatibility with start_transaction(transaction=...).
192192
193193
If only_if_parent is True, just return an INVALID_SPAN
194194
and avoid instrumentation if there's no active parent span.

tests/conftest.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
import os
33
import socket
44
import warnings
5+
from opentelemetry import trace as otel_trace
6+
7+
try:
8+
from opentelemetry.util._once import Once
9+
except ImportError:
10+
Once = None
511
from threading import Thread
612
from http.server import BaseHTTPRequestHandler, HTTPServer
713

@@ -73,6 +79,14 @@ def clean_scopes():
7379
setup_initial_scopes()
7480

7581

82+
@pytest.fixture(autouse=True)
83+
def clear_tracer_provider():
84+
"""Reset TracerProvider so that we can set it up from scratch."""
85+
if Once is not None:
86+
otel_trace._TRACER_PROVIDER_SET_ONCE = Once()
87+
otel_trace._TRACER_PROVIDER = None
88+
89+
7690
@pytest.fixture(autouse=True)
7791
def internal_exceptions(request):
7892
errors = []

tests/opentelemetry/test_propagator.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@
44

55
from opentelemetry.trace.propagation import get_current_span
66
from opentelemetry.propagators.textmap import DefaultSetter
7+
from opentelemetry.semconv.trace import SpanAttributes
8+
from opentelemetry.sdk.trace import ReadableSpan
9+
from opentelemetry.trace import SpanKind
710

811
import sentry_sdk
12+
from sentry_sdk.consts import MATCH_ALL
913
from sentry_sdk.opentelemetry.consts import (
1014
SENTRY_BAGGAGE_KEY,
1115
SENTRY_TRACE_KEY,
16+
SENTRY_SCOPES_KEY,
1217
)
1318
from sentry_sdk.opentelemetry import SentryPropagator
1419
from tests.conftest import SortedBaggage
@@ -208,3 +213,67 @@ def test_inject_head_sdk(sentry_init):
208213
assert carrier["baggage"] == SortedBaggage(
209214
expected_baggage.format(trace_id=span.trace_id)
210215
)
216+
217+
218+
@pytest.mark.parametrize(
219+
"trace_propagation_targets,url,trace_propagated",
220+
[
221+
# No targets - should not propagate
222+
([], "https://example.com/api/users", False),
223+
(None, "https://example.com/api/users", False),
224+
# MATCH_ALL - should propagate
225+
([MATCH_ALL], "https://example.com/api/users", True),
226+
# Exact match - should propagate
227+
(["https://example.com"], "https://example.com/api/users", True),
228+
(["https://example.com/"], "https://example.com/api/users", True),
229+
# No match - should not propagate
230+
(["https://example.com"], "https://other-domain.com/api/users", False),
231+
(["https://example.com/"], "https://other-domain.com/api/users", False),
232+
# Regex patterns
233+
(
234+
["https://example.com", r"https?:\/\/[\w\-]+(\.[\w\-]+)+\.net"],
235+
"https://good.example.net/api",
236+
True,
237+
),
238+
(
239+
["https://example.com", r"https?:\/\/[\w\-]+(\.[\w\-]+)+\.net"],
240+
"https://example.net/api",
241+
False,
242+
),
243+
# HTTP vs HTTPS
244+
(["https://example.com"], "http://example.com/api/users", False),
245+
(["http://example.com"], "https://example.com/api/users", False),
246+
# Path matching
247+
(["https://example.com/api"], "https://example.com/api/users", True),
248+
(["https://example.com/api"], "https://example.com/other/path", False),
249+
],
250+
)
251+
def test_propagator_trace_propagation_targets(
252+
sentry_init,
253+
trace_propagation_targets,
254+
url,
255+
trace_propagated,
256+
):
257+
"""Test that the propagator respects trace_propagation_targets for HTTP spans."""
258+
sentry_init(
259+
trace_propagation_targets=trace_propagation_targets,
260+
traces_sample_rate=1.0,
261+
)
262+
263+
carrier = {}
264+
setter = DefaultSetter()
265+
266+
# Create a real HTTP span with the test URL
267+
with sentry_sdk.start_span(name="http.client") as span:
268+
span.set_attribute(SpanAttributes.HTTP_METHOD, "GET")
269+
span.set_attribute(SpanAttributes.HTTP_URL, url)
270+
271+
# Test the propagator
272+
SentryPropagator().inject(carrier, setter=setter)
273+
274+
if trace_propagated:
275+
assert "sentry-trace" in carrier
276+
assert "baggage" in carrier
277+
else:
278+
assert "sentry-trace" not in carrier
279+
assert "baggage" not in carrier

0 commit comments

Comments
 (0)