diff --git a/sentry_sdk/opentelemetry/tracing.py b/sentry_sdk/opentelemetry/tracing.py index 8392c1515a..5002f71c50 100644 --- a/sentry_sdk/opentelemetry/tracing.py +++ b/sentry_sdk/opentelemetry/tracing.py @@ -7,6 +7,7 @@ SentrySampler, SentrySpanProcessor, ) +from sentry_sdk.utils import logger def patch_readable_span(): @@ -28,8 +29,31 @@ def sentry_patched_readable_span(self): def setup_sentry_tracing(): # type: () -> None - provider = TracerProvider(sampler=SentrySampler()) - provider.add_span_processor(SentrySpanProcessor()) - trace.set_tracer_provider(provider) + # TracerProvider can only be set once. If we're the first ones setting it, + # there's no issue. If it already exists, we need to patch it. + from opentelemetry.trace import _TRACER_PROVIDER + + if _TRACER_PROVIDER is not None: + logger.debug("[Tracing] Detected an existing TracerProvider, patching") + tracer_provider = _TRACER_PROVIDER + tracer_provider.sampler = SentrySampler() # type: ignore[attr-defined] + + else: + logger.debug("[Tracing] No TracerProvider set, creating a new one") + tracer_provider = TracerProvider(sampler=SentrySampler()) + trace.set_tracer_provider(tracer_provider) + + try: + existing_span_processors = ( + tracer_provider._active_span_processor._span_processors # type: ignore[attr-defined] + ) + except Exception: + existing_span_processors = [] + + for span_processor in existing_span_processors: + if isinstance(span_processor, SentrySpanProcessor): + break + else: + tracer_provider.add_span_processor(SentrySpanProcessor()) # type: ignore[attr-defined] set_global_textmap(SentryPropagator()) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 7a5f12fe91..f15f07065a 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -188,7 +188,7 @@ def __init__( If otel_span is passed explicitly, just acts as a proxy. If span is passed explicitly, use it. The only purpose of this param - if backwards compatibility with start_transaction(transaction=...). + is backwards compatibility with start_transaction(transaction=...). If only_if_parent is True, just return an INVALID_SPAN and avoid instrumentation if there's no active parent span. diff --git a/tests/conftest.py b/tests/conftest.py index d64652c4e9..3daf915b15 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,12 @@ import os import socket import warnings +from opentelemetry import trace as otel_trace + +try: + from opentelemetry.util._once import Once +except ImportError: + Once = None from threading import Thread from http.server import BaseHTTPRequestHandler, HTTPServer @@ -73,6 +79,14 @@ def clean_scopes(): setup_initial_scopes() +@pytest.fixture(autouse=True) +def clear_tracer_provider(): + """Reset TracerProvider so that we can set it up from scratch.""" + if Once is not None: + otel_trace._TRACER_PROVIDER_SET_ONCE = Once() + otel_trace._TRACER_PROVIDER = None + + @pytest.fixture(autouse=True) def internal_exceptions(request): errors = []