Skip to content

Commit d773428

Browse files
committed
ref: Add experimental streaming API
1 parent 1f0ffc1 commit d773428

File tree

2 files changed

+146
-2
lines changed

2 files changed

+146
-2
lines changed

sentry_sdk/scope.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
normalize_incoming_data,
3434
PropagationContext,
3535
)
36-
from sentry_sdk.traces import StreamedSpan
36+
from sentry_sdk.traces import StreamedSpan, NoOpStreamedSpan
3737
from sentry_sdk.tracing import (
3838
BAGGAGE_HEADER_NAME,
3939
SENTRY_TRACE_HEADER_NAME,
@@ -1174,6 +1174,58 @@ def start_span(
11741174

11751175
return span
11761176

1177+
def start_streamed_span(
1178+
self,
1179+
name: str,
1180+
attributes: "Optional[Attributes]" = None,
1181+
parent_span: "Optional[StreamedSpan]" = None,
1182+
active: bool = True,
1183+
) -> "StreamedSpan":
1184+
# TODO: rename to start_span once we drop the old API
1185+
if isinstance(parent_span, NoOpStreamedSpan):
1186+
# parent_span is only set if the user explicitly set it
1187+
logger.debug(
1188+
"Ignored parent span provided. Span will be parented to the "
1189+
"currently active span instead."
1190+
)
1191+
1192+
if parent_span is None or isinstance(parent_span, NoOpStreamedSpan):
1193+
parent_span = self.span or self.get_current_scope().span # type: ignore
1194+
1195+
# If no eligible parent_span was provided and there is no currently
1196+
# active span, this is a segment
1197+
if parent_span is None:
1198+
propagation_context = self.get_active_propagation_context()
1199+
1200+
return StreamedSpan(
1201+
name=name,
1202+
attributes=attributes,
1203+
active=active,
1204+
scope=self,
1205+
segment=None,
1206+
trace_id=propagation_context.trace_id,
1207+
parent_span_id=propagation_context.parent_span_id,
1208+
parent_sampled=propagation_context.parent_sampled,
1209+
baggage=propagation_context.baggage,
1210+
)
1211+
1212+
# This is a child span; take propagation context from the parent span
1213+
with new_scope():
1214+
if isinstance(parent_span, NoOpStreamedSpan):
1215+
return NoOpStreamedSpan()
1216+
1217+
return StreamedSpan(
1218+
name=name,
1219+
attributes=attributes,
1220+
active=active,
1221+
scope=self,
1222+
segment=parent_span._segment,
1223+
trace_id=parent_span.trace_id,
1224+
parent_span_id=parent_span.span_id,
1225+
parent_sampled=parent_span.sampled,
1226+
)
1227+
1228+
11771229
def continue_trace(
11781230
self,
11791231
environ_or_headers: "Dict[str, Any]",

sentry_sdk/traces.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from enum import Enum
1010
from typing import TYPE_CHECKING
1111

12+
import sentry_sdk
13+
from sentry_sdk.tracing_utils import Baggage
1214
from sentry_sdk.utils import format_attribute, logger
1315

1416
if TYPE_CHECKING:
@@ -57,6 +59,66 @@ def __str__(self) -> str:
5759
}
5860

5961

62+
def start_span(
63+
name: str,
64+
attributes: "Optional[Attributes]" = None,
65+
parent_span: "Optional[StreamedSpan]" = None,
66+
active: bool = True,
67+
) -> "StreamedSpan":
68+
"""
69+
Start a span.
70+
71+
The span's parent, unless provided explicitly via the `parent_span` argument,
72+
will be the current active span, if any. If there is none, this span will
73+
become the root of a new span tree.
74+
75+
`start_span()` can either be used as context manager or you can use the span
76+
object it returns and explicitly end it via `span.end()`. The following is
77+
equivalent:
78+
79+
```python
80+
import sentry_sdk
81+
82+
with sentry_sdk.traces.start_span(name="My Span"):
83+
# do something
84+
85+
# The span automatically finishes once the `with` block is exited
86+
```
87+
88+
```python
89+
import sentry_sdk
90+
91+
span = sentry_sdk.traces.start_span(name="My Span")
92+
# do something
93+
span.end()
94+
```
95+
96+
:param name: The name to identify this span by.
97+
:type name: str
98+
99+
:param attributes: Key-value attributes to set on the span from the start.
100+
These will also be accessible in the traces sampler.
101+
:type attributes: "Optional[Attributes]"
102+
103+
:param parent_span: A span instance that the new span should consider its
104+
parent. If not provided, the parent will be set to the currently active
105+
span, if any.
106+
:type parent_span: "Optional[StreamedSpan]"
107+
108+
:param active: Controls whether spans started while this span is running
109+
will automatically become its children. That's the default behavior. If
110+
you want to create a span that shouldn't have any children (unless
111+
provided explicitly via the `parent_span` argument), set this to `False`.
112+
:type active: bool
113+
114+
:return: The span that has been started.
115+
:rtype: StreamedSpan
116+
"""
117+
return sentry_sdk.get_current_scope().start_streamed_span(
118+
name, attributes, parent_span, active
119+
)
120+
121+
60122
class StreamedSpan:
61123
"""
62124
A span holds timing information of a block of code.
@@ -73,7 +135,12 @@ class StreamedSpan:
73135
"_active",
74136
"_span_id",
75137
"_trace_id",
138+
"_parent_span_id",
139+
"_segment",
140+
"_parent_sampled",
76141
"_status",
142+
"_scope",
143+
"_baggage",
77144
)
78145

79146
def __init__(
@@ -82,7 +149,12 @@ def __init__(
82149
name: str,
83150
attributes: "Optional[Attributes]" = None,
84151
active: bool = True,
152+
scope: "sentry_sdk.Scope",
153+
segment: "Optional[StreamedSpan]" = None,
85154
trace_id: "Optional[str]" = None,
155+
parent_span_id: "Optional[str]" = None,
156+
parent_sampled: "Optional[bool]" = None,
157+
baggage: "Optional[Baggage]" = None,
86158
):
87159
self._name: str = name
88160
self._active: bool = active
@@ -91,8 +163,16 @@ def __init__(
91163
for attribute, value in attributes.items():
92164
self.set_attribute(attribute, value)
93165

94-
self._span_id: "Optional[str]" = None
166+
self._scope = scope
167+
168+
self._segment = segment or self
169+
95170
self._trace_id: "Optional[str]" = trace_id
171+
self._parent_span_id = parent_span_id
172+
self._parent_sampled = parent_sampled
173+
self._baggage = baggage
174+
175+
self._span_id: "Optional[str]" = None
96176

97177
self._status = SpanStatus.OK.value
98178
self.set_attribute("sentry.span.source", SegmentSource.CUSTOM.value)
@@ -103,6 +183,7 @@ def __repr__(self) -> str:
103183
f"name={self._name}, "
104184
f"trace_id={self.trace_id}, "
105185
f"span_id={self.span_id}, "
186+
f"parent_span_id={self._parent_span_id}, "
106187
f"active={self._active})>"
107188
)
108189

@@ -165,8 +246,15 @@ def trace_id(self) -> str:
165246

166247
return self._trace_id
167248

249+
@property
250+
def sampled(self) -> "Optional[bool]":
251+
return True
252+
168253

169254
class NoOpStreamedSpan(StreamedSpan):
255+
def __init__(self) -> None:
256+
pass
257+
170258
def get_attributes(self) -> "Attributes":
171259
return {}
172260

@@ -206,3 +294,7 @@ def span_id(self) -> str:
206294
@property
207295
def trace_id(self) -> str:
208296
return "00000000000000000000000000000000"
297+
298+
@property
299+
def sampled(self) -> "Optional[bool]":
300+
return False

0 commit comments

Comments
 (0)