99from enum import Enum
1010from typing import TYPE_CHECKING
1111
12+ import sentry_sdk
13+ from sentry_sdk .tracing_utils import Baggage
1214from sentry_sdk .utils import format_attribute , logger
1315
1416if 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+
60170class 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
169302class 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