diff --git a/sentry_sdk/_span_batcher.py b/sentry_sdk/_span_batcher.py index 38ecc8da51..947eca3806 100644 --- a/sentry_sdk/_span_batcher.py +++ b/sentry_sdk/_span_batcher.py @@ -71,7 +71,9 @@ def add(self, span: "StreamedSpan") -> None: def _to_transport_format(item: "StreamedSpan") -> "Any": # TODO[span-first] res: "dict[str, Any]" = { - "name": item.name, + "span_id": item.span_id, + "name": item._name, + "status": item._status, } if item._attributes: diff --git a/sentry_sdk/traces.py b/sentry_sdk/traces.py index a63a9bebb0..531a06b1fd 100644 --- a/sentry_sdk/traces.py +++ b/sentry_sdk/traces.py @@ -6,15 +6,61 @@ """ import uuid +from enum import Enum from typing import TYPE_CHECKING +from sentry_sdk.consts import SPANDATA from sentry_sdk.utils import format_attribute if TYPE_CHECKING: - from typing import Optional + from typing import Optional, Union from sentry_sdk._types import Attributes, AttributeValue +FLAGS_CAPACITY = 10 + + +class SpanStatus(str, Enum): + OK = "ok" + ERROR = "error" + + def __str__(self) -> str: + return self.value + + +# Segment source, see +# https://getsentry.github.io/sentry-conventions/generated/attributes/sentry.html#sentryspansource +class SegmentSource(str, Enum): + COMPONENT = "component" + CUSTOM = "custom" + ROUTE = "route" + TASK = "task" + URL = "url" + VIEW = "view" + + def __str__(self) -> str: + return self.value + + +# These are typically high cardinality and the server hates them +LOW_QUALITY_SEGMENT_SOURCES = [ + SegmentSource.URL, +] + + +SOURCE_FOR_STYLE = { + "endpoint": SegmentSource.COMPONENT, + "function_name": SegmentSource.COMPONENT, + "handler_name": SegmentSource.COMPONENT, + "method_and_path_pattern": SegmentSource.ROUTE, + "path": SegmentSource.URL, + "route_name": SegmentSource.COMPONENT, + "route_pattern": SegmentSource.ROUTE, + "uri_template": SegmentSource.ROUTE, + "url": SegmentSource.ROUTE, +} + + class StreamedSpan: """ A span holds timing information of a block of code. @@ -26,9 +72,12 @@ class StreamedSpan: """ __slots__ = ( - "name", + "_name", "_attributes", + "_span_id", "_trace_id", + "_status", + "_flags", ) def __init__( @@ -38,13 +87,19 @@ def __init__( attributes: "Optional[Attributes]" = None, trace_id: "Optional[str]" = None, ): - self.name: str = name + self._name: str = name self._attributes: "Attributes" = {} if attributes: for attribute, value in attributes.items(): self.set_attribute(attribute, value) - self._trace_id = trace_id + self._span_id: "Optional[str]" = None + self._trace_id: "Optional[str]" = trace_id + + self.set_status(SpanStatus.OK) + self.set_source(SegmentSource.CUSTOM) + + self._flags: dict[str, bool] = {} def get_attributes(self) -> "Attributes": return self._attributes @@ -62,6 +117,55 @@ def remove_attribute(self, key: str) -> None: except KeyError: pass + def get_status(self) -> "Union[SpanStatus, str]": + if self._status in {s.value for s in SpanStatus}: + return SpanStatus(self._status) + + return self._status + + def set_status(self, status: "Union[SpanStatus, str]") -> None: + if isinstance(status, Enum): + status = status.value + + self._status = status + + def set_http_status(self, http_status: int) -> None: + self.set_attribute(SPANDATA.HTTP_STATUS_CODE, http_status) + + if http_status >= 400: + self.set_status(SpanStatus.ERROR) + else: + self.set_status(SpanStatus.OK) + + def get_name(self) -> str: + return self._name + + def set_name(self, name: str) -> None: + self._name = name + + def set_flag(self, flag: str, result: bool) -> None: + if len(self._flags) < FLAGS_CAPACITY: + self._flags[flag] = result + + def set_op(self, op: str) -> None: + self.set_attribute("sentry.op", op) + + def set_origin(self, origin: str) -> None: + self.set_attribute("sentry.origin", origin) + + def set_source(self, source: "Union[str, SegmentSource]") -> None: + if isinstance(source, Enum): + source = source.value + + self.set_attribute("sentry.span.source", source) + + @property + def span_id(self) -> str: + if not self._span_id: + self._span_id = uuid.uuid4().hex[16:] + + return self._span_id + @property def trace_id(self) -> str: if not self._trace_id: