From 0412dea5ca9e56e06e35b09e1886f36c971e919d Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Mon, 14 Sep 2026 08:28:22 +0000 Subject: [PATCH] fix(telemetry_processor): validate buffer config --- lib/sentry/config.ex | 41 ++++++++++++++++++++++++++- lib/sentry/telemetry/buffer.ex | 6 ++++ test/sentry/config_test.exs | 8 ++++++ test/sentry/telemetry/buffer_test.exs | 31 ++++++++++++++++++-- 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/lib/sentry/config.ex b/lib/sentry/config.ex index 475ec4eb1..2a1439fc0 100644 --- a/lib/sentry/config.ex +++ b/lib/sentry/config.ex @@ -742,7 +742,7 @@ defmodule Sentry.Config do """ ], telemetry_buffer_capacities: [ - type: {:map, {:in, [:error, :check_in, :transaction, :log, :metric]}, :pos_integer}, + type: {:custom, __MODULE__, :__validate_telemetry_buffer_capacities__, []}, default: %{}, type_doc: "`%{category => pos_integer()}`", doc: """ @@ -1493,6 +1493,45 @@ defmodule Sentry.Config do {:error, "expected a Regex or a string pattern, got: #{inspect(term)}"} end + def __validate_telemetry_buffer_capacities__(capacities) when is_map(capacities) do + Enum.reduce_while(capacities, {:ok, capacities}, fn {category, capacity}, acc -> + case validate_buffer_capacity(category, capacity) do + :ok -> {:cont, acc} + {:error, _reason} = error -> {:halt, error} + end + end) + end + + def __validate_telemetry_buffer_capacities__(other) do + {:error, "expected a map of category to capacity, got: #{inspect(other)}"} + end + + defp validate_buffer_capacity(category, _capacity) + when category not in [:error, :check_in, :transaction, :log, :metric] do + {:error, + "expected category to be one of [:error, :check_in, :transaction, :log, :metric], " <> + "got: #{inspect(category)}"} + end + + defp validate_buffer_capacity(category, capacity) + when not is_integer(capacity) or capacity < 1 do + {:error, + "expected capacity for #{inspect(category)} to be a positive integer, " <> + "got: #{inspect(capacity)}"} + end + + defp validate_buffer_capacity(category, capacity) do + batch_size = Sentry.Telemetry.Category.default_config(category).batch_size + + if capacity < batch_size do + {:error, + "capacity for #{inspect(category)} must be at least its batch size of #{batch_size}, " <> + "got: #{inspect(capacity)}"} + else + :ok + end + end + def __validate_oban_tags_to_sentry_tags__(nil), do: {:ok, nil} def __validate_oban_tags_to_sentry_tags__(fun) when is_function(fun, 1) do diff --git a/lib/sentry/telemetry/buffer.ex b/lib/sentry/telemetry/buffer.ex index b281de720..bc133309e 100644 --- a/lib/sentry/telemetry/buffer.ex +++ b/lib/sentry/telemetry/buffer.ex @@ -136,6 +136,12 @@ defmodule Sentry.Telemetry.Buffer do last_flush_time: System.monotonic_time(:millisecond) } + if state.batch_size > state.capacity do + raise ArgumentError, + ":batch_size (#{state.batch_size}) cannot be greater than :capacity " <> + "(#{state.capacity}) for the #{inspect(category)} buffer" + end + {:ok, state} end diff --git a/test/sentry/config_test.exs b/test/sentry/config_test.exs index 3a96f9f68..7d2fa07f7 100644 --- a/test/sentry/config_test.exs +++ b/test/sentry/config_test.exs @@ -367,6 +367,14 @@ defmodule Sentry.ConfigTest do assert_raise ArgumentError, ~r/telemetry_buffer_capacities/, fn -> Config.validate!(telemetry_buffer_capacities: %{log: 0}) end + + assert_raise ArgumentError, ~r/telemetry_buffer_capacities/, fn -> + Config.validate!(telemetry_buffer_capacities: %{log: 50}) + end + + assert Config.validate!(telemetry_buffer_capacities: %{log: 100})[ + :telemetry_buffer_capacities + ] == %{log: 100} end test ":namespace with valid resolver" do diff --git a/test/sentry/telemetry/buffer_test.exs b/test/sentry/telemetry/buffer_test.exs index 107cc1213..a9f25158f 100644 --- a/test/sentry/telemetry/buffer_test.exs +++ b/test/sentry/telemetry/buffer_test.exs @@ -27,6 +27,21 @@ defmodule Sentry.Telemetry.BufferTest do GenServer.stop(pid) end + test "refuses a capacity smaller than the batch size" do + Process.flag(:trap_exit, true) + + assert {:error, {%ArgumentError{message: message}, _stacktrace}} = + Buffer.start_link( + category: :log, + name: :test_buffer_capacity_below_batch, + capacity: 2, + batch_size: 5 + ) + + assert message =~ ":batch_size" + assert message =~ ":capacity" + end + test "allows overriding capacity, batch_size, and timeout" do assert {:ok, pid} = Buffer.start_link( @@ -135,7 +150,14 @@ defmodule Sentry.Telemetry.BufferTest do describe "overflow behavior" do test "drops oldest item when buffer is full" do - {:ok, pid} = Buffer.start_link(category: :log, name: :test_buffer_overflow, capacity: 2) + {:ok, pid} = + Buffer.start_link( + category: :log, + name: :test_buffer_overflow, + capacity: 2, + batch_size: 1 + ) + Buffer.add(pid, make_item("e1")) Buffer.add(pid, make_item("e2")) Buffer.add(pid, make_item("e3")) @@ -172,7 +194,12 @@ defmodule Sentry.Telemetry.BufferTest do test "FIFO ordering is preserved after overflow" do {:ok, pid} = - Buffer.start_link(category: :log, name: :test_buffer_fifo_overflow, capacity: 3) + Buffer.start_link( + category: :log, + name: :test_buffer_fifo_overflow, + capacity: 3, + batch_size: 1 + ) for i <- 1..5, do: Buffer.add(pid, make_item("e#{i}"))