Skip to content

Commit 6ad4031

Browse files
authored
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 6ad4031

File tree

3 files changed

+42
-4
lines changed

3 files changed

+42
-4
lines changed

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 = []

0 commit comments

Comments
 (0)