Skip to content

Commit 426cacd

Browse files
committed
feat: support for transactions in Telemetry Processor
1 parent b18c7b4 commit 426cacd

8 files changed

Lines changed: 169 additions & 27 deletions

File tree

lib/sentry/client.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,12 @@ defmodule Sentry.Client do
271271
{:ok, ""}
272272

273273
:not_collecting ->
274-
:ok = Transport.Sender.send_async(client, transaction)
274+
if Config.telemetry_processor_category?(:transaction) do
275+
:ok = TelemetryProcessor.add(transaction)
276+
else
277+
:ok = Transport.Sender.send_async(client, transaction)
278+
end
279+
275280
{:ok, ""}
276281
end
277282
end

lib/sentry/config.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,24 +495,24 @@ defmodule Sentry.Config do
495495
"""
496496
],
497497
telemetry_buffer_capacities: [
498-
type: {:map, {:in, [:error, :check_in, :log]}, :pos_integer},
498+
type: {:map, {:in, [:error, :check_in, :transaction, :log]}, :pos_integer},
499499
default: %{},
500500
type_doc: "`%{category => pos_integer()}`",
501501
doc: """
502502
Overrides for the maximum number of items each telemetry buffer can hold.
503503
When a buffer reaches capacity, oldest items are dropped to make room.
504-
Default: error=100, check_in=100, log=1000.
504+
Default: error=100, check_in=100, transaction=1000, log=1000.
505505
*Available since v12.0.0*.
506506
"""
507507
],
508508
telemetry_scheduler_weights: [
509-
type: {:map, {:in, [:critical, :high, :low]}, :pos_integer},
509+
type: {:map, {:in, [:critical, :high, :medium, :low]}, :pos_integer},
510510
default: %{},
511511
type_doc: "`%{priority => pos_integer()}`",
512512
doc: """
513513
Overrides for the weighted round-robin scheduler priority weights.
514514
Higher weights mean more sending slots for that priority level.
515-
Default: critical=5, high=4, low=2.
515+
Default: critical=5, high=4, medium=3, low=2.
516516
*Available since v12.0.0*.
517517
"""
518518
],

lib/sentry/telemetry/category.ex

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,24 @@ defmodule Sentry.Telemetry.Category do
99
1010
* `:error` - Error events (critical priority)
1111
* `:check_in` - Cron check-ins (high priority)
12+
* `:transaction` - Performance transactions (medium priority)
1213
* `:log` - Log entries (low priority)
1314
1415
## Priorities and Weights
1516
1617
* `:critical` - weight 5 (errors)
1718
* `:high` - weight 4 (check-ins)
19+
* `:medium` - weight 3 (transactions)
1820
* `:low` - weight 2 (logs)
1921
2022
"""
2123
@moduledoc since: "12.0.0"
2224

2325
@typedoc "Telemetry category types."
24-
@type t :: :error | :check_in | :log
26+
@type t :: :error | :check_in | :transaction | :log
2527

2628
@typedoc "Priority levels for categories."
27-
@type priority :: :critical | :high | :low
29+
@type priority :: :critical | :high | :medium | :low
2830

2931
@typedoc "Buffer configuration for a category."
3032
@type config :: %{
@@ -33,18 +35,20 @@ defmodule Sentry.Telemetry.Category do
3335
timeout: pos_integer() | nil
3436
}
3537

36-
@priorities [:critical, :high, :low]
37-
@categories [:error, :check_in, :log]
38+
@priorities [:critical, :high, :medium, :low]
39+
@categories [:error, :check_in, :transaction, :log]
3840

3941
@weights %{
4042
critical: 5,
4143
high: 4,
44+
medium: 3,
4245
low: 2
4346
}
4447

4548
@default_configs %{
4649
error: %{capacity: 100, batch_size: 1, timeout: nil},
4750
check_in: %{capacity: 100, batch_size: 1, timeout: nil},
51+
transaction: %{capacity: 1000, batch_size: 1, timeout: nil},
4852
log: %{capacity: 1000, batch_size: 100, timeout: 5000}
4953
}
5054

@@ -59,13 +63,17 @@ defmodule Sentry.Telemetry.Category do
5963
iex> Sentry.Telemetry.Category.priority(:check_in)
6064
:high
6165
66+
iex> Sentry.Telemetry.Category.priority(:transaction)
67+
:medium
68+
6269
iex> Sentry.Telemetry.Category.priority(:log)
6370
:low
6471
6572
"""
6673
@spec priority(t()) :: priority()
6774
def priority(:error), do: :critical
6875
def priority(:check_in), do: :high
76+
def priority(:transaction), do: :medium
6977
def priority(:log), do: :low
7078

7179
@doc """
@@ -78,6 +86,9 @@ defmodule Sentry.Telemetry.Category do
7886
iex> Sentry.Telemetry.Category.weight(:high)
7987
4
8088
89+
iex> Sentry.Telemetry.Category.weight(:medium)
90+
3
91+
8192
iex> Sentry.Telemetry.Category.weight(:low)
8293
2
8394
@@ -104,6 +115,9 @@ defmodule Sentry.Telemetry.Category do
104115
iex> Sentry.Telemetry.Category.default_config(:check_in)
105116
%{capacity: 100, batch_size: 1, timeout: nil}
106117
118+
iex> Sentry.Telemetry.Category.default_config(:transaction)
119+
%{capacity: 1000, batch_size: 1, timeout: nil}
120+
107121
iex> Sentry.Telemetry.Category.default_config(:log)
108122
%{capacity: 1000, batch_size: 100, timeout: 5000}
109123
@@ -119,7 +133,7 @@ defmodule Sentry.Telemetry.Category do
119133
## Examples
120134
121135
iex> Sentry.Telemetry.Category.all()
122-
[:error, :check_in, :log]
136+
[:error, :check_in, :transaction, :log]
123137
124138
"""
125139
@spec all() :: [t()]
@@ -131,7 +145,7 @@ defmodule Sentry.Telemetry.Category do
131145
## Examples
132146
133147
iex> Sentry.Telemetry.Category.priorities()
134-
[:critical, :high, :low]
148+
[:critical, :high, :medium, :low]
135149
136150
"""
137151
@spec priorities() :: [priority()]

lib/sentry/telemetry/scheduler.ex

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule Sentry.Telemetry.Scheduler do
99
1010
* `:critical` - weight 5 (errors)
1111
* `:high` - weight 4 (check-ins)
12+
* `:medium` - weight 3 (transactions)
1213
* `:low` - weight 2 (logs)
1314
1415
## Signal-Based Wake
@@ -34,13 +35,14 @@ defmodule Sentry.Telemetry.Scheduler do
3435
alias __MODULE__
3536

3637
alias Sentry.Telemetry.{Buffer, Category}
37-
alias Sentry.{CheckIn, ClientReport, Config, Envelope, Event, LogEvent, Transport}
38+
alias Sentry.{CheckIn, ClientReport, Config, Envelope, Event, LogEvent, Transaction, Transport}
3839

3940
@default_capacity 1000
4041

4142
@type buffers :: %{
4243
error: GenServer.server(),
4344
check_in: GenServer.server(),
45+
transaction: GenServer.server(),
4446
log: GenServer.server()
4547
}
4648

@@ -79,7 +81,7 @@ defmodule Sentry.Telemetry.Scheduler do
7981
## Examples
8082
8183
iex> Sentry.Telemetry.Scheduler.build_priority_cycle()
82-
[:error, :error, :error, :error, :error, :check_in, :check_in, :check_in, :check_in, :log, :log]
84+
[:error, :error, :error, :error, :error, :check_in, :check_in, :check_in, :check_in, :transaction, :transaction, :transaction, :log, :log]
8385
8486
"""
8587
@spec build_priority_cycle(map() | nil) :: [Category.t()]
@@ -243,6 +245,10 @@ defmodule Sentry.Telemetry.Scheduler do
243245
process_and_send_check_in(state, check_in, &send_envelope/2)
244246
end
245247

248+
defp send_items(state, :transaction, [%Transaction{} = transaction]) do
249+
process_and_send_transaction(state, transaction, &send_envelope/2)
250+
end
251+
246252
defp send_items(state, :log, log_events) do
247253
process_and_send_logs(state, log_events, &send_envelope/2)
248254
end
@@ -263,6 +269,11 @@ defmodule Sentry.Telemetry.Scheduler do
263269
process_and_send_check_in(state, check_in, &send_envelope_direct/2)
264270
end)
265271

272+
:transaction ->
273+
Enum.each(items, fn transaction ->
274+
process_and_send_transaction(state, transaction, &send_envelope_direct/2)
275+
end)
276+
266277
:log ->
267278
process_and_send_logs(state, items, &send_envelope_direct/2)
268279
end
@@ -294,6 +305,27 @@ defmodule Sentry.Telemetry.Scheduler do
294305
send_fn.(state, envelope)
295306
end
296307

308+
defp process_and_send_transaction(
309+
%{on_envelope: on_envelope} = state,
310+
%Transaction{} = transaction,
311+
send_fn
312+
) do
313+
# Skip test collection when on_envelope is set (used by unit tests)
314+
if is_nil(on_envelope) do
315+
case Sentry.Test.maybe_collect(transaction) do
316+
:collected ->
317+
state
318+
319+
:not_collecting ->
320+
envelope = Envelope.from_transaction(transaction)
321+
send_fn.(state, envelope)
322+
end
323+
else
324+
envelope = Envelope.from_transaction(transaction)
325+
send_fn.(state, envelope)
326+
end
327+
end
328+
297329
defp process_and_send_logs(%{on_envelope: on_envelope} = state, log_events, send_fn) do
298330
processed_logs = apply_before_send_log_callbacks(log_events)
299331

@@ -468,6 +500,7 @@ defmodule Sentry.Telemetry.Scheduler do
468500
%{
469501
critical: Category.weight(:critical),
470502
high: Category.weight(:high),
503+
medium: Category.weight(:medium),
471504
low: Category.weight(:low)
472505
}
473506
end
@@ -476,6 +509,7 @@ defmodule Sentry.Telemetry.Scheduler do
476509
[
477510
{:error, :critical},
478511
{:check_in, :high},
512+
{:transaction, :medium},
479513
{:log, :low}
480514
]
481515
end

lib/sentry/telemetry_processor.ex

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ defmodule Sentry.TelemetryProcessor do
1212
1313
* Error Buffer - for error events (critical priority)
1414
* Check-in Buffer - for cron check-ins (high priority)
15+
* Transaction Buffer - for performance transactions (medium priority)
1516
* Log Buffer - for log entries (low priority)
1617
* Scheduler - weighted round-robin scheduler with integrated transport queue
1718
@@ -23,6 +24,9 @@ defmodule Sentry.TelemetryProcessor do
2324
# Add check-ins to the buffer
2425
TelemetryProcessor.add(processor, %Sentry.CheckIn{...})
2526
27+
# Add transactions to the buffer
28+
TelemetryProcessor.add(processor, %Sentry.Transaction{...})
29+
2630
# Add log events to the buffer
2731
TelemetryProcessor.add(processor, %Sentry.LogEvent{...})
2832
@@ -35,7 +39,7 @@ defmodule Sentry.TelemetryProcessor do
3539
use Supervisor
3640

3741
alias Sentry.Telemetry.{Buffer, Category, Scheduler}
38-
alias Sentry.{CheckIn, Event, LogEvent}
42+
alias Sentry.{CheckIn, Event, LogEvent, Transaction}
3943

4044
@default_name __MODULE__
4145

@@ -91,7 +95,7 @@ defmodule Sentry.TelemetryProcessor do
9195
9296
Returns `:ok`.
9397
"""
94-
@spec add(Event.t() | CheckIn.t() | LogEvent.t()) :: :ok
98+
@spec add(Event.t() | CheckIn.t() | Transaction.t() | LogEvent.t()) :: :ok
9599
def add(%Event{} = item) do
96100
add(processor_name(), item)
97101
end
@@ -100,6 +104,10 @@ defmodule Sentry.TelemetryProcessor do
100104
add(processor_name(), item)
101105
end
102106

107+
def add(%Transaction{} = item) do
108+
add(processor_name(), item)
109+
end
110+
103111
def add(%LogEvent{} = item) do
104112
add(processor_name(), item)
105113
end
@@ -111,7 +119,8 @@ defmodule Sentry.TelemetryProcessor do
111119
112120
Returns `:ok`.
113121
"""
114-
@spec add(Supervisor.supervisor(), Event.t() | CheckIn.t() | LogEvent.t()) :: :ok
122+
@spec add(Supervisor.supervisor(), Event.t() | CheckIn.t() | Transaction.t() | LogEvent.t()) ::
123+
:ok
115124
def add(processor, %Event{} = item) when is_atom(processor) do
116125
Buffer.add(buffer_name(processor, :error), item)
117126
Scheduler.signal(scheduler_name(processor))
@@ -140,6 +149,20 @@ defmodule Sentry.TelemetryProcessor do
140149
:ok
141150
end
142151

152+
def add(processor, %Transaction{} = item) when is_atom(processor) do
153+
Buffer.add(buffer_name(processor, :transaction), item)
154+
Scheduler.signal(scheduler_name(processor))
155+
:ok
156+
end
157+
158+
def add(processor, %Transaction{} = item) do
159+
buffer = get_buffer(processor, :transaction)
160+
Buffer.add(buffer, item)
161+
scheduler = get_scheduler(processor)
162+
Scheduler.signal(scheduler)
163+
:ok
164+
end
165+
143166
def add(processor, %LogEvent{} = item) when is_atom(processor) do
144167
Buffer.add(buffer_name(processor, :log), item)
145168
Scheduler.signal(scheduler_name(processor))
@@ -187,7 +210,7 @@ defmodule Sentry.TelemetryProcessor do
187210
Returns the buffer pid for a given category.
188211
"""
189212
@spec get_buffer(Supervisor.supervisor(), Category.t()) :: pid()
190-
def get_buffer(processor, category) when category in [:error, :check_in, :log] do
213+
def get_buffer(processor, category) when category in [:error, :check_in, :transaction, :log] do
191214
children = Supervisor.which_children(processor)
192215
buffer_id = buffer_id(category)
193216

@@ -217,7 +240,7 @@ defmodule Sentry.TelemetryProcessor do
217240
Returns 0 if the processor is not running.
218241
"""
219242
@spec buffer_size(Category.t()) :: non_neg_integer()
220-
def buffer_size(category) when category in [:error, :check_in, :log] do
243+
def buffer_size(category) when category in [:error, :check_in, :transaction, :log] do
221244
buffer_size(processor_name(), category)
222245
end
223246

@@ -227,7 +250,7 @@ defmodule Sentry.TelemetryProcessor do
227250
Returns 0 if the processor is not running.
228251
"""
229252
@spec buffer_size(Supervisor.supervisor(), Category.t()) :: non_neg_integer()
230-
def buffer_size(processor, category) when category in [:error, :check_in, :log] do
253+
def buffer_size(processor, category) when category in [:error, :check_in, :transaction, :log] do
231254
case safe_get_buffer(processor, category) do
232255
{:ok, buffer} -> Buffer.size(buffer)
233256
:error -> 0
@@ -290,6 +313,7 @@ defmodule Sentry.TelemetryProcessor do
290313
buffers: %{
291314
error: Map.fetch!(buffer_names, :error),
292315
check_in: Map.fetch!(buffer_names, :check_in),
316+
transaction: Map.fetch!(buffer_names, :transaction),
293317
log: Map.fetch!(buffer_names, :log)
294318
},
295319
name: scheduler_name(processor_name),
@@ -310,6 +334,7 @@ defmodule Sentry.TelemetryProcessor do
310334

311335
defp buffer_id(:error), do: :error_buffer
312336
defp buffer_id(:check_in), do: :check_in_buffer
337+
defp buffer_id(:transaction), do: :transaction_buffer
313338
defp buffer_id(:log), do: :log_buffer
314339

315340
@doc false

test/sentry/telemetry/scheduler_test.exs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,17 @@ defmodule Sentry.Telemetry.SchedulerTest do
1919
test "builds cycle with correct weights for all categories" do
2020
cycle = Scheduler.build_priority_cycle()
2121

22-
# Default weights: critical=5, high=4, low=2
23-
assert length(cycle) == 11
24-
assert Enum.frequencies(cycle) == %{error: 5, check_in: 4, log: 2}
22+
# Default weights: critical=5, high=4, medium=3, low=2
23+
assert length(cycle) == 14
24+
assert Enum.frequencies(cycle) == %{error: 5, check_in: 4, transaction: 3, log: 2}
2525
end
2626

2727
test "builds cycle with custom weights" do
2828
custom_weights = %{low: 5}
2929
cycle = Scheduler.build_priority_cycle(custom_weights)
3030

31-
assert length(cycle) == 14
32-
assert Enum.frequencies(cycle) == %{error: 5, check_in: 4, log: 5}
31+
assert length(cycle) == 17
32+
assert Enum.frequencies(cycle) == %{error: 5, check_in: 4, transaction: 3, log: 5}
3333
end
3434
end
3535

0 commit comments

Comments
 (0)