diff --git a/lib/tower.ex b/lib/tower.ex index bc87d22..ff1b41a 100644 --- a/lib/tower.ex +++ b/lib/tower.ex @@ -11,13 +11,22 @@ defmodule Tower do :ok = Tower.LoggerHandler.detach() end - def report_exception(exception, stacktrace, meta \\ %{}) do + def report_exception(exception, stacktrace, meta \\ %{}) + when is_exception(exception) and is_list(stacktrace) do reporters() |> Enum.each(fn reporter -> reporter.report_exception(exception, stacktrace, meta) end) end + def report(type, reason, stacktrace, meta \\ %{}) + when is_atom(type) and is_binary(reason) and is_list(stacktrace) do + reporters() + |> Enum.each(fn reporter -> + reporter.report(type, reason, stacktrace, meta) + end) + end + def reporters do [ Tower.EphemeralReporter diff --git a/lib/tower/ephemeral_reporter.ex b/lib/tower/ephemeral_reporter.ex index 67f98ec..a60f1e7 100644 --- a/lib/tower/ephemeral_reporter.ex +++ b/lib/tower/ephemeral_reporter.ex @@ -9,16 +9,34 @@ defmodule Tower.EphemeralReporter do when is_exception(exception) and is_list(stacktrace) do Agent.update( __MODULE__, - fn exceptions -> + fn errors -> [ - %{timestamp: DateTime.utc_now(), exception: exception, stacktrace: stacktrace} - | exceptions + %{ + timestamp: DateTime.utc_now(), + type: exception.__struct__, + reason: Exception.message(exception), + stacktrace: stacktrace + } + | errors ] end ) end - def exceptions do + def report(type, reason, stacktrace, _meta \\ %{}) + when is_atom(type) and is_binary(reason) and is_list(stacktrace) do + Agent.update( + __MODULE__, + fn errors -> + [ + %{timestamp: DateTime.utc_now(), type: type, reason: reason, stacktrace: stacktrace} + | errors + ] + end + ) + end + + def errors do Agent.get(__MODULE__, & &1) end end diff --git a/lib/tower/logger_handler.ex b/lib/tower/logger_handler.ex index ff85688..a24132f 100644 --- a/lib/tower/logger_handler.ex +++ b/lib/tower/logger_handler.ex @@ -24,13 +24,19 @@ defmodule Tower.LoggerHandler do :ok end - def log(%{level: _level, meta: %{crash_reason: {exception, stacktrace}} = meta}, _config) + def log(%{level: :error, meta: %{crash_reason: {exception, stacktrace}} = meta}, _config) when is_exception(exception) and is_list(stacktrace) do IO.puts("[Tower.LoggerHandler] EXCEPTION #{inspect(exception)}") Tower.report_exception(exception, stacktrace, meta) end + def log(%{level: :error, meta: %{crash_reason: {{:nocatch, reason}, stacktrace}} = meta}, _config) when is_list(stacktrace) do + IO.puts("[Tower.LoggerHandler] NOCATCH #{inspect(reason)}") + + Tower.report(:nocatch, reason, stacktrace, meta) + end + def log(log_event, _config) do IO.puts( "[Tower.LoggerHandler] UNHANDLED LOG EVENT log_event=#{inspect(log_event, pretty: true)}" diff --git a/test/tower_test.exs b/test/tower_test.exs index 853a7a7..9685441 100644 --- a/test/tower_test.exs +++ b/test/tower_test.exs @@ -13,7 +13,7 @@ defmodule TowerTest do test "starts with 0 exceptions" do Tower.EphemeralReporter.start_link([]) - assert Tower.EphemeralReporter.exceptions() |> length() == 0 + assert [] = Tower.EphemeralReporter.errors() end test "reports a raise" do @@ -26,10 +26,31 @@ defmodule TowerTest do assert( [ %{ - exception: %RuntimeError{message: "error inside process"}, + type: RuntimeError, + reason: "error inside process", stacktrace: stacktrace } - ] = Tower.EphemeralReporter.exceptions() + ] = Tower.EphemeralReporter.errors() + ) + + assert is_list(stacktrace) + end + + test "reports a throw" do + Tower.EphemeralReporter.start_link([]) + + in_unlinked_process(fn -> + throw "error" + end) + + assert( + [ + %{ + type: :nocatch, + reason: "error", + stacktrace: stacktrace + } + ] = Tower.EphemeralReporter.errors() ) assert is_list(stacktrace) @@ -45,10 +66,11 @@ defmodule TowerTest do assert( [ %{ - exception: %ArithmeticError{message: "bad argument in arithmetic expression"}, + type: ArithmeticError, + reason: "bad argument in arithmetic expression", stacktrace: stacktrace } - ] = Tower.EphemeralReporter.exceptions() + ] = Tower.EphemeralReporter.errors() ) assert is_list(stacktrace)