88from sentry_sdk .profiler .continuous_profiler import get_profiler_id
99from sentry_sdk .tracing_utils import (
1010 Baggage ,
11+ _generate_sample_rand ,
1112 has_span_streaming_enabled ,
1213 has_tracing_enabled ,
1314)
3839- initial status: OK? or unset?
3940- dropped spans are not migrated
4041- recheck transaction.finish <-> Streamedspan.end
41- - profile not part of the event, how to send?
42+ - profiling: drop transaction based
43+ - profiling: actually send profiles
4244- maybe: use getters/setter OR properties but not both
4345- add size-based flushing to buffer(s)
4446- migrate transaction sample_rand logic
47+ - remove deprecated profiler impl
48+ - {custom_}sampling_context? -> if this is going to die, we need to revive the
49+ potel pr that went through the integrations and got rid of custom_sampling_context
50+ in favor of attributes
4551
4652Notes:
4753- removed ability to provide a start_timestamp
@@ -54,7 +60,9 @@ def start_span(
5460 attributes : "Optional[Attributes]" = None ,
5561 parent_span : "Optional[StreamedSpan]" = None ,
5662) -> "StreamedSpan" :
57- return sentry_sdk .get_current_scope ().start_streamed_span ()
63+ return sentry_sdk .get_current_scope ().start_streamed_span (
64+ name , attributes , parent_span
65+ )
5866
5967
6068class SpanStatus (str , Enum ):
@@ -109,44 +117,57 @@ class StreamedSpan:
109117 """
110118
111119 __slots__ = (
112- "_name " ,
113- "_attributes " ,
120+ "name " ,
121+ "attributes " ,
114122 "_span_id" ,
115123 "_trace_id" ,
116- "_parent_span_id " ,
117- "_segment " ,
124+ "parent_span_id " ,
125+ "segment " ,
118126 "_sampled" ,
119- "_start_timestamp" ,
120- "_timestamp" ,
121- "_status" ,
127+ "parent_sampled" ,
128+ "start_timestamp" ,
129+ "timestamp" ,
130+ "status" ,
122131 "_start_timestamp_monotonic_ns" ,
123132 "_scope" ,
124133 "_flags" ,
125134 "_context_manager_state" ,
126135 "_profile" ,
127136 "_continuous_profile" ,
128137 "_baggage" ,
129- "_sample_rate " ,
138+ "sample_rate " ,
130139 "_sample_rand" ,
140+ "source" ,
131141 )
132142
133143 def __init__ (
134144 self ,
145+ * ,
135146 name : str ,
136- trace_id : str ,
147+ scope : "Scope" ,
137148 attributes : "Optional[Attributes]" = None ,
149+ # TODO[span-first]: would be good to actually take this propagation
150+ # context stuff directly from the PropagationContext, but for that
151+ # we'd actually need to refactor PropagationContext to stay in sync
152+ # with what's going on (e.g. update the current span_id) and not just
153+ # update when a trace is continued
154+ trace_id : "Optional[str]" = None ,
138155 parent_span_id : "Optional[str]" = None ,
156+ parent_sampled : "Optional[bool]" = None ,
157+ baggage : "Optional[Baggage]" = None ,
139158 segment : "Optional[StreamedSpan]" = None ,
140- scope : "Optional[Scope]" = None ,
141159 ) -> None :
142- self ._name : str = name
143- self ._attributes : "Attributes" = attributes
160+ self ._scope = scope
161+
162+ self .name : str = name
163+ self .attributes : "Attributes" = attributes
144164
145165 self ._trace_id = trace_id
146- self ._parent_span_id = parent_span_id
147- self ._segment = segment or self
166+ self .parent_span_id = parent_span_id
167+ self .parent_sampled = parent_sampled
168+ self .segment = segment or self
148169
149- self ._start_timestamp = datetime .now (timezone .utc )
170+ self .start_timestamp = datetime .now (timezone .utc )
150171
151172 try :
152173 # profiling depends on this value and requires that
@@ -155,24 +176,42 @@ def __init__(
155176 except AttributeError :
156177 pass
157178
158- self ._timestamp : "Optional[datetime]" = None
179+ self .timestamp : "Optional[datetime]" = None
159180 self ._span_id : "Optional[str]" = None
160- self ._status : SpanStatus = SpanStatus .OK
181+
182+ self .status : SpanStatus = SpanStatus .OK
183+ self .source : "Optional[SegmentSource]" = SegmentSource .CUSTOM
184+ # XXX[span-first] ^ populate this correctly
185+
161186 self ._sampled : "Optional[bool]" = None
162- self ._scope : "Optional[Scope]" = scope # TODO[span-first] when are we starting a span with a specific scope? is this needed?
187+ self .sample_rate : "Optional[float]" = None
188+ self ._sample_rand : "Optional[float]" = None
189+
190+ # XXX[span-first]: just do this for segments?
191+ self ._baggage = baggage
192+ baggage_sample_rand = (
193+ None if self ._baggage is None else self ._baggage ._sample_rand ()
194+ )
195+ if baggage_sample_rand is not None :
196+ self ._sample_rand = baggage_sample_rand
197+ else :
198+ self ._sample_rand = _generate_sample_rand (self .trace_id )
199+
163200 self ._flags : dict [str , bool ] = {}
201+ self ._profile = None
202+ self ._continuous_profile = None
164203
165204 self ._update_active_thread ()
166205 self ._set_profiler_id (get_profiler_id ())
167206
168207 def __repr__ (self ) -> str :
169208 return (
170209 f"<{ self .__class__ .__name__ } ("
171- f"name={ self ._name } , "
172- f"trace_id={ self ._trace_id } , "
173- f"span_id={ self ._span_id } , "
174- f"parent_span_id={ self ._parent_span_id } , "
175- f"sampled={ self ._sampled } )>"
210+ f"name={ self .name } , "
211+ f"trace_id={ self .trace_id } , "
212+ f"span_id={ self .span_id } , "
213+ f"parent_span_id={ self .parent_span_id } , "
214+ f"sampled={ self .sampled } )>"
176215 )
177216
178217 def __enter__ (self ) -> "StreamedSpan" :
@@ -215,8 +254,7 @@ def end(
215254
216255 :param end_timestamp: Optional timestamp that should
217256 be used as timestamp instead of the current time.
218- :param scope: The scope to use for this transaction.
219- If not provided, the current scope will be used.
257+ :param scope: The scope to use.
220258 """
221259 client = sentry_sdk .get_client ()
222260 if not client .is_active ():
@@ -227,7 +265,7 @@ def end(
227265 )
228266
229267 # Explicit check against False needed because self.sampled might be None
230- if self ._sampled is False :
268+ if self .sampled is False :
231269 logger .debug ("Discarding span because sampled = False" )
232270
233271 # This is not entirely accurate because discards here are not
@@ -243,7 +281,7 @@ def end(
243281
244282 return None
245283
246- if self ._sampled is None :
284+ if self .sampled is None :
247285 logger .warning ("Discarding transaction without sampling decision." )
248286
249287 if self .timestamp is not None :
@@ -257,46 +295,39 @@ def end(
257295 self .timestamp = end_timestamp
258296 else :
259297 elapsed = nanosecond_time () - self ._start_timestamp_monotonic_ns
260- self .timestamp = self ._start_timestamp + timedelta (
298+ self .timestamp = self .start_timestamp + timedelta (
261299 microseconds = elapsed / 1000
262300 )
263301 except AttributeError :
264302 self .timestamp = datetime .now (timezone .utc )
265303
266- if self .segment .sampled :
267- client ._capture_span (self )
304+ if self .segment .sampled : # XXX this should just use its own sampled
305+ sentry_sdk .get_current_scope ()._capture_span (self )
306+
268307 return
269308
270309 def get_attributes (self ) -> "Attributes" :
271- return self ._attributes
310+ return self .attributes
272311
273312 def set_attribute (self , key : str , value : "AttributeValue" ) -> None :
274- self ._attributes [key ] = format_attribute (value )
313+ self .attributes [key ] = format_attribute (value )
275314
276315 def set_attributes (self , attributes : "Attributes" ) -> None :
277316 for key , value in attributes .items ():
278317 self .set_attribute (key , value )
279318
280319 def set_status (self , status : SpanStatus ) -> None :
281- self ._status = status
320+ self .status = status
282321
283322 def get_name (self ) -> str :
284- return self ._name
323+ return self .name
285324
286325 def set_name (self , name : str ) -> None :
287- self ._name = name
288-
289- @property
290- def segment (self ) -> "StreamedSpan" :
291- return self ._segment
326+ self .name = name
292327
293328 def is_segment (self ) -> bool :
294329 return self .segment == self
295330
296- @property
297- def sampled (self ) -> "Optional[bool]" :
298- return self ._sampled
299-
300331 @property
301332 def span_id (self ) -> str :
302333 if not self ._span_id :
@@ -312,6 +343,15 @@ def trace_id(self) -> str:
312343 return self ._trace_id
313344
314345 @property
346+ def sampled (self ) -> "Optional[bool]" :
347+ if self ._sampled is not None :
348+ return self ._sampled
349+
350+ if not self .is_segment ():
351+ self ._sampled = self .parent_sampled
352+
353+ return self ._sampled
354+
315355 def dynamic_sampling_context (self ) -> str :
316356 return self .segment ._get_baggage ().dynamic_sampling_context ()
317357
@@ -352,29 +392,15 @@ def _get_baggage(self) -> "Baggage":
352392
353393 return self ._baggage
354394
355- def _set_initial_sampling_decision (
356- self , sampling_context : "SamplingContext"
357- ) -> None :
395+ def _set_sampling_decision (self , sampling_context : "SamplingContext" ) -> None :
358396 """
359- Sets the segment's sampling decision, according to the following
360- precedence rules:
361-
362- 1. If `traces_sampler` is defined, its decision will be used. It can
363- choose to keep or ignore any parent sampling decision, or use the
364- sampling context data to make its own decision or to choose a sample
365- rate for the transaction.
366-
367- 2. If `traces_sampler` is not defined, but there's a parent sampling
368- decision, the parent sampling decision will be used.
369-
370- 3. If `traces_sampler` is not defined and there's no parent sampling
371- decision, `traces_sample_rate` will be used.
397+ Set the segment's sampling decision, inherited by all child spans.
372398 """
373399 client = sentry_sdk .get_client ()
374400
375401 # nothing to do if tracing is disabled
376402 if not has_tracing_enabled (client .options ):
377- self .sampled = False
403+ self ._sampled = False
378404 return
379405
380406 if not self .is_segment ():
@@ -398,9 +424,9 @@ def _set_initial_sampling_decision(
398424 # booleans or numbers between 0 and 1.)
399425 if not is_valid_sample_rate (sample_rate , source = "Tracing" ):
400426 logger .warning (
401- f"[Tracing] Discarding { self ._name } because of invalid sample rate."
427+ f"[Tracing] Discarding { self .name } because of invalid sample rate."
402428 )
403- self .sampled = False
429+ self ._sampled = False
404430 return
405431
406432 self .sample_rate = float (sample_rate )
@@ -416,12 +442,12 @@ def _set_initial_sampling_decision(
416442 else :
417443 reason = "traces_sample_rate is set to 0"
418444
419- logger .debug (f"[Tracing] Discarding { self ._name } because { reason } " )
420- self .sampled = False
445+ logger .debug (f"[Tracing] Discarding { self .name } because { reason } " )
446+ self ._sampled = False
421447 return
422448
423449 # Now we roll the dice.
424- self .sampled = self ._sample_rand < self .sample_rate
450+ self ._sampled = self ._sample_rand < self .sample_rate
425451
426452 if self .sampled :
427453 logger .debug (f"[Tracing] Starting { self .name } " )
0 commit comments