Skip to content

Commit 4e4fb5e

Browse files
committed
WIP: support for error events in Telemetry Processor
1 parent 1fb8bc7 commit 4e4fb5e

8 files changed

Lines changed: 245 additions & 61 deletions

File tree

lib/sentry/client.ex

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ defmodule Sentry.Client do
1616
Interfaces,
1717
LoggerUtils,
1818
Options,
19+
TelemetryProcessor,
1920
Transaction,
2021
Transport
2122
}
@@ -227,7 +228,12 @@ defmodule Sentry.Client do
227228
{:ok, ""}
228229

229230
:not_collecting ->
230-
:ok = Transport.Sender.send_async(client, event)
231+
if Config.telemetry_processor_category?(:error) do
232+
:ok = TelemetryProcessor.add(event)
233+
else
234+
:ok = Transport.Sender.send_async(client, event)
235+
end
236+
231237
{:ok, ""}
232238
end
233239
end

lib/sentry/config.ex

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,24 @@ defmodule Sentry.Config do
394394
Use `Sentry.LogsHandler` to capture log events from Erlang's `:logger`.
395395
*Available since 12.0.0*.
396396
"""
397+
],
398+
telemetry_processor_categories: [
399+
type: {:list, {:in, [:error, :check_in, :transaction, :log]}},
400+
default: [:log],
401+
doc: """
402+
List of event categories that should be processed through the TelemetryProcessor.
403+
Categories in this list use the TelemetryProcessor's ring buffer and weighted
404+
round-robin scheduler, which provides prioritized scheduling and backpressure.
405+
Categories not in this list use the original sender-based approach.
406+
407+
Available categories:
408+
* `:error` - Error events (critical priority, batch_size=1)
409+
* `:check_in` - Cron check-ins (high priority, batch_size=1)
410+
* `:transaction` - Performance transactions (medium priority, batch_size=1)
411+
* `:log` - Log entries (low priority, batch_size=100, 5s timeout)
412+
413+
*Available since 12.0.0*.
414+
"""
397415
]
398416
]
399417

@@ -860,6 +878,13 @@ defmodule Sentry.Config do
860878
@spec transport_capacity() :: pos_integer()
861879
def transport_capacity, do: fetch!(:transport_capacity)
862880

881+
@spec telemetry_processor_categories() :: [atom()]
882+
def telemetry_processor_categories, do: fetch!(:telemetry_processor_categories)
883+
884+
@spec telemetry_processor_category?(atom()) :: boolean()
885+
def telemetry_processor_category?(category),
886+
do: category in telemetry_processor_categories()
887+
863888
@spec before_send_log() ::
864889
(Sentry.LogEvent.t() -> Sentry.LogEvent.t() | nil | false) | {module(), atom()} | nil
865890
def before_send_log, do: get(:before_send_log)

lib/sentry/telemetry/category.ex

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,24 @@ defmodule Sentry.Telemetry.Category do
55
The TelemetryProcessor uses categories to classify different types of telemetry data
66
and prioritize their sending based on a weighted round-robin scheduler.
77
8-
Currently, only the `:log` category is managed by the TelemetryProcessor.
9-
Other categories (errors, transactions, check-ins) will be added in future versions.
10-
118
## Categories
129
10+
* `:error` - Error events (critical priority)
1311
* `:log` - Log entries (low priority)
1412
1513
## Priorities and Weights
1614
15+
* `:critical` - weight 5 (errors)
1716
* `:low` - weight 2 (logs)
1817
1918
"""
2019
@moduledoc since: "12.0.0"
2120

2221
@typedoc "Telemetry category types."
23-
@type t :: :log
22+
@type t :: :error | :log
2423

2524
@typedoc "Priority levels for categories."
26-
@type priority :: :low
25+
@type priority :: :critical | :low
2726

2827
@typedoc "Buffer configuration for a category."
2928
@type config :: %{
@@ -32,14 +31,16 @@ defmodule Sentry.Telemetry.Category do
3231
timeout: pos_integer() | nil
3332
}
3433

35-
@priorities [:low]
36-
@categories [:log]
34+
@priorities [:critical, :low]
35+
@categories [:error, :log]
3736

3837
@weights %{
38+
critical: 5,
3939
low: 2
4040
}
4141

4242
@default_configs %{
43+
error: %{capacity: 100, batch_size: 1, timeout: nil},
4344
log: %{capacity: 1000, batch_size: 100, timeout: 5000}
4445
}
4546

@@ -48,11 +49,15 @@ defmodule Sentry.Telemetry.Category do
4849
4950
## Examples
5051
52+
iex> Sentry.Telemetry.Category.priority(:error)
53+
:critical
54+
5155
iex> Sentry.Telemetry.Category.priority(:log)
5256
:low
5357
5458
"""
5559
@spec priority(t()) :: priority()
60+
def priority(:error), do: :critical
5661
def priority(:log), do: :low
5762

5863
@doc """
@@ -82,6 +87,9 @@ defmodule Sentry.Telemetry.Category do
8287
8388
## Examples
8489
90+
iex> Sentry.Telemetry.Category.default_config(:error)
91+
%{capacity: 100, batch_size: 1, timeout: nil}
92+
8593
iex> Sentry.Telemetry.Category.default_config(:log)
8694
%{capacity: 1000, batch_size: 100, timeout: 5000}
8795
@@ -97,7 +105,7 @@ defmodule Sentry.Telemetry.Category do
97105
## Examples
98106
99107
iex> Sentry.Telemetry.Category.all()
100-
[:log]
108+
[:error, :log]
101109
102110
"""
103111
@spec all() :: [t()]
@@ -109,7 +117,7 @@ defmodule Sentry.Telemetry.Category do
109117
## Examples
110118
111119
iex> Sentry.Telemetry.Category.priorities()
112-
[:low]
120+
[:critical, :low]
113121
114122
"""
115123
@spec priorities() :: [priority()]

lib/sentry/telemetry/scheduler.ex

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ defmodule Sentry.Telemetry.Scheduler do
55
The scheduler cycles through category buffers based on priority weights,
66
ensuring critical telemetry gets priority over high-volume data under load.
77
8-
Currently, only the `:log` category is managed. The weighted round-robin
9-
structure is in place for future expansion to additional categories.
10-
118
## Weights
129
10+
* `:critical` - weight 5 (errors)
1311
* `:low` - weight 2 (logs)
1412
1513
## Signal-Based Wake
@@ -35,11 +33,12 @@ defmodule Sentry.Telemetry.Scheduler do
3533
alias __MODULE__
3634

3735
alias Sentry.Telemetry.{Buffer, Category}
38-
alias Sentry.{ClientReport, Config, Envelope, LogEvent, Transport}
36+
alias Sentry.{ClientReport, Config, Envelope, Event, LogEvent, Transport}
3937

4038
@default_capacity 1000
4139

4240
@type buffers :: %{
41+
error: GenServer.server(),
4342
log: GenServer.server()
4443
}
4544

@@ -78,7 +77,7 @@ defmodule Sentry.Telemetry.Scheduler do
7877
## Examples
7978
8079
iex> Sentry.Telemetry.Scheduler.build_priority_cycle()
81-
[:log, :log]
80+
[:error, :error, :error, :error, :error, :log, :log]
8281
8382
"""
8483
@spec build_priority_cycle(map() | nil) :: [Category.t()]
@@ -151,7 +150,9 @@ defmodule Sentry.Telemetry.Scheduler do
151150
on_envelope = Keyword.get(opts, :on_envelope)
152151
capacity = Keyword.get(opts, :capacity, @default_capacity)
153152

154-
priority_cycle = build_priority_cycle(weights)
153+
priority_cycle =
154+
build_priority_cycle(weights)
155+
|> Enum.filter(fn category -> Map.has_key?(buffers, category) end)
155156

156157
state = %Scheduler{
157158
buffers: buffers,
@@ -232,6 +233,10 @@ defmodule Sentry.Telemetry.Scheduler do
232233
end
233234
end
234235

236+
defp send_items(state, :error, [%Event{} = event]) do
237+
process_and_send_event(state, event, &send_envelope/2)
238+
end
239+
235240
defp send_items(state, :log, log_events) do
236241
process_and_send_logs(state, log_events, &send_envelope/2)
237242
end
@@ -242,14 +247,37 @@ defmodule Sentry.Telemetry.Scheduler do
242247

243248
if items != [] do
244249
case category do
245-
:log -> process_and_send_logs(state, items, &send_envelope_direct/2)
250+
:error ->
251+
Enum.each(items, fn event ->
252+
process_and_send_event(state, event, &send_envelope_direct/2)
253+
end)
254+
255+
:log ->
256+
process_and_send_logs(state, items, &send_envelope_direct/2)
246257
end
247258
end
248259
end
249260

250261
state
251262
end
252263

264+
defp process_and_send_event(%{on_envelope: on_envelope} = state, %Event{} = event, send_fn) do
265+
# Skip test collection when on_envelope is set (used by unit tests)
266+
if is_nil(on_envelope) do
267+
case Sentry.Test.maybe_collect(event) do
268+
:collected ->
269+
state
270+
271+
:not_collecting ->
272+
envelope = Envelope.from_event(event)
273+
send_fn.(state, envelope)
274+
end
275+
else
276+
envelope = Envelope.from_event(event)
277+
send_fn.(state, envelope)
278+
end
279+
end
280+
253281
defp process_and_send_logs(%{on_envelope: on_envelope} = state, log_events, send_fn) do
254282
processed_logs = apply_before_send_log_callbacks(log_events)
255283

@@ -320,7 +348,7 @@ defmodule Sentry.Telemetry.Scheduler do
320348
defp send_envelope(%Scheduler{on_envelope: nil} = state, envelope) do
321349
if state.size >= state.capacity do
322350
Logger.warning(
323-
"Sentry: transport queue full, dropping #{Envelope.item_count(envelope)} log item(s)"
351+
"Sentry: transport queue full, dropping #{Envelope.item_count(envelope)} item(s)"
324352
)
325353

326354
ClientReport.Sender.record_discarded_events(:queue_overflow, envelope.items)
@@ -352,7 +380,7 @@ defmodule Sentry.Telemetry.Scheduler do
352380
:ok
353381

354382
{:error, error} ->
355-
Logger.warning("Sentry: failed to send log envelope: #{Exception.message(error)}")
383+
Logger.warning("Sentry: failed to send envelope: #{Exception.message(error)}")
356384

357385
{:error, error}
358386
end
@@ -423,12 +451,14 @@ defmodule Sentry.Telemetry.Scheduler do
423451

424452
defp default_weights do
425453
%{
454+
critical: Category.weight(:critical),
426455
low: Category.weight(:low)
427456
}
428457
end
429458

430459
defp category_priority_mapping do
431460
[
461+
{:error, :critical},
432462
{:log, :low}
433463
]
434464
end

0 commit comments

Comments
 (0)