diff --git a/sentry_sdk/integrations/opentelemetry/integration.py b/sentry_sdk/integrations/opentelemetry/integration.py index 595fdfcb6e..944326a124 100644 --- a/sentry_sdk/integrations/opentelemetry/integration.py +++ b/sentry_sdk/integrations/opentelemetry/integration.py @@ -18,7 +18,6 @@ try: from opentelemetry import trace from opentelemetry.propagate import set_global_textmap - from opentelemetry.trace import Span as AbstractSpan from opentelemetry.sdk.trace import TracerProvider, Span, ReadableSpan except ImportError: raise DidNotEnable("opentelemetry not installed") @@ -28,11 +27,6 @@ except ImportError: DjangoInstrumentor = None -from sentry_sdk._types import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Union, Any - CONFIGURABLE_INSTRUMENTATIONS = { DjangoInstrumentor: {"is_sql_commentor_enabled": True}, @@ -63,23 +57,12 @@ def _patch_readable_span(): We need to pass through sentry specific metadata/objects from Span to ReadableSpan to work with them consistently in the SpanProcessor. """ - - @property - def sentry_meta(self): - # type: (Union[AbstractSpan, Span, ReadableSpan]) -> dict[str, Any] - if not getattr(self, "_sentry_meta", None): - self._sentry_meta = {} - return self._sentry_meta - - AbstractSpan.sentry_meta = sentry_meta - ReadableSpan.sentry_meta = sentry_meta - old_readable_span = Span._readable_span def sentry_patched_readable_span(self): # type: (Span) -> ReadableSpan readable_span = old_readable_span(self) - readable_span._sentry_meta = self._sentry_meta + readable_span._sentry_meta = getattr(self, "_sentry_meta", {}) return readable_span Span._readable_span = sentry_patched_readable_span diff --git a/sentry_sdk/integrations/opentelemetry/potel_span_processor.py b/sentry_sdk/integrations/opentelemetry/potel_span_processor.py index a254aada05..fb20c7abfe 100644 --- a/sentry_sdk/integrations/opentelemetry/potel_span_processor.py +++ b/sentry_sdk/integrations/opentelemetry/potel_span_processor.py @@ -20,6 +20,8 @@ extract_span_data, extract_transaction_name_source, get_trace_context, + get_sentry_meta, + set_sentry_meta, ) from sentry_sdk.integrations.opentelemetry.consts import ( OTEL_SENTRY_CONTEXT, @@ -85,12 +87,11 @@ def _add_root_span(self, span, parent_span): """ if parent_span != INVALID_SPAN and not parent_span.get_span_context().is_remote: # child span points to parent's root or parent - span.sentry_meta["root_span"] = parent_span.sentry_meta.get( - "root_span", parent_span - ) + parent_root_span = get_sentry_meta(parent_span, "root_span") + set_sentry_meta(span, "root_span", parent_root_span or parent_span) else: # root span points to itself - span.sentry_meta["root_span"] = span + set_sentry_meta(span, "root_span", span) def _flush_root_span(self, span): # type: (ReadableSpan) -> None diff --git a/sentry_sdk/integrations/opentelemetry/utils.py b/sentry_sdk/integrations/opentelemetry/utils.py index dc0fff7f8c..d274f4e887 100644 --- a/sentry_sdk/integrations/opentelemetry/utils.py +++ b/sentry_sdk/integrations/opentelemetry/utils.py @@ -5,7 +5,7 @@ from urllib3.util import parse_url as urlparse from urllib.parse import quote from opentelemetry.trace import ( - Span, + Span as AbstractSpan, SpanKind, StatusCode, format_trace_id, @@ -365,7 +365,7 @@ def has_incoming_trace(trace_state): def get_trace_state(span): - # type: (Union[Span, ReadableSpan]) -> TraceState + # type: (Union[AbstractSpan, ReadableSpan]) -> TraceState """ Get the existing trace_state with sentry items or populate it if we are the head SDK. @@ -404,27 +404,31 @@ def get_trace_state(span): Baggage.SENTRY_PREFIX + "public_key", Dsn(options["dsn"]).public_key ) - # we cannot access the root span in most cases here, so we HAVE to rely on the - # scopes to carry the correct transaction name/source. - # IDEALLY we will always move to using the isolation scope here - # but our integrations do all kinds of stuff with both isolation and current - # so I am keeping both for now as a best attempt solution till we get to a better state. - isolation_scope = sentry_sdk.get_isolation_scope() - current_scope = sentry_sdk.get_current_scope() - if ( - current_scope.transaction_name - and current_scope.transaction_source not in LOW_QUALITY_TRANSACTION_SOURCES - ): - trace_state = trace_state.update( - Baggage.SENTRY_PREFIX + "transaction", current_scope.transaction_name - ) - elif ( - isolation_scope.transaction_name - and isolation_scope.transaction_source - not in LOW_QUALITY_TRANSACTION_SOURCES - ): - trace_state = trace_state.update( - Baggage.SENTRY_PREFIX + "transaction", isolation_scope.transaction_name + root_span = get_sentry_meta(span, "root_span") + if root_span and isinstance(root_span, ReadableSpan): + transaction_name, transaction_source = extract_transaction_name_source( + root_span ) + if ( + transaction_name + and transaction_source not in LOW_QUALITY_TRANSACTION_SOURCES + ): + trace_state = trace_state.update( + Baggage.SENTRY_PREFIX + "transaction", transaction_name + ) + return trace_state + + +def get_sentry_meta(span, key): + # type: (Union[AbstractSpan, ReadableSpan], str) -> Any + sentry_meta = getattr(span, "_sentry_meta", None) + return sentry_meta.get(key) if sentry_meta else None + + +def set_sentry_meta(span, key, value): + # type: (Union[AbstractSpan, ReadableSpan], str, Any) -> None + sentry_meta = getattr(span, "_sentry_meta", {}) + sentry_meta[key] = value + span._sentry_meta = sentry_meta diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index bdfa846257..513368c823 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1326,8 +1326,12 @@ def containing_transaction(self): @property def root_span(self): # type: () -> Optional[POTelSpan] + from sentry_sdk.integrations.opentelemetry.utils import ( + get_sentry_meta, + ) + root_otel_span = cast( - "Optional[OtelSpan]", self._otel_span.sentry_meta.get("root_span", None) + "Optional[OtelSpan]", get_sentry_meta(self._otel_span, "root_span") ) return POTelSpan(otel_span=root_otel_span) if root_otel_span else None