11import random
2+ from decimal import Decimal
23from typing import cast
34
45from opentelemetry import trace
56from opentelemetry .sdk .trace .sampling import Sampler , SamplingResult , Decision
67from opentelemetry .trace .span import TraceState
78
89import sentry_sdk
9- from sentry_sdk .tracing_utils import has_tracing_enabled
10+ from sentry_sdk .tracing_utils import (
11+ _generate_sample_rand ,
12+ _sample_rand_range ,
13+ has_tracing_enabled ,
14+ )
1015from sentry_sdk .utils import is_valid_sample_rate , logger
1116from sentry_sdk .integrations .opentelemetry .consts import (
1217 TRACESTATE_SAMPLED_KEY ,
18+ TRACESTATE_SAMPLE_RAND_KEY ,
1319 TRACESTATE_SAMPLE_RATE_KEY ,
1420 SentrySpanAttribute ,
1521)
@@ -70,8 +76,28 @@ def get_parent_sample_rate(parent_context, trace_id):
7076 return None
7177
7278
73- def dropped_result (parent_span_context , attributes , sample_rate = None ):
74- # type: (SpanContext, Attributes, Optional[float]) -> SamplingResult
79+ def get_parent_sample_rand (parent_context , trace_id ):
80+ # type: (Optional[SpanContext], int) -> Optional[Decimal]
81+ if parent_context is None :
82+ return None
83+
84+ is_span_context_valid = parent_context is not None and parent_context .is_valid
85+
86+ if is_span_context_valid and parent_context .trace_id == trace_id :
87+ parent_sample_rand = parent_context .trace_state .get (TRACESTATE_SAMPLE_RAND_KEY )
88+ if parent_sample_rand is None :
89+ return None
90+
91+ try :
92+ return Decimal (parent_sample_rand )
93+ except Exception :
94+ return None
95+
96+ return None
97+
98+
99+ def dropped_result (parent_span_context , attributes , sample_rate = None , sample_rand = None ):
100+ # type: (SpanContext, Attributes, Optional[float], Optional[Decimal]) -> SamplingResult
75101 # these will only be added the first time in a root span sampling decision
76102 # if sample_rate is provided, it'll be updated in trace state
77103 trace_state = parent_span_context .trace_state
@@ -84,6 +110,9 @@ def dropped_result(parent_span_context, attributes, sample_rate=None):
84110 if sample_rate is not None :
85111 trace_state = trace_state .update (TRACESTATE_SAMPLE_RATE_KEY , str (sample_rate ))
86112
113+ if sample_rand is not None :
114+ trace_state = trace_state .update (TRACESTATE_SAMPLE_RAND_KEY , str (sample_rand ))
115+
87116 is_root_span = not (
88117 parent_span_context .is_valid and not parent_span_context .is_remote
89118 )
@@ -108,8 +137,8 @@ def dropped_result(parent_span_context, attributes, sample_rate=None):
108137 )
109138
110139
111- def sampled_result (span_context , attributes , sample_rate ):
112- # type: (SpanContext, Attributes, Optional[float]) -> SamplingResult
140+ def sampled_result (span_context , attributes , sample_rate = None , sample_rand = None ):
141+ # type: (SpanContext, Attributes, Optional[float], Optional[Decimal] ) -> SamplingResult
113142 # these will only be added the first time in a root span sampling decision
114143 # if sample_rate is provided, it'll be updated in trace state
115144 trace_state = span_context .trace_state
@@ -122,6 +151,9 @@ def sampled_result(span_context, attributes, sample_rate):
122151 if sample_rate is not None :
123152 trace_state = trace_state .update (TRACESTATE_SAMPLE_RATE_KEY , str (sample_rate ))
124153
154+ if sample_rand is not None :
155+ trace_state = trace_state .update (TRACESTATE_SAMPLE_RAND_KEY , str (sample_rand ))
156+
125157 return SamplingResult (
126158 Decision .RECORD_AND_SAMPLE ,
127159 attributes = attributes ,
@@ -156,6 +188,15 @@ def should_sample(
156188
157189 sample_rate = None
158190
191+ parent_sampled = get_parent_sampled (parent_span_context , trace_id )
192+ parent_sample_rate = get_parent_sample_rate (parent_span_context , trace_id )
193+ parent_sample_rand = get_parent_sample_rand (parent_span_context , trace_id )
194+ if parent_sample_rand is not None :
195+ sample_rand = parent_sample_rand
196+ else :
197+ lower , upper = _sample_rand_range (parent_sampled , parent_sample_rate )
198+ sample_rand = _generate_sample_rand (lower , upper )
199+
159200 # Explicit sampled value provided at start_span
160201 custom_sampled = cast (
161202 "Optional[bool]" , attributes .get (SentrySpanAttribute .CUSTOM_SAMPLED )
@@ -165,11 +206,17 @@ def should_sample(
165206 sample_rate = float (custom_sampled )
166207 if sample_rate > 0 :
167208 return sampled_result (
168- parent_span_context , attributes , sample_rate = sample_rate
209+ parent_span_context ,
210+ attributes ,
211+ sample_rate = sample_rate ,
212+ sample_rand = sample_rand ,
169213 )
170214 else :
171215 return dropped_result (
172- parent_span_context , attributes , sample_rate = sample_rate
216+ parent_span_context ,
217+ attributes ,
218+ sample_rate = sample_rate ,
219+ sample_rand = sample_rand ,
173220 )
174221 else :
175222 logger .debug (
@@ -190,8 +237,6 @@ def should_sample(
190237 sample_rate_to_propagate = sample_rate
191238 else :
192239 # Check if there is a parent with a sampling decision
193- parent_sampled = get_parent_sampled (parent_span_context , trace_id )
194- parent_sample_rate = get_parent_sample_rate (parent_span_context , trace_id )
195240 if parent_sampled is not None :
196241 sample_rate = bool (parent_sampled )
197242 sample_rate_to_propagate = (
@@ -207,7 +252,9 @@ def should_sample(
207252 logger .warning (
208253 f"[Tracing] Discarding { name } because of invalid sample rate."
209254 )
210- return dropped_result (parent_span_context , attributes )
255+ return dropped_result (
256+ parent_span_context , attributes , sample_rand = sample_rand
257+ )
211258
212259 # Down-sample in case of back pressure monitor says so
213260 if is_root_span and client .monitor :
@@ -221,11 +268,17 @@ def should_sample(
221268
222269 if sampled :
223270 return sampled_result (
224- parent_span_context , attributes , sample_rate = sample_rate_to_propagate
271+ parent_span_context ,
272+ attributes ,
273+ sample_rate = sample_rate_to_propagate ,
274+ sample_rand = sample_rand ,
225275 )
226276 else :
227277 return dropped_result (
228- parent_span_context , attributes , sample_rate = sample_rate_to_propagate
278+ parent_span_context ,
279+ attributes ,
280+ sample_rate = sample_rate_to_propagate ,
281+ sample_rand = sample_rand ,
229282 )
230283
231284 def get_description (self ) -> str :
0 commit comments