Skip to content

Commit

Permalink
feat: easier to handle general error from inside a catch (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
grzuy authored Jul 31, 2024
1 parent d953024 commit a58bde8
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 2 deletions.
8 changes: 8 additions & 0 deletions lib/tower.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ defmodule Tower do
:ok = Tower.LoggerHandler.detach()
end

@spec handle_caught(Exception.kind(), Event.reason(), Exception.stacktrace()) :: :ok
@spec handle_caught(Exception.kind(), Event.reason(), Exception.stacktrace(), Keyword.t()) ::
:ok
def handle_caught(kind, reason, stacktrace, options \\ []) do
Event.from_caught(kind, reason, stacktrace, options)
|> report_event()
end

@spec handle_exception(Exception.t(), Exception.stacktrace()) :: :ok
@spec handle_exception(Exception.t(), Exception.stacktrace(), Keyword.t()) :: :ok
def handle_exception(exception, stacktrace, options \\ [])
Expand Down
28 changes: 26 additions & 2 deletions lib/tower/event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,40 @@ defmodule Tower.Event do
defstruct [:time, :level, :kind, :reason, :stacktrace, :metadata]

@type metadata :: %{optional(:log_event) => :logger.log_event()}
@type error_kind :: :error | :exit | :throw
@type non_error_kind :: :message
@type reason :: Exception.t() | term()

@type t :: %__MODULE__{
time: :logger.timestamp(),
level: :logger.level(),
kind: :error | :exit | :throw | :message,
reason: Exception.t() | term(),
kind: error_kind() | non_error_kind(),
reason: reason(),
stacktrace: Exception.stacktrace() | nil,
metadata: metadata()
}

@spec from_caught(Exception.kind(), reason(), Exception.stacktrace()) :: t()
@spec from_caught(Exception.kind(), reason(), Exception.stacktrace(), Keyword.t()) :: t()
def from_caught(kind, reason, stacktrace, options \\ [])

def from_caught(:error, exception, stacktrace, options) when is_exception(exception) do
from_exception(exception, stacktrace, options)
end

def from_caught(:error, reason, stacktrace, options) do
Exception.normalize(:error, reason, stacktrace)
|> from_exception(stacktrace, options)
end

def from_caught(:exit, reason, stacktrace, options) do
from_exit(reason, stacktrace, options)
end

def from_caught(:throw, reason, stacktrace, options) do
from_throw(reason, stacktrace, options)
end

@spec from_exception(Exception.t(), Exception.stacktrace()) :: t()
@spec from_exception(Exception.t(), Exception.stacktrace(), Keyword.t()) :: t()
def from_exception(exception, stacktrace, options \\ []) do
Expand Down
80 changes: 80 additions & 0 deletions test/tower_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,32 @@ defmodule TowerTest do
end

test "reports Exception manually" do
in_unlinked_process(fn ->
try do
1 / 0

Check warning on line 269 in test/tower_test.exs

View workflow job for this annotation

GitHub Actions / main (1.15, 25.3.2.12)

the call to //2 will fail with ArithmeticError

Check warning on line 269 in test/tower_test.exs

View workflow job for this annotation

GitHub Actions / main (1.14, 24.3.4.17)

the call to //2 will fail with ArithmeticError

Check warning on line 269 in test/tower_test.exs

View workflow job for this annotation

GitHub Actions / main (1.13, 23.3.4.20)

this expression will fail with ArithmeticError
catch
kind, reason ->
Tower.handle_caught(kind, reason, __STACKTRACE__)
end
end)

assert_eventually(
[
%{
time: time,
level: :error,
kind: ArithmeticError,
reason: "bad argument in arithmetic expression",
stacktrace: stacktrace
}
] = reported_events()
)

assert_in_delta(time, :logger.timestamp(), 100_000)
assert is_list(stacktrace)
end

test "reports Exception manually (shorthand)" do
in_unlinked_process(fn ->
try do
1 / 0

Check warning on line 295 in test/tower_test.exs

View workflow job for this annotation

GitHub Actions / main (1.15, 25.3.2.12)

the call to //2 will fail with ArithmeticError

Check warning on line 295 in test/tower_test.exs

View workflow job for this annotation

GitHub Actions / main (1.14, 24.3.4.17)

the call to //2 will fail with ArithmeticError

Check warning on line 295 in test/tower_test.exs

View workflow job for this annotation

GitHub Actions / main (1.13, 23.3.4.20)

this expression will fail with ArithmeticError
Expand Down Expand Up @@ -291,6 +317,33 @@ defmodule TowerTest do

@tag capture_log: true
test "manually reports a thrown string" do
in_unlinked_process(fn ->
try do
throw("error")
catch
kind, reason ->
Tower.handle_caught(kind, reason, __STACKTRACE__)
end
end)

assert_eventually(
[
%{
time: time,
level: :error,
kind: :throw,
reason: "error",
stacktrace: stacktrace
}
] = reported_events()
)

assert_in_delta(time, :logger.timestamp(), 100_000)
assert is_list(stacktrace)
end

@tag capture_log: true
test "manually reports a thrown string (shorthand)" do
in_unlinked_process(fn ->
try do
throw("error")
Expand Down Expand Up @@ -318,6 +371,33 @@ defmodule TowerTest do

@tag capture_log: true
test "manually reports an abnormal exit" do
in_unlinked_process(fn ->
try do
exit(:abnormal)
catch
kind, reason ->
Tower.handle_caught(kind, reason, __STACKTRACE__)
end
end)

assert_eventually(
[
%{
time: time,
level: :error,
kind: :exit,
reason: :abnormal,
stacktrace: stacktrace
}
] = reported_events()
)

assert_in_delta(time, :logger.timestamp(), 100_000)
assert is_list(stacktrace)
end

@tag capture_log: true
test "manually reports an abnormal exit (shorthand)" do
in_unlinked_process(fn ->
try do
exit(:abnormal)
Expand Down

0 comments on commit a58bde8

Please sign in to comment.