Skip to content

Commit a7f1b52

Browse files
committed
Read or generate sample_rand
1 parent 6f05951 commit a7f1b52

File tree

3 files changed

+70
-17
lines changed

3 files changed

+70
-17
lines changed

sentry_sdk/integrations/opentelemetry/consts.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
SENTRY_USE_CURRENT_SCOPE_KEY = create_key("sentry_use_current_scope")
1313
SENTRY_USE_ISOLATION_SCOPE_KEY = create_key("sentry_use_isolation_scope")
1414

15+
# trace state keys
1516
TRACESTATE_SAMPLED_KEY = Baggage.SENTRY_PREFIX + "sampled"
1617
TRACESTATE_SAMPLE_RATE_KEY = Baggage.SENTRY_PREFIX + "sample_rate"
18+
TRACESTATE_SAMPLE_RAND_KEY = Baggage.SENTRY_PREFIX + "sample_rand"
1719

20+
# misc
1821
OTEL_SENTRY_CONTEXT = "otel"
1922
SPAN_ORIGIN = "auto.otel"
2023

sentry_sdk/integrations/opentelemetry/sampler.py

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import random
2+
from decimal import Decimal
23
from typing import cast
34

45
from opentelemetry import trace
56
from opentelemetry.sdk.trace.sampling import Sampler, SamplingResult, Decision
67
from opentelemetry.trace.span import TraceState
78

89
import 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+
)
1015
from sentry_sdk.utils import is_valid_sample_rate, logger
1116
from 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:

sentry_sdk/tracing_utils.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import contextlib
2+
import decimal
23
import inspect
34
import os
45
import re
@@ -682,13 +683,12 @@ def get_current_span(scope=None):
682683
return current_span
683684

684685

685-
# XXX-potel-ivana: use this
686686
def _generate_sample_rand(
687687
trace_id, # type: Optional[str]
688688
*,
689689
interval=(0.0, 1.0), # type: tuple[float, float]
690690
):
691-
# type: (...) -> Any
691+
# type: (...) -> decimal.Decimal
692692
"""Generate a sample_rand value from a trace ID.
693693
694694
The generated value will be pseudorandomly chosen from the provided
@@ -698,8 +698,6 @@ def _generate_sample_rand(
698698
699699
The pseudorandom number generator is seeded with the trace ID.
700700
"""
701-
import decimal
702-
703701
lower, upper = interval
704702
if not lower < upper: # using `if lower >= upper` would handle NaNs incorrectly
705703
raise ValueError("Invalid interval: lower must be less than upper")
@@ -715,7 +713,6 @@ def _generate_sample_rand(
715713
)
716714

717715

718-
# XXX-potel-ivana: use this
719716
def _sample_rand_range(parent_sampled, sample_rate):
720717
# type: (Optional[bool], Optional[float]) -> tuple[float, float]
721718
"""

0 commit comments

Comments
 (0)