blob: 8528da3750821009a422ef7b627d2ea839d2bce8 [file] [log] [blame]
Christopher Piro5b3a8f72007-08-01 22:27:37 +00001%%% Copyright (c) 2007- Facebook
2%%% Distributed under the Thrift Software License
3%%%
4%%% See accompanying file LICENSE or visit the Thrift site at:
5%%% http://developers.facebook.com/thrift/
6
7-module(thrift_logger).
8
9-behaviour(gen_event).
10
Christopher Piro68940292007-10-02 00:35:12 +000011-record(state, {config}).
12
13-define(CONFIG(Item), config(Item, State)).
Christopher Piro5b3a8f72007-08-01 22:27:37 +000014
Christopher Pirofa0c8572007-08-11 01:15:57 +000015%% TODO(cpiro): either
16%% make exceptions know whether they need to be displayed
17%% or not exit with tExecptions for non-errors
18%% or "register" tExceptions with the logger (I LIKE!)
19%% ... we shouldn't need to build any specifics in here
20-include("thrift.hrl").
21-include("oop.hrl").
22-include("transport/tTransportException.hrl").
23
Christopher Piro5b3a8f72007-08-01 22:27:37 +000024%% gen_event callbacks
25-export([init/1, handle_event/2, handle_call/2,
26 handle_info/2, terminate/2, code_change/3]).
27
Christopher Piro68940292007-10-02 00:35:12 +000028-export([install/0, install/1, reconfig/0, reconfig/1]).
Christopher Piro5b3a8f72007-08-01 22:27:37 +000029
30%% ensure the regular logger is out and ours is in
31install() ->
32 install([]).
Christopher Piro68940292007-10-02 00:35:12 +000033install(Config) ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +000034 %% remove loggers
Christopher Piro68940292007-10-02 00:35:12 +000035 io:format("starting logger~n"),
Christopher Piro5b3a8f72007-08-01 22:27:37 +000036 lists:foreach(fun(Logger) ->
37 case Logger of
38 _ -> gen_event:delete_handler(error_logger, Logger, normal)
39 end end,
40 gen_event:which_handlers(error_logger)),
41
42 %% TODO(cpiro): sasl someday?
43 %% gen_event:add_handler(error_logger, sasl_report_file_h, {LogFile, all}),
Christopher Piro5b3a8f72007-08-01 22:27:37 +000044
Christopher Piro68940292007-10-02 00:35:12 +000045 gen_event:add_handler(error_logger, ?MODULE, []),
Christopher Piro5b3a8f72007-08-01 22:27:37 +000046
Christopher Piro68940292007-10-02 00:35:12 +000047 reconfig(Config),
48
49 ok.
50
51%% load our config file and amend the given stuff
52reconfig() ->
53 reconfig([]).
54
55reconfig(Config) ->
56 gen_event:call(error_logger, ?MODULE, {reconfig, Config}).
Christopher Piro5b3a8f72007-08-01 22:27:37 +000057
58%%====================================================================
59%% gen_event callbacks
60%%====================================================================
61%%--------------------------------------------------------------------
62%% @spec init(Args) -> {ok, State}.
63%%
64%% @doc
65%% Whenever a new event handler is added to an event manager,
66%% this function is called to initialize the event handler.
67%% @end
68%%--------------------------------------------------------------------
69init([]) ->
Christopher Piro68940292007-10-02 00:35:12 +000070 BootConfig = [
71 {term_width, 80},
72 {force_one_line, false},
73 {omit, []},
Christopher Piro3b63fe42007-10-19 21:34:31 +000074 {omit_fmt, []},
75 {show_pid, false},
Christopher Piro68940292007-10-02 00:35:12 +000076 {gen_server_messages, true},
77 {lookup, false}
78 ], %% configuration before we try loading from file
Christopher Piro5b3a8f72007-08-01 22:27:37 +000079
Christopher Piro68940292007-10-02 00:35:12 +000080 State = #state{config=BootConfig},
Christopher Piro5b3a8f72007-08-01 22:27:37 +000081 {ok, State}.
82
83%%--------------------------------------------------------------------
84%% @spec handle_event(Event, State) -> {ok, State} |
85%% {swap_handler, Args1, State1, Mod2, Args2} |
86%% remove_handler.
87%%
88%% @doc
89%% Whenever an event manager receives an event sent using
90%% gen_event:notify/2 or gen_event:sync_notify/2, this function is called for
91%% each installed event handler to handle the event.
92%% @end
93%%--------------------------------------------------------------------
94handle_event2(Symbol, Pid, Type, Message, State) -> % Message must be a string
95 {ok, MessageSafe, NL} = regexp:gsub(Message, "[\n]+", " "), % collapse whitespace to one space
96
97 Type1 =
98 case Type of
99 "" -> "";
100 _ -> sformat("~p ", [Type])
101 end,
102
Christopher Piro3b63fe42007-10-19 21:34:31 +0000103 Banner =
104 case ?CONFIG(show_pid) of
105 true ->
106 sformat("~s ~s ~s", [Symbol, Pid, Type1]);
107 false ->
108 sformat("~s~i ~s", [Symbol, Pid, Type1])
109 end,
110 BannerLen = length(Banner),
111
112 %% there's no way to see if Message is a string? just try
113 Output = sformat("~s", [Message]),
114 OutputSafe = sformat("~s", [MessageSafe]),
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000115
116 Length =
Christopher Piro68940292007-10-02 00:35:12 +0000117 case (length(OutputSafe) + BannerLen) < ?CONFIG(term_width) of
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000118 true -> short;
119 false -> long
120 end,
121
122 OneLine =
123 case NL == 0 of
124 true -> oneliner;
125 false -> multiline
126 end,
127
Christopher Piro68940292007-10-02 00:35:12 +0000128 case { ?CONFIG(force_one_line), Length, OneLine } of
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000129 %% one line and short ... print as is
130 {_, short, oneliner} ->
131 format("~s~s~n", [Banner, OutputSafe]);
132
133 %% too long ... squash to one
134 {true, long, _} ->
135 O = Banner ++ OutputSafe,
Christopher Piro68940292007-10-02 00:35:12 +0000136 Format = sformat("~~~ps >~n", [?CONFIG(term_width)-2]), % e.g. "~80s >~n"
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000137 format(Format, [O]);
138
139 %% short but multiline... collapse to one
140 {true, short, multiline} ->
141 format("~s~s~n", [Banner, OutputSafe]);
142
143 %% just print it
144 _ ->
145 format("~s~n~s~n~n", [Banner, Output])
146 end.
147
148%%
Christopher Piro524c3ec2007-10-13 05:15:33 +0000149-define(GS_TERM_FORMAT, "** Generic server ~p terminating \n** Last message in was ~p~n** When Server state == ~p~n** Reason for termination == ~n** ~p~n").
150
Christopher Piroefd5eec2007-10-02 01:33:37 +0000151handle_event1({What, _Gleader, {Ref, Format, Data}}, State) when is_list(Format) ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000152 Symbol = case What of
153 error -> "!!";
154 warning_msg -> "**";
155 info_msg -> "..";
156 _Else -> "??"
157 end,
158
Christopher Piro524c3ec2007-10-13 05:15:33 +0000159 case {Format, Data} of
160 {?GS_TERM_FORMAT, [Ref, LastMessage, Obj, Reason]} ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000161 %% TODO: move as much logic as possible out of thrift_logger
Christopher Pirofa0c8572007-08-11 01:15:57 +0000162 Ignore =
163 begin
164 is_tuple(Reason) andalso
165 size(Reason) >= 1 andalso element(1, Reason) == timeout
166 end
167 orelse
168 begin
169 case thrift_utils:unnest_record(Reason, tTransportException) of
170 error -> false;
171 {ok, TTE} ->
172 oop:get(TTE, type) == ?tTransportException_NOT_OPEN andalso
173 oop:get(TTE, message) == "in tSocket:read/2: gen_tcp:recv"
174 end
175 end,
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000176
177 case Ignore of
178 true ->
179 ok;
180 false ->
181 Format1 = "** gen_server terminating in message ~p~n** State = ~s~n** Reason = ~s~n",
182 Message = sformat(Format1, [LastMessage, oop:inspect(Obj), oop:inspect(Reason)]), %% TODO(cpiro): hope Reason is an object?
Christopher Piro68940292007-10-02 00:35:12 +0000183 handle_event2(Symbol, Ref, "", Message, State)
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000184 end;
Christopher Piro524c3ec2007-10-13 05:15:33 +0000185 {?GS_TERM_FORMAT, _Dta} ->
186 Message = sformat("DATA DIDN'T MATCH: ~p~n", [Data]) ++ sformat(Format, Data),
187 handle_event2(Symbol, Ref, "", Message, State);
Christopher Piro3b63fe42007-10-19 21:34:31 +0000188 {_, _} ->
189 case lists:member(Format, ?CONFIG(omit_fmt)) of
190 false ->
191 Message = sformat(Format, Data),
192 handle_event2(Symbol, Ref, "", Message, State);
193 true ->
194 ok
195 end
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000196 end,
197 {ok, State};
198
199handle_event1({What, _Gleader, {Pid, Type, Report}}, State) ->
200 Symbol = case What of
201 error_report -> "!!";
202 warning_report -> "**";
203 info_report -> "..";
204 _Else -> "??"
205 end,
206
207 case Type of
Christopher Piro3b63fe42007-10-19 21:34:31 +0000208 crash_report ->
209 case do_print_crash_report(Report) of
210 true ->
211 io:format("~~~~ crash report: '~p'~n", [Report]);
212 false ->
213 ok
214 end;
215%% crash_report ->
216%% [Cruft|_] = Report,
217%% {value, {_, Reason}} = lists:keysearch(error_info, 1, Cruft),
218%% {value, {_, {_, _, [_,_,_,_, Obj, []]}}} = lists:keysearch(initial_call, 1, Cruft),
219%% sformat("state == ~s~nreason ==~s", [oop:inspect(Obj), oop:inspect(Reason)]),
220%% ok;
221 progress ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000222 ok;
223
224 _ ->
225 Message = sformat("|| ~s", [oop:inspect(Report)]),
226 handle_event2(Symbol, Pid, Type, Message, State)
227 end,
228 {ok, State};
229
230handle_event1(_Event, State) ->
231 handle_event2("??", "<?.?.?>", "", _Event, State),
232 {ok, State}.
233
234handle_event(Event, State) ->
235 try
236 handle_event1(Event, State)
237 catch
238 _:E ->
239 format("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~n error logger error:~n ~p~n Event = ~p~n State = ~p~n ~p~n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~n",
240 [E, Event, State, erlang:get_stacktrace()]),
241 {ok, State}
242 end.
243
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000244%%--------------------------------------------------------------------
245%% @spec handle_call(Request, State) -> {ok, Reply, State} |
Christopher Piro68940292007-10-02 00:35:12 +0000246%% {swap_handler, Reply, Args1, State1,
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000247%% Mod2, Args2} |
248%% {remove_handler, Reply}.
249%%
250%% @doc
251%% Whenever an event manager receives a request sent using
252%% gen_event:call/3,4, this function is called for the specified event
253%% handler to handle the request.
254%% @end
255%%--------------------------------------------------------------------
Christopher Piro68940292007-10-02 00:35:12 +0000256handle_call({reconfig, Amendments}, State) ->
257 {OkOrError, State1} = reconfig1(State, Amendments),
258 format(".. reconfig was ~p~n", [OkOrError]),
259 {ok, OkOrError, State1};
260
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000261handle_call(_Request, State) ->
262 Reply = ok,
263 {ok, Reply, State}.
264
265%%--------------------------------------------------------------------
266%% @spec handle_info(Info, State) -> {ok, State} |
267%% {swap_handler, Args1, State1, Mod2, Args2} |
268%% remove_handler.
269%%
270%% @doc
271%% This function is called for each installed event handler when
272%% an event manager receives any other message than an event or a synchronous
273%% request (or a system message).
274%% @end
275%%--------------------------------------------------------------------
276handle_info(_Info, State) ->
277 {ok, State}.
278
279%%--------------------------------------------------------------------
280%% @spec terminate(Reason, State) -> void().
281%%
282%% @doc
283%% Whenever an event handler is deleted from an event manager,
284%% this function is called. It should be the opposite of Module:init/1 and
285%% do any necessary cleaning up.
286%% @end
287%%--------------------------------------------------------------------
288terminate(normal, _State) ->
289 ok;
290terminate(Reason, _State) ->
291 format("*****************~n~n frick, error logger terminating: ~p~n~n*****************~n~n", [Reason]),
292 ok.
293
294%%--------------------------------------------------------------------
295%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}.
296%%
297%% @doc
298%% Convert process state when code is changed
299%% @end
300%%--------------------------------------------------------------------
301code_change(_OldVsn, State, _Extra) ->
302 {ok, State}.
303
304%%====================================================================
305%%% Internal functions
306%%====================================================================
Christopher Piro68940292007-10-02 00:35:12 +0000307
308%% how to output
309format(Format, Data) ->
310 io:format(Format, Data).
311
312%% convenience
313sformat(Format, Data) ->
314 thrift_utils:sformat(Format, Data).
315
316reconfig1(State, Amendments) ->
317 case thrift:config(thrift_logger) of
318 {value, Config} ->
319 Config1 = lists:keysort(1, Config),
320 Amendments1 = lists:keysort(1, Amendments),
321 Config2 = lists:keymerge(1, Amendments1, Config1),
322
323 State1 = State#state{config=Config2},
324 {ok, State1};
325 _ ->
326 {error, State}
327 end.
328
329config(Item, State) ->
330 case thrift:config(Item, State#state.config) of
331 {value, V} ->
332 V;
333 Else ->
334 ?ERROR("config for ~p is unavailable: ~p", [Item, Else])
335 end.
Christopher Piro3b63fe42007-10-19 21:34:31 +0000336
337do_print_crash_report(Report) ->
338 case Report of
339 [[_,_,{error_info, XXX}|_] | _] ->
340 case thrift_utils:first_item(XXX) of
341 tTransportException ->
342 false;
343 _ ->
344 true
345 end;
346 _ ->
347 true
348 end.