%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2017. All Rights Reserved. %% %% 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. %% %% %CopyrightEnd% %% -module(error_logger_tty_h). -behaviour(gen_event). %%% %%% A handler that can be connected to the error_logger %%% event handler. Writes all events formatted to stdout. %%% %%% It can only be started from error_logger:swap_handler(tty) %%% or error_logger:tty(true). %%% -export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, code_change/3]). -export([write_event/2,write_event/3]). -record(st, {user, prev_handler, io_mod=io, depth=unlimited, modifier=""}). %% This one is used when we takeover from the simple error_logger. init({[], {error_logger, Buf}}) -> User = set_group_leader(), Depth = error_logger:get_format_depth(), Modifier = modifier(), State = #st{user=User,prev_handler=error_logger, depth=Depth,modifier=Modifier}, write_events(State, Buf), {ok, State}; %% This one is used if someone took over from us, and now wants to %% go back. init({[], {error_logger_tty_h, PrevHandler}}) -> User = set_group_leader(), {ok, #st{user=User,prev_handler=PrevHandler}}; %% This one is used when we are started directly. init([]) -> User = set_group_leader(), Depth = error_logger:get_format_depth(), Modifier = modifier(), {ok, #st{user=User,prev_handler=[],depth=Depth,modifier=Modifier}}. handle_event({_Type, GL, _Msg}, State) when node(GL) =/= node() -> {ok, State}; handle_event(Event, State) -> ok = do_write_event(State, tag_event(Event)), {ok, State}. handle_info({'EXIT', User, _Reason}, #st{user=User,prev_handler=PrevHandler}=State) -> case PrevHandler of [] -> remove_handler; _ -> {swap_handler, install_prev, State, PrevHandler, go_back} end; handle_info(_, State) -> {ok, State}. handle_call(_Query, State) -> {ok, {error, bad_query}, State}. terminate(install_prev, _State) -> []; terminate(_Reason, #st{prev_handler=PrevHandler}) -> {error_logger_tty_h, PrevHandler}. code_change(_OldVsn, State, _Extra) -> {ok, State}. %% Exported (but unoffical) API. write_event(Event, IoMod) -> do_write_event(#st{io_mod=IoMod}, Event). write_event(Event, IoMod, {Depth, Enc}) -> Modifier = modifier(Enc), do_write_event(#st{io_mod=IoMod,depth=Depth,modifier=Modifier}, Event). %%% ------------------------------------------------------ %%% Misc. functions. %%% ------------------------------------------------------ set_group_leader() -> case whereis(user) of User when is_pid(User) -> link(User), group_leader(User,self()), User; _ -> false end. tag_event(Event) -> {erlang:universaltime(), Event}. write_events(State, [Ev|Es]) -> %% Write the events in reverse order. _ = write_events(State, Es), _ = do_write_event(State, Ev), ok; write_events(_State, []) -> ok. do_write_event(#st{modifier=M}=State, {Time, Event}) -> case parse_event(Event,M) of ignore -> ok; {Title,Pid,FormatList} -> Header = header(Time, Title, M), Body = format_body(State, FormatList), AtNode = if node(Pid) =/= node() -> ["** at node ",atom_to_list(node(Pid))," **\n"]; true -> [] end, Str = [Header,AtNode,Body], case State#st.io_mod of io_lib -> Str; io -> io:put_chars(user, Str) end end; do_write_event(_, _) -> ok. format_body(#st{modifier=M}=State, [{Format,Args}|T]) -> S = try format(State, Format, Args) of S0 -> S0 catch _:_ -> format(State, "ERROR: ~"++M++"p - ~"++M++"p\n", [Format,Args]) end, [S|format_body(State, T)]; format_body(_State, []) -> []. format(#st{depth=unlimited}, Format, Args) -> io_lib:format(Format, Args); format(#st{depth=Depth}, Format0, Args) -> Format1 = io_lib:scan_format(Format0, Args), Format = limit_format(Format1, Depth), io_lib:build_text(Format). limit_format([#{control_char:=C0}=M0|T], Depth) when C0 =:= $p; C0 =:= $w -> C = C0 - ($a - $A), %To uppercase. #{args:=Args} = M0, M = M0#{control_char:=C,args:=Args++[Depth]}, [M|limit_format(T, Depth)]; limit_format([H|T], Depth) -> [H|limit_format(T, Depth)]; limit_format([], _) -> []. parse_event({error, _GL, {Pid, Format, Args}},_) -> {"ERROR REPORT",Pid,[{Format,Args}]}; parse_event({info_msg, _GL, {Pid, Format, Args}},_) -> {"INFO REPORT",Pid,[{Format, Args}]}; parse_event({warning_msg, _GL, {Pid, Format, Args}},_) -> {"WARNING REPORT",Pid,[{Format,Args}]}; parse_event({error_report, _GL, {Pid, std_error, Args}},M) -> {"ERROR REPORT",Pid,format_term(Args,M)}; parse_event({info_report, _GL, {Pid, std_info, Args}},M) -> {"INFO REPORT",Pid,format_term(Args,M)}; parse_event({warning_report, _GL, {Pid, std_warning, Args}},M) -> {"WARNING REPORT",Pid,format_term(Args,M)}; parse_event(_,_) -> ignore. format_term(Term,M) when is_list(Term) -> case string_p(lists:flatten(Term)) of true -> [{"~"++M++"s\n",[Term]}]; false -> format_term_list(Term,M) end; format_term(Term,M) -> [{"~"++M++"p\n",[Term]}]. format_term_list([{Tag,Data}|T],M) -> [{" ~"++M++"p: ~"++M++"p\n",[Tag,Data]}|format_term_list(T,M)]; format_term_list([Data|T],M) -> [{" ~"++M++"p\n",[Data]}|format_term_list(T,M)]; format_term_list([],_) -> []. string_p([]) -> false; string_p(FlatList) -> io_lib:printable_list(FlatList). get_utc_config() -> %% SASL utc_log configuration overrides stdlib config %% in order to have uniform timestamps in log messages case application:get_env(sasl, utc_log) of {ok, Val} -> Val; undefined -> case application:get_env(stdlib, utc_log) of {ok, Val} -> Val; undefined -> false end end. header(Time, Title, M) -> case get_utc_config() of true -> header(Time, Title, "UTC ", M); _ -> header(calendar:universal_time_to_local_time(Time), Title, "", M) end. header({{Y,Mo,D},{H,Mi,S}}, Title, UTC, M) -> io_lib:format("~n=~"++M++"s==== ~p-~s-~p::~s:~s:~s ~s===~n", [Title,D,month(Mo),Y,t(H),t(Mi),t(S),UTC]). t(X) when is_integer(X) -> t1(integer_to_list(X)); t(_) -> "". t1([X]) -> [$0,X]; t1(X) -> X. month(1) -> "Jan"; month(2) -> "Feb"; month(3) -> "Mar"; month(4) -> "Apr"; month(5) -> "May"; month(6) -> "Jun"; month(7) -> "Jul"; month(8) -> "Aug"; month(9) -> "Sep"; month(10) -> "Oct"; month(11) -> "Nov"; month(12) -> "Dec". modifier() -> modifier(encoding()). modifier(latin1) -> ""; modifier(_) -> "t". encoding() -> proplists:get_value(encoding,io:getopts(),latin1).