88import sentry_sdk
99from sentry_sdk .tracing_utils import (
1010 _generate_sample_rand ,
11- _round_sample_rand ,
1211 has_tracing_enabled ,
1312)
1413from sentry_sdk .utils import is_valid_sample_rate , logger
@@ -87,12 +86,12 @@ def get_parent_sample_rand(parent_context, trace_id):
8786 if parent_sample_rand is None :
8887 return None
8988
90- return _round_sample_rand (parent_sample_rand )
89+ return Decimal (parent_sample_rand )
9190
9291 return None
9392
9493
95- def dropped_result (parent_span_context , attributes , sample_rate = None , sample_rand = None ):
94+ def dropped_result (span_context , attributes , sample_rate = None , sample_rand = None ):
9695 # type: (SpanContext, Attributes, Optional[float], Optional[Decimal]) -> SamplingResult
9796 """
9897 React to a span getting unsampled and return a DROP SamplingResult.
@@ -104,26 +103,11 @@ def dropped_result(parent_span_context, attributes, sample_rate=None, sample_ran
104103 See for more info about OTel sampling:
105104 https://opentelemetry-python.readthedocs.io/en/latest/sdk/trace.sampling.html
106105 """
107- # these will only be added the first time in a root span sampling decision
108- # if sample_rate or sample_rand is provided, they'll be updated in trace state
109- trace_state = parent_span_context .trace_state
110-
111- if TRACESTATE_SAMPLED_KEY not in trace_state :
112- trace_state = trace_state .add (TRACESTATE_SAMPLED_KEY , "false" )
113- elif trace_state .get (TRACESTATE_SAMPLED_KEY ) == "deferred" :
114- trace_state = trace_state .update (TRACESTATE_SAMPLED_KEY , "false" )
115-
116- if sample_rate is not None :
117- trace_state = trace_state .update (TRACESTATE_SAMPLE_RATE_KEY , str (sample_rate ))
118-
119- if sample_rand is not None :
120- trace_state = trace_state .update (
121- TRACESTATE_SAMPLE_RAND_KEY , f"{ sample_rand :.6f} " # noqa: E231
122- )
123-
124- is_root_span = not (
125- parent_span_context .is_valid and not parent_span_context .is_remote
106+ trace_state = _update_trace_state (
107+ span_context , sampled = False , sample_rate = sample_rate , sample_rand = sample_rand
126108 )
109+
110+ is_root_span = not (span_context .is_valid and not span_context .is_remote )
127111 if is_root_span :
128112 # Tell Sentry why we dropped the transaction/root-span
129113 client = sentry_sdk .get_client ()
@@ -156,28 +140,38 @@ def sampled_result(span_context, attributes, sample_rate=None, sample_rand=None)
156140 See for more info about OTel sampling:
157141 https://opentelemetry-python.readthedocs.io/en/latest/sdk/trace.sampling.html
158142 """
159- # these will only be added the first time in a root span sampling decision
160- # if sample_rate or sample_rand is provided, they'll be updated in trace state
143+ trace_state = _update_trace_state (
144+ span_context , sampled = True , sample_rate = sample_rate , sample_rand = sample_rand
145+ )
146+
147+ return SamplingResult (
148+ Decision .RECORD_AND_SAMPLE ,
149+ attributes = attributes ,
150+ trace_state = trace_state ,
151+ )
152+
153+
154+ def _update_trace_state (span_context , sampled , sample_rate = None , sample_rand = None ):
155+ # type: (..., bool, Optional[float], Optional[Decimal]) -> TraceState
161156 trace_state = span_context .trace_state
162157
158+ sampled = "true" if sampled else "false"
163159 if TRACESTATE_SAMPLED_KEY not in trace_state :
164- trace_state = trace_state .add (TRACESTATE_SAMPLED_KEY , "true" )
160+ trace_state = trace_state .add (TRACESTATE_SAMPLED_KEY , sampled )
165161 elif trace_state .get (TRACESTATE_SAMPLED_KEY ) == "deferred" :
166- trace_state = trace_state .update (TRACESTATE_SAMPLED_KEY , "true" )
162+ trace_state = trace_state .update (TRACESTATE_SAMPLED_KEY , sampled )
167163
164+ # sample_rate should always be updated to ensure complete traces
168165 if sample_rate is not None :
169166 trace_state = trace_state .update (TRACESTATE_SAMPLE_RATE_KEY , str (sample_rate ))
170167
168+ # only add sample_rand if there isn't one
171169 if sample_rand is not None :
172- trace_state = trace_state .update (
170+ trace_state = trace_state .add (
173171 TRACESTATE_SAMPLE_RAND_KEY , f"{ sample_rand :.6f} " # noqa: E231
174172 )
175173
176- return SamplingResult (
177- Decision .RECORD_AND_SAMPLE ,
178- attributes = attributes ,
179- trace_state = trace_state ,
180- )
174+ return trace_state
181175
182176
183177class SentrySampler (Sampler ):
@@ -274,17 +268,15 @@ def should_sample(
274268 logger .warning (
275269 f"[Tracing] Discarding { name } because of invalid sample rate."
276270 )
277- return dropped_result (
278- parent_span_context , attributes , sample_rand = sample_rand
279- )
271+ return dropped_result (parent_span_context , attributes )
280272
281273 # Down-sample in case of back pressure monitor says so
282274 if is_root_span and client .monitor :
283275 sample_rate /= 2 ** client .monitor .downsample_factor
284276 if client .monitor .downsample_factor > 0 :
285277 sample_rate_to_propagate = sample_rate
286278
287- # Roll the dice on sample rate
279+ # Compare sample_rand to sample_rate to make the final sampling decision
288280 sample_rate = float (cast ("Union[bool, float, int]" , sample_rate ))
289281 sampled = sample_rand < sample_rate
290282
@@ -293,14 +285,14 @@ def should_sample(
293285 parent_span_context ,
294286 attributes ,
295287 sample_rate = sample_rate_to_propagate ,
296- sample_rand = sample_rand ,
288+ sample_rand = None if sample_rand == parent_sample_rand else sample_rand ,
297289 )
298290 else :
299291 return dropped_result (
300292 parent_span_context ,
301293 attributes ,
302294 sample_rate = sample_rate_to_propagate ,
303- sample_rand = sample_rand ,
295+ sample_rand = None if sample_rand == parent_sample_rand else sample_rand ,
304296 )
305297
306298 def get_description (self ) -> str :
0 commit comments