diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39019b7..4a8d5ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,10 @@ jobs: elixir: 1.5 - otp: 21.3 elixir: 1.8 + - otp: 21.3 + elixir: 1.10 + - otp: 24 + elixir: 1.13 env: MIX_ENV: test diff --git a/lib/rollbax/reporter/standard.ex b/lib/rollbax/reporter/standard.ex index 8857395..27b7389 100644 --- a/lib/rollbax/reporter/standard.ex +++ b/lib/rollbax/reporter/standard.ex @@ -10,6 +10,10 @@ defmodule Rollbax.Reporter.Standard do handle_error_format(format, data) end + def handle_event(:error_report, {_pid, :crash_report, data}) do + handle_error_format(:crash_report, data) + end + def handle_event(_type, _event) do :next end @@ -92,6 +96,73 @@ defmodule Rollbax.Reporter.Standard do } end + # OTP error logger crash report + defp handle_error_format(:crash_report, [data, _]) do + {m, f, a} = Keyword.fetch!(data, :initial_call) + + name = + case Keyword.get(data, :registered_name) do + [] -> data |> Keyword.fetch!(:pid) |> inspect() + name -> inspect(name) + end + + {class, message, stacktrace, crash_report} = + case Keyword.fetch!(data, :error_info) do + {_, %class{message: message}, stacktrace} -> + {inspect(class), message, stacktrace, ""} + + {:exit, reason, stacktrace} when is_atom(reason) -> + {inspect(reason), inspect(reason), stacktrace, ""} + + {_, info, stacktrace} -> + info = + if is_tuple(info) do + elem(info, 0) + else + info + end + + case info do + %class{message: message} -> + {inspect(class), message, stacktrace, inspect(info)} + + %class{} -> + {inspect(class), inspect(class), stacktrace, inspect(info)} + + atom when is_atom(atom) -> + {inspect(atom), inspect(atom), stacktrace, inspect(info)} + + {%class{message: message}, inner_stacktrace} -> + {inspect(class), message, inner_stacktrace, inspect(info)} + + {%class{}, inner_stacktrace} -> + {inspect(class), inspect(class), inner_stacktrace, inspect(info)} + + {atom, inner_stacktrace} when is_atom(atom) -> + {inspect(atom), inspect(atom), inner_stacktrace, inspect(info)} + + {{%class{message: message}, inner_stacktrace}, _} -> + {inspect(class), message, inner_stacktrace, inspect(info)} + + reason -> + {"ProcessCrash", "A process crashed", stacktrace, inspect(reason, limit: :infinity)} + end + end + + %Rollbax.Exception{ + class: "Crash report (#{class})", + message: message, + stacktrace: stacktrace, + custom: %{ + name: name, + started_from: data |> Keyword.fetch!(:ancestors) |> hd() |> inspect(), + function: inspect(Function.capture(m, f, length(a))), + arguments: inspect(a), + crash_report: crash_report + } + } + end + # Any other error (for example, the ones logged through # :error_logger.error_msg/1). This reporter doesn't report those to Rollbar. defp handle_error_format(_format, _data) do diff --git a/test/rollbax/logger_test.exs b/test/rollbax/logger_test.exs index db44c33..d967020 100644 --- a/test/rollbax/logger_test.exs +++ b/test/rollbax/logger_test.exs @@ -32,8 +32,9 @@ defmodule Rollbax.LoggerTest do def init(args), do: {:ok, args} - def handle_cast(:raise_elixir, _state) do - Map.fetch!(Map.new(), :nonexistent_key) + def handle_cast(:raise_elixir, state) do + :ok = Map.fetch!(Map.new(), :nonexistent_key) + {:noreply, state} end end @@ -236,7 +237,7 @@ defmodule Rollbax.LoggerTest do data = assert_performed_request()["data"] assert data["body"]["trace"]["exception"] == %{ - "class" => "Task terminating (RuntimeError)", + "class" => "Crash report (RuntimeError)", "message" => "oops" } @@ -246,7 +247,7 @@ defmodule Rollbax.LoggerTest do ~r[anonymous fn/0 in Rollbax.LoggerTest.(\")?test task with anonymous function raising an error(\")?/1] assert data["custom"]["name"] == inspect(task) - assert data["custom"]["function"] =~ ~r/\A#Function<.* in Rollbax\.LoggerTest/ + assert data["custom"]["function"] =~ ~r/Rollbax\.LoggerTest/ assert data["custom"]["arguments"] == "[]" end) end @@ -262,7 +263,7 @@ defmodule Rollbax.LoggerTest do data = assert_performed_request()["data"] assert data["body"]["trace"]["exception"] == %{ - "class" => "Task terminating (RuntimeError)", + "class" => "Crash report (RuntimeError)", "message" => "my message" } @@ -272,14 +273,44 @@ defmodule Rollbax.LoggerTest do assert data["custom"] == %{ "name" => inspect(task), "function" => "&MyModule.raise_error/1", - "arguments" => ~s(["my message"]), - "started_from" => inspect(self()) + "arguments" => "[:Argument__1]", + "started_from" => inspect(self()), + "crash_report" => "" } end) after purge_module(MyModule) end + test "task with undefined mfa" do + defmodule Test.Undef do + def func(_arg), do: nil + end + + capture_log(fn -> + {:ok, task} = Task.start(Elixir.UndefinedMFA, :func, []) + + data = assert_performed_request()["data"] + + assert data["body"]["trace"]["exception"] == %{ + "class" => "Crash report (:undef)", + "message" => ":undef" + } + + assert find_frames_for_current_file(data["body"]["trace"]["frames"]) == [] + + assert data["custom"] == %{ + "name" => inspect(task), + "function" => "&UndefinedMFA.func/0", + "arguments" => "[]", + "started_from" => inspect(self()), + "crash_report" => ":undef" + } + end) + after + purge_module(UndefinedMFA) + end + if List.to_integer(:erlang.system_info(:otp_release)) < 19 do test "gen_fsm terminating" do defmodule Elixir.MyGenFsm do diff --git a/test/test_helper.exs b/test/test_helper.exs index 475a68d..0e938cf 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -18,13 +18,16 @@ defmodule ExUnit.RollbaxCase do api_endpoint \\ "http://localhost:4004", proxy \\ nil ) do - Rollbax.Client.start_link( - api_endpoint: api_endpoint, - access_token: token, - environment: env, - enabled: true, - custom: custom, - proxy: proxy + start_supervised( + {Rollbax.Client, + [ + api_endpoint: api_endpoint, + access_token: token, + environment: env, + enabled: true, + custom: custom, + proxy: proxy + ]} ) end