diff --git a/.changeset/sentry-handler-opts.md b/.changeset/sentry-handler-opts.md new file mode 100644 index 0000000000..861fbcf960 --- /dev/null +++ b/.changeset/sentry-handler-opts.md @@ -0,0 +1,5 @@ +--- +'@core/sync-service': patch +--- + +`Electric.Telemetry.Sentry.add_logger_handler/1` now accepts an optional second argument — a keyword list whose entries are merged into the `Sentry.LoggerHandler` config map — so downstream apps can tune handler settings like `:discard_threshold` and `:sync_threshold` without reaching into `:logger` after the fact. The existing single-arg `add_logger_handler(id)` form is preserved. diff --git a/packages/sync-service/lib/electric/telemetry/sentry.ex b/packages/sync-service/lib/electric/telemetry/sentry.ex index ef201b3ca3..8578fcdcad 100644 --- a/packages/sync-service/lib/electric/telemetry/sentry.ex +++ b/packages/sync-service/lib/electric/telemetry/sentry.ex @@ -2,19 +2,29 @@ defmodule Electric.Telemetry.Sentry do use Electric.Telemetry @default_handler_id :electric_sentry_handler + @default_config %{metadata: :all, capture_log_messages: true, level: :error} - @spec add_logger_handler(handler_id :: atom()) :: :ok | {:error, term()} + @typedoc """ + Extra entries for the `Sentry.LoggerHandler` config map (e.g. + `:discard_threshold`, `:sync_threshold`). Merged over the defaults at + install time. + """ + @type handler_opts :: [{atom(), term()}] + + def default_handler_id, do: @default_handler_id + + @spec add_logger_handler(atom(), handler_opts()) :: :ok | {:error, term()} + @spec add_logger_handler(atom()) :: :ok | {:error, term()} @spec add_logger_handler() :: :ok | {:error, term()} - def add_logger_handler(id \\ @default_handler_id) + def add_logger_handler(id \\ @default_handler_id, opts \\ []) with_telemetry Sentry.LoggerHandler do - def add_logger_handler(id) do - :logger.add_handler(id, Sentry.LoggerHandler, %{ - config: %{metadata: :all, capture_log_messages: true, level: :error} - }) + def add_logger_handler(id, opts) do + config = Map.merge(@default_config, Map.new(opts)) + :logger.add_handler(id, Sentry.LoggerHandler, %{config: config}) end else - def add_logger_handler(_id), do: :ok + def add_logger_handler(_id, _opts), do: :ok end @spec set_tags_context(keyword()) :: :ok diff --git a/packages/sync-service/test/electric/telemetry/sentry_test.exs b/packages/sync-service/test/electric/telemetry/sentry_test.exs new file mode 100644 index 0000000000..499dea7e86 --- /dev/null +++ b/packages/sync-service/test/electric/telemetry/sentry_test.exs @@ -0,0 +1,66 @@ +if Electric.telemetry_enabled?() and Code.ensure_loaded?(Sentry.LoggerHandler) do + defmodule Electric.Telemetry.SentryTest do + # async: false because :logger handler state is VM-global — unique ids per + # test avoid name collisions but not the add/remove race across processes. + use ExUnit.Case, async: false + + alias Electric.Telemetry.Sentry, as: ElectricSentry + + setup do + id = :"sentry_test_#{System.unique_integer([:positive])}" + on_exit(fn -> _ = :logger.remove_handler(id) end) + {:ok, handler_id: id} + end + + defp handler_config!(id) do + {:ok, %{config: config}} = :logger.get_handler_config(id) + config + end + + describe "add_logger_handler/2" do + test "installs Sentry.LoggerHandler with default config", %{handler_id: id} do + assert :ok = ElectricSentry.add_logger_handler(id) + + {:ok, handler} = :logger.get_handler_config(id) + assert handler.module == Sentry.LoggerHandler + + assert %{metadata: :all, capture_log_messages: true, level: :error} = + handler_config!(id) + end + + test "merges caller-supplied options into the handler config", + %{handler_id: id} do + assert :ok = + ElectricSentry.add_logger_handler(id, + discard_threshold: 2000, + sync_threshold: nil + ) + + assert %{ + metadata: :all, + capture_log_messages: true, + level: :error, + discard_threshold: 2000, + sync_threshold: nil + } = handler_config!(id) + end + + test "caller-supplied options override defaults", %{handler_id: id} do + assert :ok = ElectricSentry.add_logger_handler(id, level: :warning) + + assert %{level: :warning} = handler_config!(id) + end + + test "uses the default handler id when called with no arguments" do + default_id = ElectricSentry.default_handler_id() + _ = :logger.remove_handler(default_id) + on_exit(fn -> _ = :logger.remove_handler(default_id) end) + + assert :ok = ElectricSentry.add_logger_handler() + + {:ok, handler} = :logger.get_handler_config(default_id) + assert handler.module == Sentry.LoggerHandler + end + end + end +end