Skip to content

Commit bdd774f

Browse files
authored
Merge branch 'master' into shellmayr/cohere/1-genai-migration
2 parents 2b2d3a6 + 53e2148 commit bdd774f

File tree

3 files changed

+663
-374
lines changed

3 files changed

+663
-374
lines changed

sentry_sdk/scope.py

Lines changed: 54 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 _DEFAULT_PARENT_SPAN, StreamedSpan, NoOpStreamedSpan
3737
from sentry_sdk.tracing import (
3838
BAGGAGE_HEADER_NAME,
3939
SENTRY_TRACE_HEADER_NAME,
@@ -1174,6 +1174,59 @@ def start_span(
11741174

11751175
return span
11761176

1177+
def start_streamed_span(
1178+
self,
1179+
name: str,
1180+
attributes: "Optional[Attributes]",
1181+
parent_span: "Optional[StreamedSpan]",
1182+
active: bool,
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 _DEFAULT_PARENT_SPAN or isinstance(
1193+
parent_span, NoOpStreamedSpan
1194+
):
1195+
parent_span = self.span # type: ignore
1196+
1197+
# If no eligible parent_span was provided and there is no currently
1198+
# active span, this is a segment
1199+
if parent_span is None:
1200+
propagation_context = self.get_active_propagation_context()
1201+
1202+
return StreamedSpan(
1203+
name=name,
1204+
attributes=attributes,
1205+
active=active,
1206+
scope=self,
1207+
segment=None,
1208+
trace_id=propagation_context.trace_id,
1209+
parent_span_id=propagation_context.parent_span_id,
1210+
parent_sampled=propagation_context.parent_sampled,
1211+
baggage=propagation_context.baggage,
1212+
)
1213+
1214+
# This is a child span; take propagation context from the parent span
1215+
with new_scope():
1216+
if isinstance(parent_span, NoOpStreamedSpan):
1217+
return NoOpStreamedSpan()
1218+
1219+
return StreamedSpan(
1220+
name=name,
1221+
attributes=attributes,
1222+
active=active,
1223+
scope=self,
1224+
segment=parent_span._segment,
1225+
trace_id=parent_span.trace_id,
1226+
parent_span_id=parent_span.span_id,
1227+
parent_sampled=parent_span.sampled,
1228+
)
1229+
11771230
def continue_trace(
11781231
self,
11791232
environ_or_headers: "Dict[str, Any]",

sentry_sdk/traces.py

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
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:
15-
from typing import Optional, Union
17+
from typing import Any, Optional, Union
1618
from sentry_sdk._types import Attributes, AttributeValue
1719

1820

@@ -57,6 +59,114 @@ def __str__(self) -> str:
5759
}
5860

5961

62+
# Sentinel value for an unset parent_span to be able to distinguish it from
63+
# a None set by the user
64+
_DEFAULT_PARENT_SPAN = object()
65+
66+
67+
def start_span(
68+
name: str,
69+
attributes: "Optional[Attributes]" = None,
70+
parent_span: "Optional[StreamedSpan]" = _DEFAULT_PARENT_SPAN, # type: ignore[assignment]
71+
active: bool = True,
72+
) -> "StreamedSpan":
73+
"""
74+
Start a span.
75+
76+
The span's parent, unless provided explicitly via the `parent_span` argument,
77+
will be the current active span, if any. If there is none, this span will
78+
become the root of a new span tree. If you explicitly want this span to be
79+
top-level without a parent, set `parent_span=None`.
80+
81+
`start_span()` can either be used as context manager or you can use the span
82+
object it returns and explicitly end it via `span.end()`. The following is
83+
equivalent:
84+
85+
```python
86+
import sentry_sdk
87+
88+
with sentry_sdk.traces.start_span(name="My Span"):
89+
# do something
90+
91+
# The span automatically finishes once the `with` block is exited
92+
```
93+
94+
```python
95+
import sentry_sdk
96+
97+
span = sentry_sdk.traces.start_span(name="My Span")
98+
# do something
99+
span.end()
100+
```
101+
102+
To continue a trace from another service, call
103+
`sentry_sdk.traces.continue_trace()` prior to creating a top-level span.
104+
105+
:param name: The name to identify this span by.
106+
:type name: str
107+
108+
:param attributes: Key-value attributes to set on the span from the start.
109+
These will also be accessible in the traces sampler.
110+
:type attributes: "Optional[Attributes]"
111+
112+
:param parent_span: A span instance that the new span should consider its
113+
parent. If not provided, the parent will be set to the currently active
114+
span, if any. If set to `None`, this span will become a new root-level
115+
span.
116+
:type parent_span: "Optional[StreamedSpan]"
117+
118+
:param active: Controls whether spans started while this span is running
119+
will automatically become its children. That's the default behavior. If
120+
you want to create a span that shouldn't have any children (unless
121+
provided explicitly via the `parent_span` argument), set this to `False`.
122+
:type active: bool
123+
124+
:return: The span that has been started.
125+
:rtype: StreamedSpan
126+
"""
127+
return sentry_sdk.get_current_scope().start_streamed_span(
128+
name, attributes, parent_span, active
129+
)
130+
131+
132+
def continue_trace(incoming: "dict[str, Any]") -> None:
133+
"""
134+
Continue a trace from headers or environment variables.
135+
136+
This function sets the propagation context on the scope. Any span started
137+
in the updated scope will belong under the trace extracted from the
138+
provided propagation headers or environment variables.
139+
140+
continue_trace() doesn't start any spans on its own. Use the start_span()
141+
API for that.
142+
"""
143+
# This is set both on the isolation and the current scope for compatibility
144+
# reasons. Conceptually, it belongs on the isolation scope, and it also
145+
# used to be set there in non-span-first mode. But in span first mode, we
146+
# start spans on the current scope, regardless of type, like JS does, so we
147+
# need to set the propagation context there.
148+
sentry_sdk.get_isolation_scope().generate_propagation_context(
149+
incoming,
150+
)
151+
sentry_sdk.get_current_scope().generate_propagation_context(
152+
incoming,
153+
)
154+
155+
156+
def new_trace() -> None:
157+
"""
158+
Resets the propagation context, forcing a new trace.
159+
160+
This function sets the propagation context on the scope. Any span started
161+
in the updated scope will start its own trace.
162+
163+
new_trace() doesn't start any spans on its own. Use the start_span() API
164+
for that.
165+
"""
166+
sentry_sdk.get_isolation_scope().set_new_propagation_context()
167+
sentry_sdk.get_current_scope().set_new_propagation_context()
168+
169+
60170
class StreamedSpan:
61171
"""
62172
A span holds timing information of a block of code.
@@ -73,7 +183,12 @@ class StreamedSpan:
73183
"_active",
74184
"_span_id",
75185
"_trace_id",
186+
"_parent_span_id",
187+
"_segment",
188+
"_parent_sampled",
76189
"_status",
190+
"_scope",
191+
"_baggage",
77192
)
78193

79194
def __init__(
@@ -82,7 +197,12 @@ def __init__(
82197
name: str,
83198
attributes: "Optional[Attributes]" = None,
84199
active: bool = True,
200+
scope: "sentry_sdk.Scope",
201+
segment: "Optional[StreamedSpan]" = None,
85202
trace_id: "Optional[str]" = None,
203+
parent_span_id: "Optional[str]" = None,
204+
parent_sampled: "Optional[bool]" = None,
205+
baggage: "Optional[Baggage]" = None,
86206
):
87207
self._name: str = name
88208
self._active: bool = active
@@ -91,8 +211,16 @@ def __init__(
91211
for attribute, value in attributes.items():
92212
self.set_attribute(attribute, value)
93213

94-
self._span_id: "Optional[str]" = None
214+
self._scope = scope
215+
216+
self._segment = segment or self
217+
95218
self._trace_id: "Optional[str]" = trace_id
219+
self._parent_span_id = parent_span_id
220+
self._parent_sampled = parent_sampled
221+
self._baggage = baggage
222+
223+
self._span_id: "Optional[str]" = None
96224

97225
self._status = SpanStatus.OK.value
98226
self.set_attribute("sentry.span.source", SegmentSource.CUSTOM.value)
@@ -103,6 +231,7 @@ def __repr__(self) -> str:
103231
f"name={self._name}, "
104232
f"trace_id={self.trace_id}, "
105233
f"span_id={self.span_id}, "
234+
f"parent_span_id={self._parent_span_id}, "
106235
f"active={self._active})>"
107236
)
108237

@@ -165,8 +294,18 @@ def trace_id(self) -> str:
165294

166295
return self._trace_id
167296

297+
@property
298+
def sampled(self) -> "Optional[bool]":
299+
return True
300+
168301

169302
class NoOpStreamedSpan(StreamedSpan):
303+
def __init__(self) -> None:
304+
pass
305+
306+
def __repr__(self) -> str:
307+
return f"<{self.__class__.__name__}(sampled={self.sampled})>"
308+
170309
def get_attributes(self) -> "Attributes":
171310
return {}
172311

@@ -206,3 +345,7 @@ def span_id(self) -> str:
206345
@property
207346
def trace_id(self) -> str:
208347
return "00000000000000000000000000000000"
348+
349+
@property
350+
def sampled(self) -> "Optional[bool]":
351+
return False

0 commit comments

Comments
 (0)