Skip to content

Commit c379df1

Browse files
committed
Clarify trace-level sampling decision-making
Previously it incorrectly referred to "parent" where in reality we're dealing with spans from the same trace and respect sampling decisions that were already made for a given trace. This is not the same thing as parent/child spans that we're handling in the SpanProcessor as this type of relationship is established post-sampling.
1 parent dafc847 commit c379df1

File tree

2 files changed

+157
-23
lines changed

2 files changed

+157
-23
lines changed

lib/sentry/opentelemetry/sampler.ex

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ if Code.ensure_loaded?(:otel_sampler) do
2424
@impl true
2525
def should_sample(
2626
ctx,
27-
trace_id,
27+
_trace_id,
2828
_links,
2929
span_name,
3030
_span_kind,
@@ -37,13 +37,13 @@ if Code.ensure_loaded?(:otel_sampler) do
3737
else
3838
sample_rate = Sentry.Config.traces_sample_rate()
3939

40-
case get_parent_sampling_decision(ctx, trace_id) do
41-
{:inherit, parent_sampled, tracestate} ->
42-
decision = if parent_sampled, do: :record_and_sample, else: :drop
40+
case get_trace_sampling_decision(ctx) do
41+
{:inherit, trace_sampled, tracestate} ->
42+
decision = if trace_sampled, do: :record_and_sample, else: :drop
4343

4444
{decision, [], tracestate}
4545

46-
:no_parent ->
46+
:no_trace ->
4747
make_sampling_decision(sample_rate)
4848
end
4949
end
@@ -58,25 +58,24 @@ if Code.ensure_loaded?(:otel_sampler) do
5858
end
5959
end
6060

61-
defp get_parent_sampling_decision(ctx, trace_id) do
61+
defp get_trace_sampling_decision(ctx) do
6262
case Tracer.current_span_ctx(ctx) do
6363
:undefined ->
64-
:no_parent
64+
:no_trace
6565

6666
span_ctx ->
67-
parent_trace_id = Span.trace_id(span_ctx)
68-
69-
if parent_trace_id == trace_id do
70-
tracestate = Span.tracestate(span_ctx)
71-
parent_sampled = get_tracestate_value(tracestate, @sentry_sampled_key)
72-
73-
case parent_sampled do
74-
"true" -> {:inherit, true, tracestate}
75-
"false" -> {:inherit, false, tracestate}
76-
nil -> :no_parent
77-
end
78-
else
79-
:no_parent
67+
tracestate = Span.tracestate(span_ctx)
68+
trace_sampled = get_tracestate_value(tracestate, @sentry_sampled_key)
69+
70+
case trace_sampled do
71+
"true" ->
72+
{:inherit, true, tracestate}
73+
74+
"false" ->
75+
{:inherit, false, tracestate}
76+
77+
nil ->
78+
:no_trace
8079
end
8180
end
8281
end

test/sentry/opentelemetry/sampler_test.exs

Lines changed: 138 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,149 @@ defmodule Sentry.Opentelemetry.SamplerTest do
131131
end
132132
end
133133

134-
describe "parent span inheritance" do
135-
test "inherits sampling decision from parent span with same trace_id" do
134+
describe "trace-level sampling consistency" do
135+
defp create_span_context_with_tracestate(trace_id, tracestate) do
136+
{
137+
:span_ctx,
138+
trace_id,
139+
123_456_789,
140+
1,
141+
tracestate,
142+
true,
143+
false,
144+
true,
145+
nil
146+
}
147+
end
148+
149+
test "all spans in trace inherit sampling decision to drop when trace was not sampled" do
150+
:sys.replace_state(ClientReport.Sender, fn _ -> %{} end)
151+
152+
trace_id = 12_345_678_901_234_567_890_123_456_789_012
153+
154+
trace_tracestate = [
155+
{"sentry-sample_rate", "1.0"},
156+
{"sentry-sample_rand", "0.5"},
157+
{"sentry-sampled", "false"}
158+
]
159+
160+
existing_span_ctx = create_span_context_with_tracestate(trace_id, trace_tracestate)
161+
162+
ctx = :otel_ctx.new()
163+
ctx_with_span = :otel_tracer.set_current_span(ctx, existing_span_ctx)
164+
token = :otel_ctx.attach(ctx_with_span)
165+
166+
try do
167+
result =
168+
Sampler.should_sample(ctx_with_span, trace_id, nil, "new span in trace", nil, nil,
169+
drop: []
170+
)
171+
172+
assert {:drop, [], returned_tracestate} = result
173+
assert returned_tracestate == trace_tracestate
174+
175+
Process.sleep(10)
176+
177+
state = :sys.get_state(ClientReport.Sender)
178+
assert state == %{{:sample_rate, "transaction"} => 1}
179+
after
180+
:otel_ctx.detach(token)
181+
end
182+
end
183+
184+
test "all spans in trace inherit sampling decision to sample when trace was sampled" do
185+
trace_id = 12_345_678_901_234_567_890_123_456_789_012
186+
187+
# Simulate existing trace context with sample decision
188+
trace_tracestate = [
189+
{"sentry-sample_rate", "1.0"},
190+
{"sentry-sample_rand", "0.5"},
191+
{"sentry-sampled", "true"}
192+
]
193+
194+
existing_span_ctx = create_span_context_with_tracestate(trace_id, trace_tracestate)
195+
196+
ctx = :otel_ctx.new()
197+
ctx_with_span = :otel_tracer.set_current_span(ctx, existing_span_ctx)
198+
token = :otel_ctx.attach(ctx_with_span)
199+
200+
try do
201+
result =
202+
Sampler.should_sample(ctx_with_span, trace_id, nil, "new span in trace", nil, nil,
203+
drop: []
204+
)
205+
206+
assert {:record_and_sample, [], returned_tracestate} = result
207+
assert returned_tracestate == trace_tracestate
208+
after
209+
:otel_ctx.detach(token)
210+
end
211+
end
212+
213+
test "makes new sampling decision when no existing trace context" do
136214
Sentry.Config.put_config(:traces_sample_rate, 1.0)
137215

138216
test_ctx = create_test_span_context()
139217

140218
assert {:record_and_sample, [], _tracestate} =
141-
Sampler.should_sample(test_ctx, 123, nil, "test span", nil, nil, drop: [])
219+
Sampler.should_sample(test_ctx, 123, nil, "root span", nil, nil, drop: [])
220+
end
221+
222+
test "makes new sampling decision when tracestate has no sentry sampling info" do
223+
trace_id = 12_345_678_901_234_567_890_123_456_789_012
224+
225+
non_sentry_tracestate = [
226+
{"other-system", "some-value"}
227+
]
228+
229+
existing_span_ctx = create_span_context_with_tracestate(trace_id, non_sentry_tracestate)
230+
231+
ctx = :otel_ctx.new()
232+
ctx_with_span = :otel_tracer.set_current_span(ctx, existing_span_ctx)
233+
token = :otel_ctx.attach(ctx_with_span)
234+
235+
try do
236+
Sentry.Config.put_config(:traces_sample_rate, 1.0)
237+
238+
result =
239+
Sampler.should_sample(ctx_with_span, trace_id, nil, "span in external trace", nil, nil,
240+
drop: []
241+
)
242+
243+
assert {:record_and_sample, [], new_tracestate} = result
244+
assert {"sentry-sampled", "true"} in new_tracestate
245+
after
246+
:otel_ctx.detach(token)
247+
end
248+
end
249+
250+
test "trace_id parameter is now irrelevant for inheritance decisions" do
251+
trace_id = 12_345_678_901_234_567_890_123_456_789_012
252+
different_trace_id = 98_765_432_109_876_543_210_987_654_321_098
253+
254+
trace_tracestate = [
255+
{"sentry-sample_rate", "1.0"},
256+
{"sentry-sample_rand", "0.5"},
257+
{"sentry-sampled", "false"}
258+
]
259+
260+
existing_span_ctx = create_span_context_with_tracestate(trace_id, trace_tracestate)
261+
262+
ctx = :otel_ctx.new()
263+
ctx_with_span = :otel_tracer.set_current_span(ctx, existing_span_ctx)
264+
token = :otel_ctx.attach(ctx_with_span)
265+
266+
try do
267+
result =
268+
Sampler.should_sample(ctx_with_span, different_trace_id, nil, "span", nil, nil,
269+
drop: []
270+
)
271+
272+
assert {:drop, [], returned_tracestate} = result
273+
assert returned_tracestate == trace_tracestate
274+
after
275+
:otel_ctx.detach(token)
276+
end
142277
end
143278
end
144279

0 commit comments

Comments
 (0)