Current section
Files
Jump to
Current section
Files
src/bugsnag_error_logger.erl
-module(bugsnag_error_logger).
-behaviour(gen_event).
-export([
init/1,
handle_call/2,
handle_event/2,
handle_info/2,
code_change/3,
terminate/2
]).
-record(state, {}).
% Callbacks
init(_InitArgs) ->
{ok, #state{}}.
handle_event({error, GroupLeader, {Pid, Format, Data}}, S) ->
handle_error_msg(GroupLeader, Pid, Format, Data, S);
handle_event({error_report, GroupLeader, {Pid, Type, Report}}, S) ->
handle_error_report(GroupLeader, Pid, Type, Report, S);
handle_event(_Event, State) ->
{ok, State}.
handle_call(_Request, State) ->
{ok, noreply, State}.
handle_info(_Info, State) ->
{ok, State}.
terminate(_Arg, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
% Private API
handle_error_msg(_, _, "** Generic server " ++ _, Data, S) ->
%% gen_server terminate
[Name, LastMsg, StateData, Reason] = Data,
notify(error, Reason,
"gen_server ~w terminated with reason: ~s",
[Name, format_reason(Reason)],
[last_message(LastMsg),
state_data(StateData)]),
{ok, S};
handle_error_msg(_, _, "** State machine " ++ _, Data, S) ->
%% gen_fsm terminate
[Name, LastMsg, StateName, StateData, Reason] = Data,
notify(error, Reason,
"gen_fsm ~w in state ~w terminated with reason: ~s",
[Name, StateName, format_reason(Reason)],
[last_message(LastMsg),
state_data(StateData)]),
{ok, S};
handle_error_msg(_, _, "** gen_event handler" ++ _, Data, S) ->
%% gen_event handler terminate
[ID, Name, LastMsg, StateData, Reason] = Data,
notify(error, Reason,
"gen_event ~w installed in ~w terminated with reason: ~s",
[ID, Name, format_reason(Reason)],
[last_message(LastMsg),
state_data(StateData)]),
{ok, S};
handle_error_msg(_, _, "Error in process " ++ _, Data, S) ->
%% spawn_link'd process terminate
[Name, Node, Reason] = Data,
notify(error, Reason,
"Process ~w on node ~w terminated with reason: ~s",
[Name, Node, format_reason(Reason)],
[{"full_reason", format_term(Reason)}]),
{ok, S};
handle_error_msg(_, _, Format, Data, S) ->
notify(error, Format, Format, Data, []),
{ok, S}.
handle_error_report(_, _, supervisor_report, Report, S) ->
case lists:sort(Report) of
[{errorContext, Context},
{offender, Offender},
{reason, Reason},
{supervisor, Name}] ->
case Reason of
normal ->
ok; % Nothing happens on a normal exit
_ ->
notify(supervisor_report, Reason,
"Supervisor ~w had child ~s exit with reason ~s in context ~w",
[element(2, Name),
format_offender(Offender),
format_reason(Reason),
Context],
[{"full_reason", format_term(Reason)}])
end;
_ ->
%% notify(Type, Reason, Format, Args, Extra),
ok
end,
{ok, S};
handle_error_report(_, _, crash_report, [Report, Neighbors], S) ->
Name = case proplists:get_value(registered_name, Report, []) of
[] -> proplists:get_value(pid, Report);
Atom -> Atom
end,
{Type, Reason, Trace} = proplists:get_value(error_info, Report),
notify(Type, {Reason, Trace},
"Process ~w with ~w neighbors crashed with reason: ~s",
[Name, length(Neighbors), format_reason(Reason)],
[{atom_to_list(Key), format_term(Val)} || {Key, Val} <- Report]),
{ok, S};
handle_error_report(_, _, std_error, _Report, S) ->
%% notify(Type, Reason, Format, Args, Extra),
{ok, S};
handle_error_report(_, _, _Type, _Report, S) ->
%% notify(Type, Reason, Format, Args, Extra),
{ok, S}.
notify(Type, Reason, Format, Args, Extra) ->
Message = format_message(Format, Args),
Request = {atom_to_list(node()), "erlang", format_term(Type), Extra},
{Class, Trace} = parse_reason(Reason),
{Module, Line} = case Trace of
[{M, _, _}|_] ->
{M, 0};
[{M, _, _, Pos}|_] ->
{M, proplists:get_value(line, Pos, 0)};
[] ->
{unknown, 0}
end,
bugsnag:notify(Type, Class, Message, Module, Line, Trace, Request).
parse_reason({Reason, [MFA|_] = Trace})
when is_tuple(MFA) ->
{Atom, DeepTrace} = parse_reason(Reason),
{Atom, DeepTrace ++ Trace};
parse_reason({Reason, []}) ->
parse_reason(Reason);
parse_reason({Reason, {_, _, _} = MFA}) ->
{Atom, DeepTrace} = parse_reason(Reason),
{Atom, DeepTrace ++ [MFA]};
parse_reason({Reason, {_, _, _, Pos} = MFA})
when is_list(Pos) ->
{Atom, DeepTrace} = parse_reason(Reason),
{Atom, DeepTrace ++ [MFA]};
parse_reason(Reason) when is_atom(Reason) ->
{Reason, []};
parse_reason(_Reason) ->
{unknown, []}.
% Formatters
format_args([], FmtAcc, ArgsAcc) ->
{string:join(lists:reverse(FmtAcc), ", "), lists:reverse(ArgsAcc)};
format_args([H|T], FmtAcc, ArgsAcc) ->
format_args(T, ["~p"|FmtAcc], [H|ArgsAcc]).
format_limit([{M, F, _}|_] = Trace) ->
case {M, F} of
{erlang, open_port} ->
"maximum number of ports exceeded";
{erlang, spawn} ->
"maximum number of processes exceeded";
{erlang, spawn_opt} ->
"maximum number of processes exceeded";
{erlang, list_to_atom} ->
"tried to create an atom larger than 255, or maximum atom count exceeded";
{ets, new} ->
"maximum number of ETS tables exceeded";
_ ->
format_term(Trace)
end.
format_message(Fmt, Args) ->
IoList = io_lib:format(Fmt, Args),
Binary = iolist_to_binary(IoList),
Binary. %% binary_to_list(Binary).
format_mfa({M, F, A} = MFA) ->
if
is_list(A) ->
{FmtStr, Args} = format_args(A, [], []),
io_lib:format("~w:~w(" ++ FmtStr ++ ")", [M, F | Args]);
is_integer(A) ->
io_lib:format("~w:~w/~w", [M, F, A]);
true ->
io_lib:format("~w", [MFA])
end;
format_mfa({M, F, A, _}) ->
format_mfa({M, F, A});
format_mfa(Other) ->
io_lib:format("~w", [Other]).
format_offender(Offender) ->
case proplists:get_value(mfargs, Offender) of
undefined ->
%% supervisor_bridge
Mod = proplists:get_value(mod, Offender),
Pid = proplists:get_value(pid, Offender),
io_lib:format("at module ~w at ~w", [Mod, Pid]);
MFArgs ->
%% supervisor
MFA = format_mfa(MFArgs),
Name = proplists:get_value(name, Offender),
Pid = proplists:get_value(pid, Offender),
io_lib:format("~p started with ~s at ~w", [Name, MFA, Pid])
end.
format_reason({'function not exported', [{M, F, A}, MFA|_]}) ->
["call to undefined function ", format_mfa({M, F, length(A)}),
" from ", format_mfa(MFA)];
format_reason({undef, [MFA|_]}) ->
["call to undefined function ", format_mfa(MFA)];
format_reason({bad_return_value, Val}) ->
["bad return value: ", format_term(Val)];
format_reason({{bad_return_value, Val}, MFA}) ->
["bad return value: ", format_term(Val),
" in ", format_mfa(MFA)];
format_reason({{case_clause, Val}, [MFA|_]}) ->
["no case clause matching ", format_term(Val),
" in ", format_mfa(MFA)];
format_reason({function_clause, [MFA|_]}) ->
["no function clause matching ", format_mfa(MFA)];
format_reason({if_clause, [MFA|_]}) ->
["no true branch found while evaluating if expression in ", format_mfa(MFA)];
format_reason({{try_clause, Val}, [MFA|_]}) ->
["no try clause matching ", format_term(Val),
" in ", format_mfa(MFA)];
format_reason({badarith, [MFA|_]}) ->
["bad arithmetic expression in ", format_mfa(MFA)];
format_reason({{badmatch, Val}, [MFA|_]}) ->
["no match of right hand value ", format_term(Val),
" in ", format_mfa(MFA)];
format_reason({emfile, _Trace}) ->
"maximum number of file descriptors exhausted, check ulimit -n";
format_reason({system_limit, [{_, _, _}|_] = Trace}) ->
["system limit: ", format_limit(Trace)];
format_reason({badarg, [MFA, MFA2|_]}) ->
case MFA of
{_M, _F, A} when is_list(A) ->
["bad argument in call to ", format_mfa(MFA),
" in ", format_mfa(MFA2)];
_ ->
%% Generated by a bad call to a BIF?
["bad argument in ", format_mfa(MFA)]
end;
format_reason({{badarity, {Fun, Args}}, [MFA|_]}) ->
{arity, Arity} = lists:keyfind(arity, 1, erlang:fun_info(Fun)),
[io_lib:format("fun called with wrong arity of ~w instead of ~w in ",
[length(Args), Arity]),
format_mfa(MFA)];
format_reason({noproc, MFA}) ->
["no such process or port in call to ", format_mfa(MFA)];
format_reason({{badfun, Term}, [MFA|_]}) ->
["bad function ", format_term(Term),
" in ", format_mfa(MFA)];
format_reason({'EXIT', Reason}) ->
format_reason(Reason);
format_reason({Reason, Child})
when is_tuple(Child) andalso
element(1, Child) =:= child ->
format_reason(Reason);
format_reason({Reason, MFA})
when is_tuple(MFA) ->
%% Attempt to capture nested reasons...
format_reason(Reason);
format_reason(Reason) ->
format_term(Reason).
format_term(Term) ->
lists:flatten(io_lib:format("~p", [Term])).
%% Misc
last_message(LastMsg) ->
{"last_message", format_term(LastMsg)}.
state_data(StateData) ->
{"state_data", format_term(StateData)}.