-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 -> % Nothing happens on a normal exit ok; _ -> 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_to_list(Binary). 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)}.