From 5d3911a56c1e1d02b7a12425de1bd12fb1e08236 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 12 Aug 2024 16:46:16 +0200 Subject: [PATCH 1/5] fill out more stubs --- sentry_sdk/tracing.py | 150 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 10 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 41c998cb99..0bcb98d198 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -36,6 +36,7 @@ R = TypeVar("R") import sentry_sdk.profiler + from sentry_sdk.scope import Scope from sentry_sdk._types import ( Event, MeasurementUnit, @@ -1262,6 +1263,9 @@ def __init__( active=True, # type: bool op=None, # type: Optional[str] description=None, # type: Optional[str] + status=None, # type: Optional[str] + scope=None, # type: Optional[Scope] + start_timestamp=None, # type: Optional[Union[datetime, float]] origin="manual", # type: str **_, # type: dict[str, object] ): @@ -1272,15 +1276,26 @@ def __init__( listed in the signature. These additional arguments are ignored. """ from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute + from sentry_sdk.integrations.opentelemetry.utils import ( + convert_to_otel_timestamp, + ) - self._otel_span = tracer.start_span(description or op or "") # XXX + if start_timestamp is not None: + # OTel timestamps have nanosecond precision + start_timestamp = convert_to_otel_timestamp(start_timestamp) + + # XXX deal with _otel_span being a NonRecordingSpan + self._otel_span = tracer.start_span( + description or op or "", start_time=start_timestamp + ) # XXX self._active = active self._otel_span.set_attribute(SentrySpanAttribute.ORIGIN, origin) if op is not None: - self._otel_span.set_attribute(SentrySpanAttribute.OP, op) - if description is not None: - self._otel_span.set_attribute(SentrySpanAttribute.DESCRIPTION, description) + self.op = op + self.description = description + if status is not None: + self.set_status(status) def __enter__(self): # type: () -> POTelSpan @@ -1301,11 +1316,97 @@ def __exit__(self, ty, value, tb): if self._active: context.detach(self._ctx_token) + @property + def description(self): + # type: () -> Optional[str] + from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute + + return self._otel_span.attributes.get(SentrySpanAttribute.DESCRIPTION) + + @description.setter + def description(self, value): + # type: (Optional[str]) -> None + from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute + + if value is not None: + self._otel_span.set_attribute(SentrySpanAttribute.DESCRIPTION, value) + + @property + def origin(self): + # type: () -> Optional[str] + from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute + + return self._otel_span.attributes.get(SentrySpanAttribute.ORIGIN) + + @origin.setter + def origin(self, value): + # type: (Optional[str]) -> None + from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute + + if value is not None: + self._otel_span.set_attribute(SentrySpanAttribute.ORIGIN, value) + @property def containing_transaction(self): # type: () -> Optional[Transaction] + """ + Get the transaction this span is a child of. + + .. deprecated:: 3.0.0 + This will be removed in the future. Use :func:`root_span` instead. + """ + logger.warning("Deprecated: This will be removed in the future.") + return self.root_span + + @containing_transaction.setter + def containing_transaction(self, value): + # type: (Span) -> None + """ + Set this span's transaction. + .. deprecated:: 3.0.0 + Use :func:`root_span` instead. + """ + pass + + @property + def parent_span_id(self): + # type: () -> Optional[str] + return self._otel_span.parent if hasattr(self._otel_span, "parent") else None + + @property + def trace_id(self): + # type: () -> Optional[str] + return self._otel_span.get_span_context().trace_id + + @property + def span_id(self): + # type: () -> Optional[str] + return self._otel_span.get_span_context().span_id + + @property + def sampled(self): + # type: () -> Optional[bool] + return self._otel_span.get_span_context().trace_flags.sampled + + @sampled.setter + def sampled(self, value): + # type: () -> Optional[bool] pass + @property + def op(self): + # type: () -> Optional[str] + from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute + + self._otel_span.attributes.get(SentrySpanAttribute.OP) + + @op.setter + def op(self, value): + # type: (str) -> None + from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute + + self._otel_span.set_attribute(SentrySpanAttribute.OP, value) + def start_child(self, **kwargs): # type: (str, **Any) -> POTelSpan pass @@ -1343,7 +1444,18 @@ def from_traceparent( def to_traceparent(self): # type: () -> str - pass + if self.sampled is True: + sampled = "1" + elif self.sampled is False: + sampled = "0" + else: + sampled = None + + traceparent = "%s-%s" % (self.trace_id, self.span_id) + if sampled is not None: + traceparent += "-%s" % (sampled,) + + return traceparent def to_baggage(self): # type: () -> Optional[Baggage] @@ -1359,23 +1471,41 @@ def set_data(self, key, value): def set_status(self, status): # type: (str) -> None - pass + if status == SPANSTATUS.OK: + otel_status = StatusCode.OK + otel_description = None + else: + otel_status = StatusCode.ERROR + otel_description = status.value + + self._otel_span.set_status(otel_status, otel_description) def set_measurement(self, name, value, unit=""): # type: (str, float, MeasurementUnit) -> None - pass + # XXX own namespace, e.g. sentry.measurement.xxx? + self._otel_span.set_attribute(name, (value, unit)) def set_thread(self, thread_id, thread_name): # type: (Optional[int], Optional[str]) -> None - pass + if thread_id is not None: + self.set_data(SPANDATA.THREAD_ID, str(thread_id)) + + if thread_name is not None: + self.set_data(SPANDATA.THREAD_NAME, thread_name) def set_profiler_id(self, profiler_id): # type: (Optional[str]) -> None - pass + if profiler_id is not None: + self.set_data(SPANDATA.PROFILER_ID, profiler_id) def set_http_status(self, http_status): # type: (int) -> None - pass + self.set_tag( + "http.status_code", str(http_status) + ) # we keep this for backwards compatibility + # XXX do we still need this? ^ + self.set_data(SPANDATA.HTTP_STATUS_CODE, http_status) + self.set_status(get_span_status_from_http_code(http_status)) def is_success(self): # type: () -> bool From f3ce775ade015a7b209cb7b2a9eaffb94bb425d0 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 12 Aug 2024 16:54:07 +0200 Subject: [PATCH 2/5] more stuff --- sentry_sdk/tracing.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 0bcb98d198..595e92029d 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1368,6 +1368,32 @@ def containing_transaction(self, value): """ pass + @property + def root_span(self): + if isinstance(self._otel_span, otel_trace.NonRecordingSpan): + return None + + parent = None + while True: + # XXX test if this actually works + if self._otel_span.parent: + parent = self._otel_span.parent + else: + break + + return parent + + @root_span.setter + def root_span(self, value): + pass + + @property + def is_root_span(self): + if isinstance(self._otel_span, otel_trace.NonRecordingSpan): + return False + + return self._otel_span.parent is None + @property def parent_span_id(self): # type: () -> Optional[str] @@ -1482,7 +1508,9 @@ def set_status(self, status): def set_measurement(self, name, value, unit=""): # type: (str, float, MeasurementUnit) -> None - # XXX own namespace, e.g. sentry.measurement.xxx? + # XXX own namespace, e.g. sentry.measurement.xxx, so that we can group + # these back together in the processor? + # XXX otel throws a warning about value, unit being different types self._otel_span.set_attribute(name, (value, unit)) def set_thread(self, thread_id, thread_name): From a7b24529005c880e9ea09b794e3192c9d6f11d1a Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 12 Aug 2024 18:10:59 +0200 Subject: [PATCH 3/5] add more things --- sentry_sdk/tracing.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 595e92029d..fdda14ce7f 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1297,6 +1297,22 @@ def __init__( if status is not None: self.set_status(status) + def __repr__(self): + # type: () -> str + return ( + "<%s(op=%r, description:%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r, origin=%r)>" + % ( + self.__class__.__name__, + self.op, + self.description, + self.trace_id, + self.span_id, + self.parent_span_id, + self.sampled, + self.origin, + ) + ) + def __enter__(self): # type: () -> POTelSpan # XXX use_span? https://github.com/open-telemetry/opentelemetry-python/blob/3836da8543ce9751051e38a110c0468724042e62/opentelemetry-api/src/opentelemetry/trace/__init__.py#L547 @@ -1433,6 +1449,26 @@ def op(self, value): self._otel_span.set_attribute(SentrySpanAttribute.OP, value) + @property + def name(self): + # type: () -> str + pass + + @name.setter + def name(self, value): + # type: (str) -> None + pass + + @property + def source(self): + # type: () -> str + pass + + @source.setter + def source(self, value): + # type: (str) -> None + pass + def start_child(self, **kwargs): # type: (str, **Any) -> POTelSpan pass From 7174739187ea5f3cc4c1c9460a604fd3571e6208 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Tue, 13 Aug 2024 13:20:22 +0200 Subject: [PATCH 4/5] remove http status code tag --- MIGRATION_GUIDE.md | 2 ++ sentry_sdk/tracing.py | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index df3ee6ea7d..e35131855b 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -11,6 +11,8 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh ### Removed +- When setting span status, the HTTP status code is no longer automatically added as a tag. + ### Deprecated diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index fdda14ce7f..106c4d1237 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1564,10 +1564,6 @@ def set_profiler_id(self, profiler_id): def set_http_status(self, http_status): # type: (int) -> None - self.set_tag( - "http.status_code", str(http_status) - ) # we keep this for backwards compatibility - # XXX do we still need this? ^ self.set_data(SPANDATA.HTTP_STATUS_CODE, http_status) self.set_status(get_span_status_from_http_code(http_status)) From 8c0d3338f5fd2b910a5bdc02f8b66ff0ba4841d4 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Tue, 13 Aug 2024 13:23:33 +0200 Subject: [PATCH 5/5] check op not being none in prop --- sentry_sdk/tracing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 106c4d1237..e9308fa88b 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1291,8 +1291,7 @@ def __init__( self._active = active self._otel_span.set_attribute(SentrySpanAttribute.ORIGIN, origin) - if op is not None: - self.op = op + self.op = op self.description = description if status is not None: self.set_status(status) @@ -1444,10 +1443,11 @@ def op(self): @op.setter def op(self, value): - # type: (str) -> None + # type: (Optional[str]) -> None from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute - self._otel_span.set_attribute(SentrySpanAttribute.OP, value) + if value is not None: + self._otel_span.set_attribute(SentrySpanAttribute.OP, value) @property def name(self):