Skip to content

Commit 3373da2

Browse files
authored
feat(span-streaming): Add more span properties (#5421)
- add setters(/getters) or properties for `op`, `source`, `name`, `origin`, `status`, feature flags, `span_id` - also, rename `name` -> `_name` since it has a getter/setter now In general regarding this getter/setter business vs. property-based getting and setting: The idiomatic way would be to use properties (e.g. `span.name = "name"`), but since we can't get around having _some_ getters/setters (e.g. `set_attribute`, `set_flag`, etc.), it's probably better to go for consistency and have getters/setters everywhere, even if it's not idiomatic. Otherwise users would have to remember if the specific thing they want to set is a property or has a setter. The above is about stuff people might want to set, so public API. For internal stuff that no one should be setting by hand (e.g. `span_id`), we can still go with properties where it makes sense. Chipping away at #5317.
1 parent 4621c29 commit 3373da2

File tree

2 files changed

+111
-5
lines changed

2 files changed

+111
-5
lines changed

sentry_sdk/_span_batcher.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ def add(self, span: "StreamedSpan") -> None:
7171
def _to_transport_format(item: "StreamedSpan") -> "Any":
7272
# TODO[span-first]
7373
res: "dict[str, Any]" = {
74-
"name": item.name,
74+
"span_id": item.span_id,
75+
"name": item._name,
76+
"status": item._status,
7577
}
7678

7779
if item._attributes:

sentry_sdk/traces.py

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,61 @@
66
"""
77

88
import uuid
9+
from enum import Enum
910
from typing import TYPE_CHECKING
1011

12+
from sentry_sdk.consts import SPANDATA
1113
from sentry_sdk.utils import format_attribute
1214

1315
if TYPE_CHECKING:
14-
from typing import Optional
16+
from typing import Optional, Union
1517
from sentry_sdk._types import Attributes, AttributeValue
1618

1719

20+
FLAGS_CAPACITY = 10
21+
22+
23+
class SpanStatus(str, Enum):
24+
OK = "ok"
25+
ERROR = "error"
26+
27+
def __str__(self) -> str:
28+
return self.value
29+
30+
31+
# Segment source, see
32+
# https://getsentry.github.io/sentry-conventions/generated/attributes/sentry.html#sentryspansource
33+
class SegmentSource(str, Enum):
34+
COMPONENT = "component"
35+
CUSTOM = "custom"
36+
ROUTE = "route"
37+
TASK = "task"
38+
URL = "url"
39+
VIEW = "view"
40+
41+
def __str__(self) -> str:
42+
return self.value
43+
44+
45+
# These are typically high cardinality and the server hates them
46+
LOW_QUALITY_SEGMENT_SOURCES = [
47+
SegmentSource.URL,
48+
]
49+
50+
51+
SOURCE_FOR_STYLE = {
52+
"endpoint": SegmentSource.COMPONENT,
53+
"function_name": SegmentSource.COMPONENT,
54+
"handler_name": SegmentSource.COMPONENT,
55+
"method_and_path_pattern": SegmentSource.ROUTE,
56+
"path": SegmentSource.URL,
57+
"route_name": SegmentSource.COMPONENT,
58+
"route_pattern": SegmentSource.ROUTE,
59+
"uri_template": SegmentSource.ROUTE,
60+
"url": SegmentSource.ROUTE,
61+
}
62+
63+
1864
class StreamedSpan:
1965
"""
2066
A span holds timing information of a block of code.
@@ -26,9 +72,12 @@ class StreamedSpan:
2672
"""
2773

2874
__slots__ = (
29-
"name",
75+
"_name",
3076
"_attributes",
77+
"_span_id",
3178
"_trace_id",
79+
"_status",
80+
"_flags",
3281
)
3382

3483
def __init__(
@@ -38,13 +87,19 @@ def __init__(
3887
attributes: "Optional[Attributes]" = None,
3988
trace_id: "Optional[str]" = None,
4089
):
41-
self.name: str = name
90+
self._name: str = name
4291
self._attributes: "Attributes" = {}
4392
if attributes:
4493
for attribute, value in attributes.items():
4594
self.set_attribute(attribute, value)
4695

47-
self._trace_id = trace_id
96+
self._span_id: "Optional[str]" = None
97+
self._trace_id: "Optional[str]" = trace_id
98+
99+
self.set_status(SpanStatus.OK)
100+
self.set_source(SegmentSource.CUSTOM)
101+
102+
self._flags: dict[str, bool] = {}
48103

49104
def get_attributes(self) -> "Attributes":
50105
return self._attributes
@@ -62,6 +117,55 @@ def remove_attribute(self, key: str) -> None:
62117
except KeyError:
63118
pass
64119

120+
def get_status(self) -> "Union[SpanStatus, str]":
121+
if self._status in {s.value for s in SpanStatus}:
122+
return SpanStatus(self._status)
123+
124+
return self._status
125+
126+
def set_status(self, status: "Union[SpanStatus, str]") -> None:
127+
if isinstance(status, Enum):
128+
status = status.value
129+
130+
self._status = status
131+
132+
def set_http_status(self, http_status: int) -> None:
133+
self.set_attribute(SPANDATA.HTTP_STATUS_CODE, http_status)
134+
135+
if http_status >= 400:
136+
self.set_status(SpanStatus.ERROR)
137+
else:
138+
self.set_status(SpanStatus.OK)
139+
140+
def get_name(self) -> str:
141+
return self._name
142+
143+
def set_name(self, name: str) -> None:
144+
self._name = name
145+
146+
def set_flag(self, flag: str, result: bool) -> None:
147+
if len(self._flags) < FLAGS_CAPACITY:
148+
self._flags[flag] = result
149+
150+
def set_op(self, op: str) -> None:
151+
self.set_attribute("sentry.op", op)
152+
153+
def set_origin(self, origin: str) -> None:
154+
self.set_attribute("sentry.origin", origin)
155+
156+
def set_source(self, source: "Union[str, SegmentSource]") -> None:
157+
if isinstance(source, Enum):
158+
source = source.value
159+
160+
self.set_attribute("sentry.span.source", source)
161+
162+
@property
163+
def span_id(self) -> str:
164+
if not self._span_id:
165+
self._span_id = uuid.uuid4().hex[16:]
166+
167+
return self._span_id
168+
65169
@property
66170
def trace_id(self) -> str:
67171
if not self._trace_id:

0 commit comments

Comments
 (0)