Skip to content

Commit 530fa4f

Browse files
authored
Merge branch 'potel-base' into ivana/custom-sampling-context-integrations
2 parents 6b063cf + ff7e134 commit 530fa4f

File tree

4 files changed

+70
-52
lines changed

4 files changed

+70
-52
lines changed

MIGRATION_GUIDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
1010

1111
- The SDK now supports Python 3.7 and higher.
1212
- `sentry_sdk.start_span` now only takes keyword arguments.
13-
- `sentry_sdk.start_span` no longer takes an explicit `span` argument.
13+
- `sentry_sdk.start_transaction`/`sentry_sdk.start_span` no longer takes the following arguments: `span`, `parent_sampled`, `trace_id`, `span_id` or `parent_span_id`.
1414
- The `Span()` constructor does not accept a `hub` parameter anymore.
1515
- `Span.finish()` does not accept a `hub` parameter anymore.
1616
- The `Profile()` constructor does not accept a `hub` parameter anymore.

sentry_sdk/integrations/opentelemetry/sampler.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,20 +120,29 @@ def should_sample(
120120
if not has_tracing_enabled(client.options):
121121
return dropped_result(parent_span_context, attributes)
122122

123+
# parent_span_context.is_valid means this span has a parent, remote or local
124+
is_root_span = not parent_span_context.is_valid or parent_span_context.is_remote
125+
123126
# Explicit sampled value provided at start_span
124127
if attributes.get(SentrySpanAttribute.CUSTOM_SAMPLED) is not None:
125-
sample_rate = float(attributes[SentrySpanAttribute.CUSTOM_SAMPLED])
126-
if sample_rate > 0:
127-
return sampled_result(parent_span_context, attributes, sample_rate)
128+
if is_root_span:
129+
sample_rate = float(attributes[SentrySpanAttribute.CUSTOM_SAMPLED])
130+
if sample_rate > 0:
131+
return sampled_result(parent_span_context, attributes, sample_rate)
132+
else:
133+
return dropped_result(parent_span_context, attributes)
128134
else:
129-
return dropped_result(parent_span_context, attributes)
135+
logger.debug(
136+
f"[Tracing] Ignoring sampled param for non-root span {name}"
137+
)
130138

131139
sample_rate = None
132140

133141
# Check if there is a traces_sampler
134142
# Traces_sampler is responsible to check parent sampled to have full transactions.
135143
has_traces_sampler = callable(client.options.get("traces_sampler"))
136-
if has_traces_sampler:
144+
145+
if is_root_span and has_traces_sampler:
137146
sampling_context = {
138147
"transaction_context": {
139148
"name": name,
@@ -162,8 +171,7 @@ def should_sample(
162171
return dropped_result(parent_span_context, attributes)
163172

164173
# Down-sample in case of back pressure monitor says so
165-
# TODO: this should only be done for transactions (aka root spans)
166-
if client.monitor:
174+
if is_root_span and client.monitor:
167175
sample_rate /= 2**client.monitor.downsample_factor
168176

169177
# Roll the dice on sample rate

tests/integrations/aiohttp/test_aiohttp.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -535,8 +535,6 @@ async def handler(request):
535535
with start_transaction(
536536
name="/interactions/other-dogs/new-dog",
537537
op="greeting.sniff",
538-
# make trace_id difference between transactions
539-
trace_id="0123456789012345678901234567890",
540538
) as transaction:
541539
client = await aiohttp_client(raw_server)
542540
resp = await client.get("/")
@@ -572,14 +570,21 @@ async def handler(request):
572570
with start_transaction(
573571
name="/interactions/other-dogs/new-dog",
574572
op="greeting.sniff",
575-
trace_id="0123456789012345678901234567890",
576-
):
573+
) as transaction:
577574
client = await aiohttp_client(raw_server)
578575
resp = await client.get("/", headers={"bagGage": "custom=value"})
579576

580577
assert (
581-
resp.request_info.headers["baggage"]
582-
== "custom=value,sentry-trace_id=0123456789012345678901234567890,sentry-environment=production,sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42,sentry-transaction=/interactions/other-dogs/new-dog,sentry-sample_rate=1.0,sentry-sampled=true"
578+
sorted(resp.request_info.headers["baggage"].split(","))
579+
== sorted([
580+
"custom=value",
581+
f"sentry-trace_id={transaction.trace_id}",
582+
"sentry-environment=production",
583+
"sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42",
584+
"sentry-transaction=/interactions/other-dogs/new-dog",
585+
"sentry-sample_rate=1.0",
586+
"sentry-sampled=true",
587+
])
583588
)
584589

585590

tests/tracing/test_sampling.py

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,33 @@
66

77
import sentry_sdk
88
from sentry_sdk import start_span, start_transaction, capture_exception
9-
from sentry_sdk.tracing import Transaction
109
from sentry_sdk.utils import logger
1110

1211

13-
def test_sampling_decided_only_for_transactions(sentry_init, capture_events):
12+
def test_sampling_decided_only_for_root_spans(sentry_init):
1413
sentry_init(traces_sample_rate=0.5)
1514

16-
with start_transaction(name="hi") as transaction:
17-
assert transaction.sampled is not None
15+
with start_span(name="outer1") as root_span1:
16+
assert root_span1.sampled is not None
1817

19-
with start_span() as span:
20-
assert span.sampled == transaction.sampled
18+
with start_span(name="inner") as span:
19+
assert span.sampled == root_span1.sampled
2120

22-
with start_span() as span:
23-
assert span.sampled is None
21+
with start_span(name="outer2") as root_span2:
22+
assert root_span2.sampled is not None
2423

2524

2625
@pytest.mark.parametrize("sampled", [True, False])
27-
def test_nested_transaction_sampling_override(sentry_init, sampled):
26+
def test_nested_span_sampling_override(sentry_init, sampled):
2827
sentry_init(traces_sample_rate=1.0)
2928

30-
with start_transaction(name="outer", sampled=sampled) as outer_transaction:
31-
assert outer_transaction.sampled is sampled
32-
with start_transaction(
33-
name="inner", sampled=(not sampled)
34-
) as inner_transaction:
35-
assert inner_transaction.sampled is not sampled
36-
assert outer_transaction.sampled is sampled
29+
with start_span(name="outer", sampled=sampled) as outer_span:
30+
assert outer_span.sampled is sampled
31+
with start_span(name="inner", sampled=(not sampled)) as inner_span:
32+
# won't work because the child span inherits the sampling decision
33+
# from the parent
34+
assert inner_span.sampled is sampled
35+
assert outer_span.sampled is sampled
3736

3837

3938
def test_no_double_sampling(sentry_init, capture_events):
@@ -147,10 +146,17 @@ def test_ignores_inherited_sample_decision_when_traces_sampler_defined(
147146
traces_sampler = mock.Mock(return_value=not parent_sampling_decision)
148147
sentry_init(traces_sampler=traces_sampler)
149148

150-
transaction = start_transaction(
151-
name="dogpark", parent_sampled=parent_sampling_decision
149+
sentry_trace_header = (
150+
"12312012123120121231201212312012-1121201211212012-{sampled}".format(
151+
sampled=int(parent_sampling_decision)
152+
)
152153
)
153-
assert transaction.sampled is not parent_sampling_decision
154+
155+
with sentry_sdk.continue_trace({"sentry-trace": sentry_trace_header}):
156+
with sentry_sdk.start_span(name="dogpark") as span:
157+
pass
158+
159+
assert span.sampled is not parent_sampling_decision
154160

155161

156162
@pytest.mark.parametrize("explicit_decision", [True, False])
@@ -176,39 +182,38 @@ def test_inherits_parent_sampling_decision_when_traces_sampler_undefined(
176182
sentry_init(traces_sample_rate=0.5)
177183
mock_random_value = 0.25 if parent_sampling_decision is False else 0.75
178184

179-
with mock.patch.object(random, "random", return_value=mock_random_value):
180-
transaction = start_transaction(
181-
name="dogpark", parent_sampled=parent_sampling_decision
185+
sentry_trace_header = (
186+
"12312012123120121231201212312012-1121201211212012-{sampled}".format(
187+
sampled=int(parent_sampling_decision)
182188
)
183-
assert transaction.sampled is parent_sampling_decision
189+
)
190+
with mock.patch.object(random, "random", return_value=mock_random_value):
191+
with sentry_sdk.continue_trace({"sentry-trace": sentry_trace_header}):
192+
with start_span(name="dogpark") as span:
193+
pass
194+
195+
assert span.sampled is parent_sampling_decision
184196

185197

186198
@pytest.mark.parametrize("parent_sampling_decision", [True, False])
187199
def test_passes_parent_sampling_decision_in_sampling_context(
188200
sentry_init, parent_sampling_decision
189201
):
190-
sentry_init(traces_sample_rate=1.0)
202+
def dummy_traces_sampler(sampling_context):
203+
assert sampling_context["parent_sampled"] is parent_sampling_decision
204+
return 1.0
205+
206+
sentry_init(traces_sample_rate=1.0, traces_sampler=dummy_traces_sampler)
191207

192208
sentry_trace_header = (
193209
"12312012123120121231201212312012-1121201211212012-{sampled}".format(
194210
sampled=int(parent_sampling_decision)
195211
)
196212
)
197213

198-
transaction = Transaction.continue_from_headers(
199-
headers={"sentry-trace": sentry_trace_header}, name="dogpark"
200-
)
201-
spy = mock.Mock(wraps=transaction)
202-
start_transaction(transaction=spy)
203-
204-
# there's only one call (so index at 0) and kwargs are always last in a call
205-
# tuple (so index at -1)
206-
sampling_context = spy._set_initial_sampling_decision.mock_calls[0][-1][
207-
"sampling_context"
208-
]
209-
assert "parent_sampled" in sampling_context
210-
# because we passed in a spy, attribute access requires unwrapping
211-
assert sampling_context["parent_sampled"]._mock_wraps is parent_sampling_decision
214+
with sentry_sdk.continue_trace({"sentry-trace": sentry_trace_header}):
215+
with sentry_sdk.start_span(name="dogpark"):
216+
pass
212217

213218

214219
def test_passes_attributes_from_start_span_to_traces_sampler(

0 commit comments

Comments
 (0)