- %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
- %%
- %% This file is provided to you under the Apache License,
- %% Version 2.0 (the "License"); you may not use this file
- %% except in compliance with the License. You may obtain
- %% a copy of the License at
- %%
- %% http://www.apache.org/licenses/LICENSE-2.0
- %%
- %% Unless required by applicable law or agreed to in writing,
- %% software distributed under the License is distributed on an
- %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- %% KIND, either express or implied. See the License for the
- %% specific language governing permissions and limitations
- %% under the License.
-
- %% @doc A error_logger backend for redirecting events into lager.
- %% Error messages and crash logs are also optionally written to a crash log.
-
- %% @see lager_crash_log
-
- %% @private
-
- -module(error_logger_lager_h).
-
- -include("lager.hrl").
-
- -behaviour(gen_event).
-
- -export([set_high_water/1]).
- -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
- code_change/3]).
-
- -export([format_reason/1]).
-
- -record(state, {
- %% how many messages per second we try to deliver
- hwm = undefined :: 'undefined' | pos_integer(),
- %% how many messages we've received this second
- mps = 0 :: non_neg_integer(),
- %% the current second
- lasttime = os:timestamp() :: erlang:timestamp(),
- %% count of dropped messages this second
- dropped = 0 :: non_neg_integer()
- }).
-
- -define(LOGMSG(Level, Pid, Msg),
- case ?SHOULD_LOG(Level) of
- true ->
- _ =lager:log(Level, Pid, Msg),
- ok;
- _ -> ok
- end).
-
- -define(LOGFMT(Level, Pid, Fmt, Args),
- case ?SHOULD_LOG(Level) of
- true ->
- _ = lager:log(Level, Pid, Fmt, Args),
- ok;
- _ -> ok
- end).
-
- -ifdef(TEST).
- -compile(export_all).
- %% Make CRASH synchronous when testing, to avoid timing headaches
- -define(CRASH_LOG(Event),
- catch(gen_server:call(lager_crash_log, {log, Event}))).
- -else.
- -define(CRASH_LOG(Event),
- gen_server:cast(lager_crash_log, {log, Event})).
- -endif.
-
- set_high_water(N) ->
- gen_event:call(error_logger, ?MODULE, {set_high_water, N}, infinity).
-
- -spec init(any()) -> {ok, #state{}}.
- init([HighWaterMark]) ->
- {ok, #state{hwm=HighWaterMark}}.
-
- handle_call({set_high_water, N}, State) ->
- {ok, ok, State#state{hwm = N}};
- handle_call(_Request, State) ->
- {ok, unknown_call, State}.
-
- handle_event(Event, State) ->
- case check_hwm(State) of
- {true, NewState} ->
- log_event(Event, NewState);
- {false, NewState} ->
- {ok, NewState}
- end.
-
- handle_info(_Info, State) ->
- {ok, State}.
-
- terminate(_Reason, _State) ->
- ok.
-
- code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
- %% internal functions
-
- check_hwm(State = #state{hwm = undefined}) ->
- {true, State};
- check_hwm(State = #state{mps = Mps, hwm = Hwm}) when Mps < Hwm ->
- %% haven't hit high water mark yet, just log it
- {true, State#state{mps=Mps+1}};
- check_hwm(State = #state{hwm = Hwm, lasttime = Last, dropped = Drop}) ->
- %% are we still in the same second?
- {M, S, _} = Now = os:timestamp(),
- case Last of
- {M, S, _} ->
- %% still in same second, but have exceeded the high water mark
- NewDrops = discard_messages(Now, 0),
- {false, State#state{dropped=Drop+NewDrops}};
- _ ->
- %% different second, reset all counters and allow it
- case Drop > 0 of
- true ->
- ?LOGFMT(warning, self(), "lager_error_logger_h dropped ~p messages in the last second that exceeded the limit of ~p messages/sec",
- [Drop, Hwm]);
- false ->
- ok
- end,
- {true, State#state{dropped = 0, mps=1, lasttime = Now}}
- end.
-
- discard_messages(Second, Count) ->
- {M, S, _} = os:timestamp(),
- case Second of
- {M, S, _} ->
- receive
- _Msg ->
- discard_messages(Second, Count+1)
- after 0 ->
- Count
- end;
- _ ->
- Count
- end.
-
- log_event(Event, State) ->
- case Event of
- {error, _GL, {Pid, Fmt, Args}} ->
- case Fmt of
- "** Generic server "++_ ->
- %% gen_server terminate
- [Name, _Msg, _State, Reason] = Args,
- ?CRASH_LOG(Event),
- ?LOGFMT(error, Pid, "gen_server ~w terminated with reason: ~s",
- [Name, format_reason(Reason)]);
- "** State machine "++_ ->
- %% gen_fsm terminate
- [Name, _Msg, StateName, _StateData, Reason] = Args,
- ?CRASH_LOG(Event),
- ?LOGFMT(error, Pid, "gen_fsm ~w in state ~w terminated with reason: ~s",
- [Name, StateName, format_reason(Reason)]);
- "** gen_event handler"++_ ->
- %% gen_event handler terminate
- [ID, Name, _Msg, _State, Reason] = Args,
- ?CRASH_LOG(Event),
- ?LOGFMT(error, Pid, "gen_event ~w installed in ~w terminated with reason: ~s",
- [ID, Name, format_reason(Reason)]);
- "** Cowboy handler"++_ ->
- %% Cowboy HTTP server error
- ?CRASH_LOG(Event),
- case Args of
- [Module, Function, Arity, _Request, _State] ->
- %% we only get the 5-element list when its a non-exported function
- ?LOGFMT(error, Pid,
- "Cowboy handler ~p terminated with reason: call to undefined function ~p:~p/~p",
- [Module, Module, Function, Arity]);
- [Module, Function, Arity, _Class, Reason | Tail] ->
- %% any other cowboy error_format list *always* ends with the stacktrace
- StackTrace = lists:last(Tail),
- ?LOGFMT(error, Pid,
- "Cowboy handler ~p terminated in ~p:~p/~p with reason: ~s",
- [Module, Module, Function, Arity, format_reason({Reason, StackTrace})])
- end;
- "webmachine error"++_ ->
- %% Webmachine HTTP server error
- ?CRASH_LOG(Event),
- [Path, Error] = Args,
- %% webmachine likes to mangle the stack, for some reason
- StackTrace = case Error of
- {error, {error, Reason, Stack}} ->
- {Reason, Stack};
- _ ->
- Error
- end,
- ?LOGFMT(error, Pid, "Webmachine error at path ~p : ~s", [Path, format_reason(StackTrace)]);
- _ ->
- ?CRASH_LOG(Event),
- ?LOGMSG(error, Pid, lager:safe_format(Fmt, Args, ?DEFAULT_TRUNCATION))
- end;
- {error_report, _GL, {Pid, std_error, D}} ->
- ?CRASH_LOG(Event),
- ?LOGMSG(error, Pid, print_silly_list(D));
- {error_report, _GL, {Pid, supervisor_report, D}} ->
- ?CRASH_LOG(Event),
- case lists:sort(D) of
- [{errorContext, Ctx}, {offender, Off}, {reason, Reason}, {supervisor, Name}] ->
- Offender = format_offender(Off),
- ?LOGFMT(error, Pid,
- "Supervisor ~w had child ~s exit with reason ~s in context ~w",
- [supervisor_name(Name), Offender, format_reason(Reason), Ctx]);
- _ ->
- ?LOGMSG(error, Pid, "SUPERVISOR REPORT " ++ print_silly_list(D))
- end;
- {error_report, _GL, {Pid, crash_report, [Self, Neighbours]}} ->
- ?CRASH_LOG(Event),
- ?LOGMSG(error, Pid, "CRASH REPORT " ++ format_crash_report(Self, Neighbours));
- {warning_msg, _GL, {Pid, Fmt, Args}} ->
- ?LOGMSG(warning, Pid, lager:safe_format(Fmt, Args, ?DEFAULT_TRUNCATION));
- {warning_report, _GL, {Pid, std_warning, Report}} ->
- ?LOGMSG(warning, Pid, print_silly_list(Report));
- {info_msg, _GL, {Pid, Fmt, Args}} ->
- ?LOGMSG(info, Pid, lager:safe_format(Fmt, Args, ?DEFAULT_TRUNCATION));
- {info_report, _GL, {Pid, std_info, D}} when is_list(D) ->
- Details = lists:sort(D),
- case Details of
- [{application, App}, {exited, Reason}, {type, _Type}] ->
- ?LOGFMT(info, Pid, "Application ~w exited with reason: ~s",
- [App, format_reason(Reason)]);
- _ ->
- ?LOGMSG(info, Pid, print_silly_list(D))
- end;
- {info_report, _GL, {Pid, std_info, D}} ->
- ?LOGFMT(info, Pid, "~w", [D]);
- {info_report, _GL, {P, progress, D}} ->
- Details = lists:sort(D),
- case Details of
- [{application, App}, {started_at, Node}] ->
- ?LOGFMT(info, P, "Application ~w started on node ~w",
- [App, Node]);
- [{started, Started}, {supervisor, Name}] ->
- MFA = format_mfa(proplists:get_value(mfargs, Started)),
- Pid = proplists:get_value(pid, Started),
- ?LOGFMT(debug, P, "Supervisor ~w started ~s at pid ~w",
- [supervisor_name(Name), MFA, Pid]);
- _ ->
- ?LOGMSG(info, P, "PROGRESS REPORT " ++ print_silly_list(D))
- end;
- _ ->
- ?LOGFMT(warning, self(), "Unexpected error_logger event ~w", [Event])
- end,
- {ok, State}.
-
- format_crash_report(Report, Neighbours) ->
- Name = case proplists:get_value(registered_name, Report, []) of
- [] ->
- %% process_info(Pid, registered_name) returns [] for unregistered processes
- proplists:get_value(pid, Report);
- Atom -> Atom
- end,
- {Class, Reason, Trace} = proplists:get_value(error_info, Report),
- ReasonStr = format_reason({Reason, Trace}),
- Type = case Class of
- exit -> "exited";
- _ -> "crashed"
- end,
- io_lib:format("Process ~w with ~w neighbours ~s with reason: ~s",
- [Name, length(Neighbours), Type, ReasonStr]).
-
- format_offender(Off) ->
- case proplists:get_value(mfargs, Off) of
- undefined ->
- %% supervisor_bridge
- io_lib:format("at module ~w at ~w",
- [proplists:get_value(mod, Off), proplists:get_value(pid, Off)]);
- MFArgs ->
- %% regular supervisor
- MFA = format_mfa(MFArgs),
- Name = proplists:get_value(name, Off),
- io_lib:format("~p started with ~s at ~w",
- [Name, MFA, proplists:get_value(pid, Off)])
- 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({'function not exported', [{M, F, A, _Props},MFA|_]}) ->
- %% R15 line numbers
- ["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, {_MFA, {'EXIT', Reason}}}) ->
- format_reason(Reason);
- format_reason({bad_return, {MFA, Val}}) ->
- ["bad return value ", print_val(Val), " from ", format_mfa(MFA)];
- format_reason({bad_return_value, Val}) ->
- ["bad return value: ", print_val(Val)];
- format_reason({{bad_return_value, Val}, MFA}) ->
- ["bad return value: ", print_val(Val), " in ", format_mfa(MFA)];
- format_reason({{badrecord, Record}, [MFA|_]}) ->
- ["bad record ", print_val(Record), " in ", format_mfa(MFA)];
- format_reason({{case_clause, Val}, [MFA|_]}) ->
- ["no case clause matching ", print_val(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 ", print_val(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 ", print_val(Val), " in ", format_mfa(MFA)];
- format_reason({emfile, _Trace}) ->
- "maximum number of file descriptors exhausted, check ulimit -n";
- format_reason({system_limit, [{M, F, _}|_] = Trace}) ->
- Limit = 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";
- _ ->
- {Str, _} = lager_trunc_io:print(Trace, 500),
- Str
- end,
- ["system limit: ", Limit];
- format_reason({badarg, [MFA,MFA2|_]}) ->
- case MFA of
- {_M, _F, A, _Props} when is_list(A) ->
- %% R15 line numbers
- ["bad argument in call to ", format_mfa(MFA), " in ", format_mfa(MFA2)];
- {_M, _F, A} when is_list(A) ->
- ["bad argument in call to ", format_mfa(MFA), " in ", format_mfa(MFA2)];
- _ ->
- %% seems to be 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 ", print_val(Term), " in ", format_mfa(MFA)];
- format_reason({Reason, [{M, F, A}|_]}) when is_atom(M), is_atom(F), is_integer(A) ->
- [format_reason(Reason), " in ", format_mfa({M, F, A})];
- format_reason({Reason, [{M, F, A, Props}|_]}) when is_atom(M), is_atom(F), is_integer(A), is_list(Props) ->
- %% line numbers
- [format_reason(Reason), " in ", format_mfa({M, F, A, Props})];
- format_reason(Reason) ->
- {Str, _} = lager_trunc_io:print(Reason, 500),
- Str.
-
- format_mfa({M, F, A}) when is_list(A) ->
- {FmtStr, Args} = format_args(A, [], []),
- io_lib:format("~w:~w("++FmtStr++")", [M, F | Args]);
- format_mfa({M, F, A}) when is_integer(A) ->
- io_lib:format("~w:~w/~w", [M, F, A]);
- format_mfa({M, F, A, Props}) when is_list(Props) ->
- case proplists:get_value(line, Props) of
- undefined ->
- format_mfa({M, F, A});
- Line ->
- [format_mfa({M, F, A}), io_lib:format(" line ~w", [Line])]
- end;
- format_mfa([{M, F, A}, _]) ->
- %% this kind of weird stacktrace can be generated by a uncaught throw in a gen_server
- format_mfa({M, F, A});
- format_mfa([{M, F, A, Props}, _]) when is_list(Props) ->
- %% this kind of weird stacktrace can be generated by a uncaught throw in a gen_server
- format_mfa({M, F, A, Props});
- format_mfa(Other) ->
- io_lib:format("~w", [Other]).
-
- format_args([], FmtAcc, ArgsAcc) ->
- {string:join(lists:reverse(FmtAcc), ", "), lists:reverse(ArgsAcc)};
- format_args([H|T], FmtAcc, ArgsAcc) ->
- {Str, _} = lager_trunc_io:print(H, 100),
- format_args(T, ["~s"|FmtAcc], [Str|ArgsAcc]).
-
- print_silly_list(L) when is_list(L) ->
- case lager_stdlib:string_p(L) of
- true ->
- lager_trunc_io:format("~s", [L], ?DEFAULT_TRUNCATION);
- _ ->
- print_silly_list(L, [], [])
- end;
- print_silly_list(L) ->
- {Str, _} = lager_trunc_io:print(L, ?DEFAULT_TRUNCATION),
- Str.
-
- print_silly_list([], Fmt, Acc) ->
- lager_trunc_io:format(string:join(lists:reverse(Fmt), ", "),
- lists:reverse(Acc), ?DEFAULT_TRUNCATION);
- print_silly_list([{K,V}|T], Fmt, Acc) ->
- print_silly_list(T, ["~p: ~p" | Fmt], [V, K | Acc]);
- print_silly_list([H|T], Fmt, Acc) ->
- print_silly_list(T, ["~p" | Fmt], [H | Acc]).
-
- print_val(Val) ->
- {Str, _} = lager_trunc_io:print(Val, 500),
- Str.
-
- supervisor_name({local, Name}) -> Name;
- supervisor_name(Name) -> Name.
- -ifdef(TEST).
-
- %% Not intended to be a fully paranoid EUnit test....
-
- t0() ->
- application:stop(lager),
- application:stop(sasl),
- lager:start(),
- set_high_water(5),
- [error_logger:warning_msg("Foo ~p!", [X]) || X <- lists:seq(1,10)],
- timer:sleep(1000),
- [error_logger:warning_msg("Bar ~p!", [X]) || X <- lists:seq(1,10)],
- timer:sleep(1000),
- error_logger:warning_msg("Baz!"),
- ok.
-
- -endif. % TEST
|