diff --git a/lib/tower.ex b/lib/tower.ex index 6fc4292..7416994 100644 --- a/lib/tower.ex +++ b/lib/tower.ex @@ -208,6 +208,14 @@ defmodule Tower do passed along to the reporter. """ + defmodule ReportEventError do + defexception [:original_exception, :reporter] + + def message(%__MODULE__{reporter: reporter}) do + "An error occurred while trying to report an event with reporter #{reporter}" + end + end + alias Tower.Event @default_reporters [Tower.EphemeralReporter] @@ -396,9 +404,23 @@ defmodule Tower do defp report_event(%Event{} = event) do reporters() |> Enum.each(fn reporter -> - async(fn -> + report_event(reporter, event) + end) + end + + defp report_event(reporter, %Event{reason: %ReportEventError{reporter: reporter}}) do + # Ignore so we don't enter in a loop trying to report to the same buggy reporter + :ignore + end + + defp report_event(reporter, event) do + async(fn -> + try do reporter.report_event(event) - end) + rescue + exception -> + raise ReportEventError, reporter: reporter, original_exception: exception + end end) end diff --git a/test/tower_test.exs b/test/tower_test.exs index 6397fd8..c449765 100644 --- a/test/tower_test.exs +++ b/test/tower_test.exs @@ -470,6 +470,57 @@ defmodule TowerTest do assert is_list(stacktrace) end + test "bug in one reporter doesn't affect other reporters" do + defmodule BuggyReporter do + @behaviour Tower.Reporter + + @impl true + def report_event(_event) do + raise "I have a bug" + end + end + + put_env(:reporters, [BuggyReporter, Tower.EphemeralReporter]) + + capture_log(fn -> + in_unlinked_process(fn -> + 1 / 0 + end) + end) + + assert_eventually( + [ + %{ + id: id1, + datetime: datetime1, + level: :error, + kind: :error, + reason: %Tower.ReportEventError{ + reporter: BuggyReporter, + original_exception: %RuntimeError{message: "I have a bug"} + }, + stacktrace: stacktrace1 + }, + %{ + id: id2, + datetime: datetime2, + level: :error, + kind: :error, + reason: %ArithmeticError{message: "bad argument in arithmetic expression"}, + stacktrace: stacktrace2 + } + ] = reported_events() + ) + + assert String.length(id1) == 36 + assert recent_datetime?(datetime1) + assert is_list(stacktrace1) + assert String.length(id2) == 36 + assert recent_datetime?(datetime2) + assert is_list(stacktrace2) + assert datetime1 > datetime2 + end + defp in_unlinked_process(fun) when is_function(fun, 0) do {:ok, pid} = Task.Supervisor.start_link()