| % Licensed 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 The formatting functions in this module are pulled |
| % from lager's error_logger_lager_h.erl which is available |
| % under the ASFv2 license. |
| |
| |
| -module(couch_log_formatter). |
| |
| |
| -export([ |
| format/4, |
| format/3, |
| format/1, |
| |
| format_reason/1, |
| format_mfa/1, |
| format_trace/1, |
| format_args/3 |
| ]). |
| |
| |
| -include("couch_log.hrl"). |
| |
| |
| -define(DEFAULT_TRUNCATION, 1024). |
| |
| |
| format(Level, Pid, Fmt, Args) -> |
| #log_entry{ |
| level = couch_log_util:level_to_atom(Level), |
| pid = Pid, |
| msg = maybe_truncate(Fmt, Args), |
| msg_id = couch_log_util:get_msg_id(), |
| time_stamp = couch_log_util:iso8601_timestamp() |
| }. |
| |
| |
| format(Level, Pid, Msg) -> |
| #log_entry{ |
| level = couch_log_util:level_to_atom(Level), |
| pid = Pid, |
| msg = maybe_truncate(Msg), |
| msg_id = couch_log_util:get_msg_id(), |
| time_stamp = couch_log_util:iso8601_timestamp() |
| }. |
| |
| |
| format(Event) -> |
| try |
| do_format(Event) |
| catch |
| Tag:Err -> |
| Msg = "Encountered error ~w when formatting ~w", |
| format(error, self(), Msg, [{Tag, Err}, Event]) |
| end. |
| |
| |
| do_format({error, _GL, {Pid, "** Generic server " ++ _, Args}}) -> |
| %% gen_server terminate |
| [Name, LastMsg, State, Reason | Extra] = Args, |
| MsgFmt = "gen_server ~w terminated with reason: ~s~n" ++ |
| " last msg: ~p~n state: ~p~n extra: ~p", |
| MsgArgs = [Name, format_reason(Reason), LastMsg, State, Extra], |
| format(error, Pid, MsgFmt, MsgArgs); |
| |
| do_format({error, _GL, {Pid, "** State machine " ++ _, Args}}) -> |
| %% gen_fsm terminate |
| [Name, LastMsg, StateName, State, Reason | Extra] = Args, |
| MsgFmt = "gen_fsm ~w in state ~w terminated with reason: ~s~n" ++ |
| " last msg: ~p~n state: ~p~n extra: ~p", |
| MsgArgs = [Name, StateName, format_reason(Reason), LastMsg, State, Extra], |
| format(error, Pid, MsgFmt, MsgArgs); |
| |
| do_format({error, _GL, {Pid, "** gen_event handler" ++ _, Args}}) -> |
| %% gen_event handler terminate |
| [ID, Name, LastMsg, State, Reason] = Args, |
| MsgFmt = "gen_event ~w installed in ~w terminated with reason: ~s~n" ++ |
| " last msg: ~p~n state: ~p", |
| MsgArgs = [ID, Name, format_reason(Reason), LastMsg, State], |
| format(error, Pid, MsgFmt, MsgArgs); |
| |
| do_format({error, _GL, {emulator, "~s~n", [Msg]}}) when is_list(Msg) -> |
| % These messages are for whenever any process exits due |
| % to a throw or error. We intercept here to remove the |
| % extra newlines. |
| NewMsg = lists:sublist(Msg, length(Msg) - 1), |
| format(error, emulator, NewMsg); |
| |
| do_format({error, _GL, {Pid, Fmt, Args}}) -> |
| format(error, Pid, Fmt, Args); |
| |
| do_format({error_report, _GL, {Pid, std_error, D}}) -> |
| format(error, Pid, print_silly_list(D)); |
| |
| do_format({error_report, _GL, {Pid, supervisor_report, D}}) -> |
| case lists:sort(D) of |
| [{errorContext, Ctx}, {offender, Off}, |
| {reason, Reason}, {supervisor, Name}] -> |
| Offender = format_offender(Off), |
| MsgFmt = "Supervisor ~w had child ~s exit " ++ |
| "with reason ~s in context ~w", |
| Args = [ |
| supervisor_name(Name), |
| Offender, |
| format_reason(Reason), |
| Ctx |
| ], |
| format(error, Pid, MsgFmt, Args); |
| _ -> |
| format(error, Pid, "SUPERVISOR REPORT " ++ print_silly_list(D)) |
| end; |
| |
| do_format({error_report, _GL, {Pid, crash_report, [Report, Neighbors]}}) -> |
| Msg = "CRASH REPORT " ++ format_crash_report(Report, Neighbors), |
| format(error, Pid, Msg); |
| |
| do_format({warning_msg, _GL, {Pid, Fmt, Args}}) -> |
| format(warning, Pid, Fmt, Args); |
| |
| do_format({warning_report, _GL, {Pid, std_warning, Report}}) -> |
| format(warning, Pid, print_silly_list(Report)); |
| |
| do_format({info_msg, _GL, {Pid, Fmt, Args}}) -> |
| format(info, Pid, Fmt, Args); |
| |
| do_format({info_report, _GL, {Pid, std_info, D}}) when is_list(D) -> |
| case lists:sort(D) of |
| [{application, App}, {exited, Reason}, {type, _Type}] -> |
| MsgFmt = "Application ~w exited with reason: ~s", |
| format(info, Pid, MsgFmt, [App, format_reason(Reason)]); |
| _ -> |
| format(info, Pid, print_silly_list(D)) |
| end; |
| |
| do_format({info_report, _GL, {Pid, std_info, D}}) -> |
| format(info, Pid, "~w", [D]); |
| |
| do_format({info_report, _GL, {Pid, progress, D}}) -> |
| case lists:sort(D) of |
| [{application, App}, {started_at, Node}] -> |
| MsgFmt = "Application ~w started on node ~w", |
| format(info, Pid, MsgFmt, [App, Node]); |
| [{started, Started}, {supervisor, Name}] -> |
| MFA = format_mfa(get_value(mfargs, Started)), |
| ChildPid = get_value(pid, Started), |
| MsgFmt = "Supervisor ~w started ~s at pid ~w", |
| format(debug, Pid, MsgFmt, [supervisor_name(Name), MFA, ChildPid]); |
| _ -> |
| format(info, Pid, "PROGRESS REPORT " ++ print_silly_list(D)) |
| end; |
| |
| do_format(Event) -> |
| format(warning, self(), "Unexpected error_logger event ~w", [Event]). |
| |
| |
| format_crash_report(Report, Neighbours) -> |
| Pid = get_value(pid, Report), |
| Name = case get_value(registered_name, Report) of |
| undefined -> |
| pid_to_list(Pid); |
| Atom -> |
| io_lib:format("~s (~w)", [Atom, Pid]) |
| end, |
| {Class, Reason, Trace} = get_value(error_info, Report), |
| ReasonStr = format_reason({Reason, Trace}), |
| Type = case Class of |
| exit -> "exited"; |
| _ -> "crashed" |
| end, |
| MsgFmt = "Process ~s with ~w neighbors ~s with reason: ~s", |
| Args = [Name, length(Neighbours), Type, ReasonStr], |
| Msg = io_lib:format(MsgFmt, Args), |
| case filter_silly_list(Report, [pid, registered_name, error_info]) of |
| [] -> |
| Msg; |
| Rest -> |
| Msg ++ "; " ++ print_silly_list(Rest) |
| end. |
| |
| |
| format_offender(Off) -> |
| case get_value(mfargs, Off) of |
| undefined -> |
| %% supervisor_bridge |
| Args = [get_value(mod, Off), get_value(pid, Off)], |
| io_lib:format("at module ~w at ~w", Args); |
| MFArgs -> |
| %% regular supervisor |
| MFA = format_mfa(MFArgs), |
| |
| %% In 2014 the error report changed from `name' to |
| %% `id', so try that first. |
| Name = case get_value(id, Off) of |
| undefined -> |
| get_value(name, Off); |
| Id -> |
| Id |
| end, |
| Args = [Name, MFA, get_value(pid, Off)], |
| io_lib:format("~p started with ~s at ~w", Args) |
| end. |
| |
| |
| format_reason({'function not exported', [{M, F, A} | Trace]}) -> |
| ["call to unexported function ", format_mfa({M, F, A}), |
| " at ", format_trace(Trace)]; |
| |
| format_reason({'function not exported' = C, [{M, F, A, _Props} | Rest]}) -> |
| %% Drop line number from undefined function |
| format_reason({C, [{M, F, A} | Rest]}); |
| |
| format_reason({undef, [MFA | Trace]}) -> |
| ["call to undefined function ", format_mfa(MFA), |
| " at ", format_trace(Trace)]; |
| |
| 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), " at ", format_mfa(MFA)]; |
| |
| format_reason({{badrecord, Record}, Trace}) -> |
| ["bad record ", print_val(Record), " at ", format_trace(Trace)]; |
| |
| format_reason({{case_clause, Val}, Trace}) -> |
| ["no case clause matching ", print_val(Val), " at ", format_trace(Trace)]; |
| |
| format_reason({function_clause, [MFA | Trace]}) -> |
| ["no function clause matching ", format_mfa(MFA), |
| " at ", format_trace(Trace)]; |
| |
| format_reason({if_clause, Trace}) -> |
| ["no true branch found while evaluating if expression at ", |
| format_trace(Trace)]; |
| |
| format_reason({{try_clause, Val}, Trace}) -> |
| ["no try clause matching ", print_val(Val), " at ", format_trace(Trace)]; |
| |
| format_reason({badarith, Trace}) -> |
| ["bad arithmetic expression at ", format_trace(Trace)]; |
| |
| format_reason({{badmatch, Val}, Trace}) -> |
| ["no match of right hand value ", print_val(Val), |
| " at ", format_trace(Trace)]; |
| |
| format_reason({emfile, Trace}) -> |
| ["maximum number of file descriptors exhausted, check ulimit -n; ", |
| format_trace(Trace)]; |
| |
| format_reason({system_limit, [{M, F, A} | 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"; |
| _ -> |
| format_mfa({M, F, A}) |
| end, |
| ["system limit: ", Limit, " at ", format_trace(Trace)]; |
| |
| format_reason({badarg, [MFA | Trace]}) -> |
| ["bad argument in call to ", format_mfa(MFA), |
| " at ", format_trace(Trace)]; |
| |
| format_reason({{badarg, Stack}, _}) -> |
| format_reason({badarg, Stack}); |
| |
| format_reason({{badarity, {Fun, Args}}, Trace}) -> |
| {arity, Arity} = lists:keyfind(arity, 1, erlang:fun_info(Fun)), |
| MsgFmt = "function called with wrong arity of ~w instead of ~w at ", |
| [io_lib:format(MsgFmt, [length(Args), Arity]), format_trace(Trace)]; |
| |
| format_reason({noproc, MFA}) -> |
| ["no such process or port in call to ", format_mfa(MFA)]; |
| |
| format_reason({{badfun, Term}, Trace}) -> |
| ["bad function ", print_val(Term), " called at ", format_trace(Trace)]; |
| |
| format_reason({Reason, [{M, F, A} | _] = Trace}) |
| when is_atom(M), is_atom(F), is_integer(A) -> |
| [format_reason(Reason), " at ", format_trace(Trace)]; |
| |
| format_reason({Reason, [{M, F, A} | _] = Trace}) |
| when is_atom(M), is_atom(F), is_list(A) -> |
| [format_reason(Reason), " at ", format_trace(Trace)]; |
| |
| format_reason({Reason, [{M, F, A, Props} | _] = Trace}) |
| when is_atom(M), is_atom(F), is_integer(A), is_list(Props) -> |
| [format_reason(Reason), " at ", format_trace(Trace)]; |
| |
| format_reason({Reason, [{M, F, A, Props} | _] = Trace}) |
| when is_atom(M), is_atom(F), is_list(A), is_list(Props) -> |
| [format_reason(Reason), " at ", format_trace(Trace)]; |
| |
| format_reason(Reason) -> |
| {Str, _} = couch_log_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 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(Trace) when is_list(Trace) -> |
| format_trace(Trace); |
| |
| format_mfa(Other) -> |
| io_lib:format("~w", [Other]). |
| |
| |
| format_trace([MFA]) -> |
| [trace_mfa(MFA)]; |
| |
| format_trace([MFA | Rest]) -> |
| [trace_mfa(MFA), " <= ", format_trace(Rest)]; |
| |
| format_trace(Other) -> |
| io_lib:format("~w", [Other]). |
| |
| |
| trace_mfa({M, F, A}) when is_list(A) -> |
| format_mfa({M, F, length(A)}); |
| |
| trace_mfa({M, F, A, Props}) when is_list(A) -> |
| format_mfa({M, F, length(A), Props}); |
| |
| trace_mfa(Other) -> |
| format_mfa(Other). |
| |
| |
| format_args([], FmtAcc, ArgsAcc) -> |
| {string:join(lists:reverse(FmtAcc), ", "), lists:reverse(ArgsAcc)}; |
| |
| format_args([H|T], FmtAcc, ArgsAcc) -> |
| {Str, _} = couch_log_trunc_io:print(H, 100), |
| format_args(T, ["~s" | FmtAcc], [Str | ArgsAcc]). |
| |
| |
| maybe_truncate(Fmt, Args) -> |
| MaxMsgSize = couch_log_config:get(max_message_size), |
| couch_log_trunc_io:format(Fmt, Args, MaxMsgSize). |
| |
| |
| maybe_truncate(Msg) -> |
| MaxMsgSize = couch_log_config:get(max_message_size), |
| case iolist_size(Msg) > MaxMsgSize of |
| true -> |
| MsgBin = iolist_to_binary(Msg), |
| PrefixSize = MaxMsgSize - 3, |
| <<Prefix:PrefixSize/binary, _/binary>> = MsgBin, |
| [Prefix, "..."]; |
| false -> |
| Msg |
| end. |
| |
| |
| print_silly_list(L) when is_list(L) -> |
| case couch_log_util:string_p(L) of |
| true -> |
| couch_log_trunc_io:format("~s", [L], ?DEFAULT_TRUNCATION); |
| _ -> |
| print_silly_list(L, [], []) |
| end; |
| |
| print_silly_list(L) -> |
| {Str, _} = couch_log_trunc_io:print(L, ?DEFAULT_TRUNCATION), |
| Str. |
| |
| |
| print_silly_list([], Fmt, Acc) -> |
| couch_log_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, _} = couch_log_trunc_io:print(Val, 500), |
| Str. |
| |
| |
| filter_silly_list([], _) -> |
| []; |
| |
| filter_silly_list([{K, V} | T], Filter) -> |
| case lists:member(K, Filter) of |
| true -> |
| filter_silly_list(T, Filter); |
| false -> |
| [{K, V} | filter_silly_list(T, Filter)] |
| end; |
| |
| filter_silly_list([H | T], Filter) -> |
| [H | filter_silly_list(T, Filter)]. |
| |
| |
| get_value(Key, Value) -> |
| get_value(Key, Value, undefined). |
| |
| get_value(Key, List, Default) -> |
| case lists:keyfind(Key, 1, List) of |
| false -> Default; |
| {Key, Value} -> Value |
| end. |
| |
| supervisor_name({local, Name}) -> Name; |
| supervisor_name(Name) -> Name. |