Skip to content

Commit dafc847

Browse files
committed
Enhance sampling logic to record discarded transactions
1 parent 7e7718f commit dafc847

File tree

3 files changed

+125
-21
lines changed

3 files changed

+125
-21
lines changed

lib/sentry/opentelemetry/sampler.ex

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ if Code.ensure_loaded?(:otel_sampler) do
33
@moduledoc false
44

55
alias OpenTelemetry.{Span, Tracer}
6+
alias Sentry.{ClientReport, Transaction}
67

78
@behaviour :otel_sampler
89

@@ -30,19 +31,30 @@ if Code.ensure_loaded?(:otel_sampler) do
3031
_attributes,
3132
config
3233
) do
33-
if span_name in config[:drop] do
34-
{:drop, [], []}
35-
else
36-
sample_rate = Sentry.Config.traces_sample_rate()
37-
38-
case get_parent_sampling_decision(ctx, trace_id) do
39-
{:inherit, parent_sampled, tracestate} ->
40-
decision = if parent_sampled, do: :record_and_sample, else: :drop
41-
{decision, [], tracestate}
42-
43-
:no_parent ->
44-
make_sampling_decision(sample_rate)
34+
result =
35+
if span_name in config[:drop] do
36+
{:drop, [], []}
37+
else
38+
sample_rate = Sentry.Config.traces_sample_rate()
39+
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
43+
44+
{decision, [], tracestate}
45+
46+
:no_parent ->
47+
make_sampling_decision(sample_rate)
48+
end
4549
end
50+
51+
case result do
52+
{:drop, _, _} ->
53+
record_discarded_transaction(ctx)
54+
result
55+
56+
_ ->
57+
result
4658
end
4759
end
4860

@@ -107,5 +119,22 @@ if Code.ensure_loaded?(:otel_sampler) do
107119
nil -> nil
108120
end
109121
end
122+
123+
defp record_discarded_transaction(ctx) do
124+
span_id =
125+
case Tracer.current_span_ctx(ctx) do
126+
:undefined -> Sentry.UUID.uuid4_hex()
127+
span_ctx -> Span.span_id(span_ctx)
128+
end
129+
130+
transaction =
131+
Transaction.new(%{
132+
span_id: span_id,
133+
start_timestamp: System.system_time(:second),
134+
timestamp: System.system_time(:second)
135+
})
136+
137+
ClientReport.Sender.record_discarded_events(:sample_rate, [transaction])
138+
end
110139
end
111140
end

test/sentry/opentelemetry/sampler_test.exs

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@ defmodule Sentry.Opentelemetry.SamplerTest do
22
use Sentry.Case, async: true
33

44
alias Sentry.OpenTelemetry.Sampler
5+
alias Sentry.ClientReport
6+
7+
defp create_test_span_context(span_id \\ 123_456_789) do
8+
{
9+
:span_ctx,
10+
12_345_678_901_234_567_890_123_456_789_012,
11+
span_id,
12+
1,
13+
[],
14+
true,
15+
false,
16+
true,
17+
nil
18+
}
19+
end
520

621
setup do
722
original_rate = Sentry.Config.traces_sample_rate()
@@ -14,18 +29,29 @@ defmodule Sentry.Opentelemetry.SamplerTest do
1429
end
1530

1631
describe "span name dropping" do
17-
test "drops spans with the given name" do
32+
test "drops spans with the given name and records discarded event" do
33+
:sys.replace_state(ClientReport.Sender, fn _ -> %{} end)
34+
35+
test_ctx = create_test_span_context()
36+
1837
assert {:drop, [], []} =
19-
Sampler.should_sample(nil, nil, nil, "Elixir.Oban.Stager process", nil, nil,
38+
Sampler.should_sample(test_ctx, nil, nil, "Elixir.Oban.Stager process", nil, nil,
2039
drop: ["Elixir.Oban.Stager process"]
2140
)
41+
42+
Process.sleep(10)
43+
44+
state = :sys.get_state(ClientReport.Sender)
45+
assert state == %{{:sample_rate, "transaction"} => 1}
2246
end
2347

2448
test "records and samples spans not in drop list" do
2549
Sentry.Config.put_config(:traces_sample_rate, 1.0)
2650

51+
test_ctx = create_test_span_context()
52+
2753
assert {:record_and_sample, [], tracestate} =
28-
Sampler.should_sample(nil, 123, nil, "Elixir.Oban.Worker process", nil, nil,
54+
Sampler.should_sample(test_ctx, 123, nil, "Elixir.Oban.Worker process", nil, nil,
2955
drop: []
3056
)
3157

@@ -36,21 +62,32 @@ defmodule Sentry.Opentelemetry.SamplerTest do
3662
end
3763

3864
describe "sampling based on traces_sample_rate" do
39-
test "always drops when sample rate is 0.0" do
65+
test "always drops when sample rate is 0.0 and records discarded event" do
66+
:sys.replace_state(ClientReport.Sender, fn _ -> %{} end)
67+
4068
Sentry.Config.put_config(:traces_sample_rate, 0.0)
4169

70+
test_ctx = create_test_span_context()
71+
4272
assert {:drop, [], tracestate} =
43-
Sampler.should_sample(nil, 123, nil, "test span", nil, nil, drop: [])
73+
Sampler.should_sample(test_ctx, 123, nil, "test span", nil, nil, drop: [])
4474

4575
assert {"sentry-sample_rate", "0.0"} in tracestate
4676
assert {"sentry-sampled", "false"} in tracestate
77+
78+
Process.sleep(10)
79+
80+
state = :sys.get_state(ClientReport.Sender)
81+
assert state == %{{:sample_rate, "transaction"} => 1}
4782
end
4883

4984
test "always samples when sample rate is 1.0" do
5085
Sentry.Config.put_config(:traces_sample_rate, 1.0)
5186

87+
test_ctx = create_test_span_context()
88+
5289
assert {:record_and_sample, [], tracestate} =
53-
Sampler.should_sample(nil, 123, nil, "test span", nil, nil, drop: [])
90+
Sampler.should_sample(test_ctx, 123, nil, "test span", nil, nil, drop: [])
5491

5592
assert {"sentry-sample_rate", "1.0"} in tracestate
5693
assert {"sentry-sampled", "true"} in tracestate
@@ -63,8 +100,10 @@ defmodule Sentry.Opentelemetry.SamplerTest do
63100

64101
results =
65102
Enum.map(trace_ids, fn trace_id ->
103+
test_ctx = create_test_span_context()
104+
66105
{decision, [], _tracestate} =
67-
Sampler.should_sample(nil, trace_id, nil, "test span", nil, nil, drop: [])
106+
Sampler.should_sample(test_ctx, trace_id, nil, "test span", nil, nil, drop: [])
68107

69108
decision == :record_and_sample
70109
end)
@@ -73,23 +112,44 @@ defmodule Sentry.Opentelemetry.SamplerTest do
73112

74113
assert sampled_count > 30 and sampled_count < 70
75114
end
115+
116+
test "records discarded events when randomly dropped by sample rate" do
117+
:sys.replace_state(ClientReport.Sender, fn _ -> %{} end)
118+
119+
Sentry.Config.put_config(:traces_sample_rate, 0.001)
120+
121+
Enum.each(1..50, fn trace_id ->
122+
test_ctx = create_test_span_context()
123+
Sampler.should_sample(test_ctx, trace_id, nil, "test span", nil, nil, drop: [])
124+
end)
125+
126+
Process.sleep(10)
127+
128+
state = :sys.get_state(ClientReport.Sender)
129+
discarded_count = Map.get(state, {:sample_rate, "transaction"}, 0)
130+
assert discarded_count > 0, "Expected some spans to be dropped and recorded"
131+
end
76132
end
77133

78134
describe "parent span inheritance" do
79135
test "inherits sampling decision from parent span with same trace_id" do
80136
Sentry.Config.put_config(:traces_sample_rate, 1.0)
81137

138+
test_ctx = create_test_span_context()
139+
82140
assert {:record_and_sample, [], _tracestate} =
83-
Sampler.should_sample(nil, 123, nil, "test span", nil, nil, drop: [])
141+
Sampler.should_sample(test_ctx, 123, nil, "test span", nil, nil, drop: [])
84142
end
85143
end
86144

87145
describe "tracestate management" do
88146
test "builds tracestate with correct format" do
89147
Sentry.Config.put_config(:traces_sample_rate, 0.75)
90148

149+
test_ctx = create_test_span_context()
150+
91151
{_decision, [], tracestate} =
92-
Sampler.should_sample(nil, 123, nil, "test span", nil, nil, drop: [])
152+
Sampler.should_sample(test_ctx, 123, nil, "test span", nil, nil, drop: [])
93153

94154
assert List.keyfind(tracestate, "sentry-sample_rate", 0)
95155
assert List.keyfind(tracestate, "sentry-sample_rand", 0)

test/sentry/opentelemetry/span_processor_test.exs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,18 @@ defmodule Sentry.Opentelemetry.SpanProcessorTest do
136136
test "drops entire trace when root span is not sampled" do
137137
put_test_config(environment_name: "test", traces_sample_rate: 0.0)
138138

139+
original_sampler = Application.get_env(:opentelemetry, :sampler)
140+
Application.put_env(:opentelemetry, :sampler, {Sentry.OpenTelemetry.Sampler, [drop: []]})
141+
139142
Sentry.Test.start_collecting_sentry_reports()
140143

141144
Enum.each(1..5, fn _ ->
142145
TestEndpoint.instrumented_function()
143146
end)
144147

145148
assert [] = Sentry.Test.pop_sentry_transactions()
149+
150+
Application.put_env(:opentelemetry, :sampler, original_sampler)
146151
end
147152

148153
@tag span_storage: true
@@ -165,6 +170,9 @@ defmodule Sentry.Opentelemetry.SpanProcessorTest do
165170
test "child spans inherit parent sampling decision" do
166171
put_test_config(environment_name: "test", traces_sample_rate: 0.5)
167172

173+
original_sampler = Application.get_env(:opentelemetry, :sampler)
174+
Application.put_env(:opentelemetry, :sampler, {Sentry.OpenTelemetry.Sampler, [drop: []]})
175+
168176
Sentry.Test.start_collecting_sentry_reports()
169177

170178
Enum.each(1..10, fn _ ->
@@ -180,6 +188,8 @@ defmodule Sentry.Opentelemetry.SpanProcessorTest do
180188
assert transaction.contexts.trace.trace_id == child_span_one.trace_id
181189
assert transaction.contexts.trace.trace_id == child_span_two.trace_id
182190
end)
191+
192+
Application.put_env(:opentelemetry, :sampler, original_sampler)
183193
end
184194

185195
@tag span_storage: true
@@ -239,6 +249,9 @@ defmodule Sentry.Opentelemetry.SpanProcessorTest do
239249
test "concurrent traces maintain independent sampling decisions" do
240250
put_test_config(environment_name: "test", traces_sample_rate: 0.5)
241251

252+
original_sampler = Application.get_env(:opentelemetry, :sampler)
253+
Application.put_env(:opentelemetry, :sampler, {Sentry.OpenTelemetry.Sampler, [drop: []]})
254+
242255
Sentry.Test.start_collecting_sentry_reports()
243256

244257
tasks =
@@ -266,6 +279,8 @@ defmodule Sentry.Opentelemetry.SpanProcessorTest do
266279

267280
assert length(transactions) >= 5
268281
assert length(transactions) <= 20
282+
283+
Application.put_env(:opentelemetry, :sampler, original_sampler)
269284
end
270285

271286
@tag span_storage: true

0 commit comments

Comments
 (0)