Skip to content

Commit

Permalink
Cleanup scopes types and no-op old scope tracing methods (#3985)
Browse files Browse the repository at this point in the history
closes #3929 and #3969
  • Loading branch information
sl0thentr0py authored Jan 22, 2025
1 parent 37d8599 commit 9df9cbf
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 142 deletions.
5 changes: 3 additions & 2 deletions sentry_sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from sentry_sdk.crons import monitor

# TODO-neel-potel make 2 scope strategies/impls and switch
from sentry_sdk.scope import Scope as BaseScope
from sentry_sdk.integrations.opentelemetry.scope import (
PotelScope as Scope,
new_scope,
Expand Down Expand Up @@ -123,7 +124,7 @@ def is_initialized():

@scopemethod
def get_global_scope():
# type: () -> Scope
# type: () -> BaseScope
return Scope.get_global_scope()


Expand Down Expand Up @@ -239,7 +240,7 @@ def flush(


def start_span(**kwargs):
# type: (type.Any) -> Span
# type: (Any) -> Span
"""
Start and return a span.
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ def _prepare_event(

for key in "release", "environment", "server_name", "dist":
if event.get(key) is None and self.options[key] is not None:
event[key] = str(self.options[key]).strip() # type: ignore[literal-required]
event[key] = str(self.options[key]).strip()
if event.get("sdk") is None:
sdk_info = dict(SDK_INFO)
sdk_info["integrations"] = sorted(self.integrations.keys())
Expand Down
34 changes: 23 additions & 11 deletions sentry_sdk/integrations/opentelemetry/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from sentry_sdk.integrations.opentelemetry.utils import trace_state_from_baggage
from sentry_sdk.scope import Scope, ScopeType
from sentry_sdk.tracing import Span
from sentry_sdk.utils import logger
from sentry_sdk._types import TYPE_CHECKING

if TYPE_CHECKING:
Expand All @@ -41,23 +42,25 @@
class PotelScope(Scope):
@classmethod
def _get_scopes(cls):
# type: () -> Optional[Tuple[Scope, Scope]]
# type: () -> Optional[Tuple[PotelScope, PotelScope]]
"""
Returns the current scopes tuple on the otel context. Internal use only.
"""
return cast("Optional[Tuple[Scope, Scope]]", get_value(SENTRY_SCOPES_KEY))
return cast(
"Optional[Tuple[PotelScope, PotelScope]]", get_value(SENTRY_SCOPES_KEY)
)

@classmethod
def get_current_scope(cls):
# type: () -> Scope
# type: () -> PotelScope
"""
Returns the current scope.
"""
return cls._get_current_scope() or _INITIAL_CURRENT_SCOPE

@classmethod
def _get_current_scope(cls):
# type: () -> Optional[Scope]
# type: () -> Optional[PotelScope]
"""
Returns the current scope without creating a new one. Internal use only.
"""
Expand All @@ -66,15 +69,15 @@ def _get_current_scope(cls):

@classmethod
def get_isolation_scope(cls):
# type: () -> Scope
# type: () -> PotelScope
"""
Returns the isolation scope.
"""
return cls._get_isolation_scope() or _INITIAL_ISOLATION_SCOPE

@classmethod
def _get_isolation_scope(cls):
# type: () -> Optional[Scope]
# type: () -> Optional[PotelScope]
"""
Returns the isolation scope without creating a new one. Internal use only.
"""
Expand All @@ -84,6 +87,11 @@ def _get_isolation_scope(cls):
@contextmanager
def continue_trace(self, environ_or_headers):
# type: (Dict[str, Any]) -> Generator[None, None, None]
"""
Sets the propagation context from environment or headers to continue an incoming trace.
Any span started within this context manager will use the same trace_id, parent_span_id
and inherit the sampling decision from the incoming trace.
"""
self.generate_propagation_context(environ_or_headers)

span_context = self._incoming_otel_span_context()
Expand Down Expand Up @@ -118,8 +126,8 @@ def _incoming_otel_span_context(self):
trace_state = trace_state.add(TRACESTATE_SAMPLED_KEY, "deferred")

span_context = SpanContext(
trace_id=int(self._propagation_context.trace_id, 16), # type: ignore
span_id=int(self._propagation_context.parent_span_id, 16), # type: ignore
trace_id=int(self._propagation_context.trace_id, 16),
span_id=int(self._propagation_context.parent_span_id, 16),
is_remote=True,
trace_flags=trace_flags,
trace_state=trace_state,
Expand All @@ -134,18 +142,22 @@ def start_transaction(self, **kwargs):
This function is deprecated and will be removed in a future release.
Use :py:meth:`sentry_sdk.start_span` instead.
"""
logger.warning(
"The `start_transaction` method is deprecated, please use `sentry_sdk.start_span instead.`"
)
return self.start_span(**kwargs)

def start_span(self, **kwargs):
# type: (Any) -> Span
return Span(**kwargs, scope=self)
return Span(**kwargs)


_INITIAL_CURRENT_SCOPE = None
_INITIAL_ISOLATION_SCOPE = None
_INITIAL_CURRENT_SCOPE = PotelScope(ty=ScopeType.CURRENT)
_INITIAL_ISOLATION_SCOPE = PotelScope(ty=ScopeType.ISOLATION)


def setup_initial_scopes():
# type: () -> None
global _INITIAL_CURRENT_SCOPE, _INITIAL_ISOLATION_SCOPE
_INITIAL_CURRENT_SCOPE = PotelScope(ty=ScopeType.CURRENT)
_INITIAL_ISOLATION_SCOPE = PotelScope(ty=ScopeType.ISOLATION)
Expand Down
35 changes: 20 additions & 15 deletions sentry_sdk/integrations/opentelemetry/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def convert_from_otel_timestamp(time):


def convert_to_otel_timestamp(time):
# type: (Union[datetime.datetime, float]) -> int
# type: (Union[datetime, float]) -> int
"""Convert a datetime to an OTel timestamp (with nanosecond precision)."""
if isinstance(time, datetime):
return int(time.timestamp() * 1e9)
Expand All @@ -121,9 +121,12 @@ def extract_span_data(span):
if span.attributes is None:
return (op, description, status, http_status, origin)

op = span.attributes.get(SentrySpanAttribute.OP) or op
description = span.attributes.get(SentrySpanAttribute.DESCRIPTION) or description
origin = span.attributes.get(SentrySpanAttribute.ORIGIN)
attribute_op = cast("Optional[str]", span.attributes.get(SentrySpanAttribute.OP))
op = attribute_op or op
description = cast(
"str", span.attributes.get(SentrySpanAttribute.DESCRIPTION) or description
)
origin = cast("Optional[str]", span.attributes.get(SentrySpanAttribute.ORIGIN))

http_method = span.attributes.get(SpanAttributes.HTTP_METHOD)
http_method = cast("Optional[str]", http_method)
Expand All @@ -137,7 +140,7 @@ def extract_span_data(span):
rpc_service = span.attributes.get(SpanAttributes.RPC_SERVICE)
if rpc_service:
return (
span.attributes.get(SentrySpanAttribute.OP) or "rpc",
attribute_op or "rpc",
description,
status,
http_status,
Expand All @@ -147,7 +150,7 @@ def extract_span_data(span):
messaging_system = span.attributes.get(SpanAttributes.MESSAGING_SYSTEM)
if messaging_system:
return (
span.attributes.get(SentrySpanAttribute.OP) or "message",
attribute_op or "message",
description,
status,
http_status,
Expand All @@ -165,7 +168,7 @@ def span_data_for_http_method(span):
# type: (ReadableSpan) -> OtelExtractedSpanData
span_attributes = span.attributes or {}

op = span_attributes.get(SentrySpanAttribute.OP)
op = cast("Optional[str]", span_attributes.get(SentrySpanAttribute.OP))
if op is None:
op = "http"

Expand All @@ -183,6 +186,7 @@ def span_data_for_http_method(span):
description = span_attributes.get(
SentrySpanAttribute.DESCRIPTION
) or span_attributes.get(SentrySpanAttribute.NAME)
description = cast("Optional[str]", description)
if description is None:
description = f"{http_method}"

Expand All @@ -205,7 +209,7 @@ def span_data_for_http_method(span):

status, http_status = extract_span_status(span)

origin = span_attributes.get(SentrySpanAttribute.ORIGIN)
origin = cast("Optional[str]", span_attributes.get(SentrySpanAttribute.ORIGIN))

return (op, description, status, http_status, origin)

Expand All @@ -214,13 +218,13 @@ def span_data_for_db_query(span):
# type: (ReadableSpan) -> OtelExtractedSpanData
span_attributes = span.attributes or {}

op = span_attributes.get(SentrySpanAttribute.OP, OP.DB)
op = cast("str", span_attributes.get(SentrySpanAttribute.OP, OP.DB))

statement = span_attributes.get(SpanAttributes.DB_STATEMENT, None)
statement = cast("Optional[str]", statement)

description = statement or span.name
origin = span_attributes.get(SentrySpanAttribute.ORIGIN)
origin = cast("Optional[str]", span_attributes.get(SentrySpanAttribute.ORIGIN))

return (op, description, None, None, origin)

Expand Down Expand Up @@ -293,19 +297,20 @@ def extract_span_attributes(span, namespace):
"""
Extract Sentry-specific span attributes and make them look the way Sentry expects.
"""
extracted_attrs = {}
extracted_attrs = {} # type: dict[str, Any]

for attr, value in (span.attributes or {}).items():
if attr.startswith(namespace):
key = attr[len(namespace) + 1 :]

if namespace == SentrySpanAttribute.MEASUREMENT:
value = {
value = cast("tuple[str, str]", value)
extracted_attrs[key] = {
"value": float(value[0]),
"unit": value[1],
}

extracted_attrs[key] = value
else:
extracted_attrs[key] = value

return extracted_attrs

Expand Down Expand Up @@ -457,7 +462,7 @@ 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
span._sentry_meta = sentry_meta # type: ignore[union-attr]


def get_profile_context(span):
Expand Down
Loading

0 comments on commit 9df9cbf

Please sign in to comment.