blob: dee014b87b721cc36723272a0429c33f5374ac3e [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, []},
74 {gen_server_messages, true},
75 {lookup, false}
76 ], %% configuration before we try loading from file
Christopher Piro5b3a8f72007-08-01 22:27:37 +000077
Christopher Piro68940292007-10-02 00:35:12 +000078 State = #state{config=BootConfig},
Christopher Piro5b3a8f72007-08-01 22:27:37 +000079 {ok, State}.
80
81%%--------------------------------------------------------------------
82%% @spec handle_event(Event, State) -> {ok, State} |
83%% {swap_handler, Args1, State1, Mod2, Args2} |
84%% remove_handler.
85%%
86%% @doc
87%% Whenever an event manager receives an event sent using
88%% gen_event:notify/2 or gen_event:sync_notify/2, this function is called for
89%% each installed event handler to handle the event.
90%% @end
91%%--------------------------------------------------------------------
92handle_event2(Symbol, Pid, Type, Message, State) -> % Message must be a string
93 {ok, MessageSafe, NL} = regexp:gsub(Message, "[\n]+", " "), % collapse whitespace to one space
94
95 Type1 =
96 case Type of
97 "" -> "";
98 _ -> sformat("~p ", [Type])
99 end,
100
101 Banner = sformat("~s ~p ~s", [Symbol, Pid, Type1]),
102 BannerLen = length(Banner),
103 {Output, OutputSafe} =
104 try %% there's no way to see if Message is a string? just try
105 {sformat("~s", [Message]),
106 sformat("~s", [MessageSafe])}
107 catch X -> why_doesnt_this_work
108 end,
109
110 Length =
Christopher Piro68940292007-10-02 00:35:12 +0000111 case (length(OutputSafe) + BannerLen) < ?CONFIG(term_width) of
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000112 true -> short;
113 false -> long
114 end,
115
116 OneLine =
117 case NL == 0 of
118 true -> oneliner;
119 false -> multiline
120 end,
121
Christopher Piro68940292007-10-02 00:35:12 +0000122 case { ?CONFIG(force_one_line), Length, OneLine } of
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000123 %% one line and short ... print as is
124 {_, short, oneliner} ->
125 format("~s~s~n", [Banner, OutputSafe]);
126
127 %% too long ... squash to one
128 {true, long, _} ->
129 O = Banner ++ OutputSafe,
Christopher Piro68940292007-10-02 00:35:12 +0000130 Format = sformat("~~~ps >~n", [?CONFIG(term_width)-2]), % e.g. "~80s >~n"
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000131 format(Format, [O]);
132
133 %% short but multiline... collapse to one
134 {true, short, multiline} ->
135 format("~s~s~n", [Banner, OutputSafe]);
136
137 %% just print it
138 _ ->
139 format("~s~n~s~n~n", [Banner, Output])
140 end.
141
142%%
Christopher Piro524c3ec2007-10-13 05:15:33 +0000143-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").
144
Christopher Piroefd5eec2007-10-02 01:33:37 +0000145handle_event1({What, _Gleader, {Ref, Format, Data}}, State) when is_list(Format) ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000146 Symbol = case What of
147 error -> "!!";
148 warning_msg -> "**";
149 info_msg -> "..";
150 _Else -> "??"
151 end,
152
Christopher Piro524c3ec2007-10-13 05:15:33 +0000153 case {Format, Data} of
154 {?GS_TERM_FORMAT, [Ref, LastMessage, Obj, Reason]} ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000155 %% TODO: move as much logic as possible out of thrift_logger
Christopher Pirofa0c8572007-08-11 01:15:57 +0000156 Ignore =
157 begin
158 is_tuple(Reason) andalso
159 size(Reason) >= 1 andalso element(1, Reason) == timeout
160 end
161 orelse
162 begin
163 case thrift_utils:unnest_record(Reason, tTransportException) of
164 error -> false;
165 {ok, TTE} ->
166 oop:get(TTE, type) == ?tTransportException_NOT_OPEN andalso
167 oop:get(TTE, message) == "in tSocket:read/2: gen_tcp:recv"
168 end
169 end,
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000170
171 case Ignore of
172 true ->
173 ok;
174 false ->
175 Format1 = "** gen_server terminating in message ~p~n** State = ~s~n** Reason = ~s~n",
176 Message = sformat(Format1, [LastMessage, oop:inspect(Obj), oop:inspect(Reason)]), %% TODO(cpiro): hope Reason is an object?
Christopher Piro68940292007-10-02 00:35:12 +0000177 handle_event2(Symbol, Ref, "", Message, State)
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000178 end;
Christopher Piro524c3ec2007-10-13 05:15:33 +0000179 {?GS_TERM_FORMAT, _Dta} ->
180 Message = sformat("DATA DIDN'T MATCH: ~p~n", [Data]) ++ sformat(Format, Data),
181 handle_event2(Symbol, Ref, "", Message, State);
182 {_Fmt, _Dta} ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000183 Message = sformat(Format, Data),
Christopher Piro68940292007-10-02 00:35:12 +0000184 handle_event2(Symbol, Ref, "", Message, State)
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000185 end,
186 {ok, State};
187
188handle_event1({What, _Gleader, {Pid, Type, Report}}, State) ->
189 Symbol = case What of
190 error_report -> "!!";
191 warning_report -> "**";
192 info_report -> "..";
193 _Else -> "??"
194 end,
195
196 case Type of
197 {thrift_info, TI} ->
198 %% should we show it?
Christopher Piro68940292007-10-02 00:35:12 +0000199 case not lists:member(TI, ?CONFIG(omit)) of
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000200 true ->
201 Message = handle_thrift_info(TI, Report, State),
202 handle_event2(Symbol, Pid, "", Message, State);
203 false ->
204 ok
205 end;
206 crash_report ->
207 %% [Cruft|_] = Report, %%
208 %% {value, {_, Reason}} = lists:keysearch(error_info, 1, Cruft), %%
209 %% {value, {_, {_, _, [_,_,_,_, Obj, []]}}} = lists:keysearch(initial_call, 1, Cruft), %%
210 %% sformat("state == ~s~nreason ==~s", [oop:inspect(Obj), oop:inspect(Reason)]), %%
211 ok;
212 progress ->
213 ok;
214
215 _ ->
216 Message = sformat("|| ~s", [oop:inspect(Report)]),
217 handle_event2(Symbol, Pid, Type, Message, State)
218 end,
219 {ok, State};
220
221handle_event1(_Event, State) ->
222 handle_event2("??", "<?.?.?>", "", _Event, State),
223 {ok, State}.
224
225handle_event(Event, State) ->
226 try
227 handle_event1(Event, State)
228 catch
229 _:E ->
230 format("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~n error logger error:~n ~p~n Event = ~p~n State = ~p~n ~p~n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~n",
231 [E, Event, State, erlang:get_stacktrace()]),
232 {ok, State}
233 end.
234
235%% thrift info handlers
236handle_thrift_info(oop_new, {Args, Class, Object}, State) ->
237 %% arg Class can't come first! Then it'd look like a Class object
238 L = io_lib:format("~p:new(~s) = ~s", [Class, thrift_utils:unbrack(Args), oop:inspect(Object)]),
239 lists:flatten(L);
240
241handle_thrift_info(server_listening, {Port}, State) ->
242 sformat("server listening on port ~p", [Port]);
243
244handle_thrift_info(req_processed, {Value}, State) ->
245 sformat("request: ~p", [Value]);
246
247handle_thrift_info(conn_accepted, {AddrString}, State) ->
248 sformat("connection accepted from ~s", [AddrString]);
249
Christopher Piroee598842007-08-03 23:34:55 +0000250handle_thrift_info(conn_timeout, {AddrString}, State) ->
251 sformat("connection timed out from ~s", [AddrString]);
252
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000253handle_thrift_info(conn_closed, {AddrString}, State) ->
254 sformat("connection closed from ~s", [AddrString]);
255
256handle_thrift_info(Else, Report, State) ->
257 sformat("~p ~s", [Else, oop:inspect(Report)]).
258
259%%--------------------------------------------------------------------
260%% @spec handle_call(Request, State) -> {ok, Reply, State} |
Christopher Piro68940292007-10-02 00:35:12 +0000261%% {swap_handler, Reply, Args1, State1,
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000262%% Mod2, Args2} |
263%% {remove_handler, Reply}.
264%%
265%% @doc
266%% Whenever an event manager receives a request sent using
267%% gen_event:call/3,4, this function is called for the specified event
268%% handler to handle the request.
269%% @end
270%%--------------------------------------------------------------------
Christopher Piro68940292007-10-02 00:35:12 +0000271handle_call({reconfig, Amendments}, State) ->
272 {OkOrError, State1} = reconfig1(State, Amendments),
273 format(".. reconfig was ~p~n", [OkOrError]),
274 {ok, OkOrError, State1};
275
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000276handle_call(_Request, State) ->
277 Reply = ok,
278 {ok, Reply, State}.
279
280%%--------------------------------------------------------------------
281%% @spec handle_info(Info, State) -> {ok, State} |
282%% {swap_handler, Args1, State1, Mod2, Args2} |
283%% remove_handler.
284%%
285%% @doc
286%% This function is called for each installed event handler when
287%% an event manager receives any other message than an event or a synchronous
288%% request (or a system message).
289%% @end
290%%--------------------------------------------------------------------
291handle_info(_Info, State) ->
292 {ok, State}.
293
294%%--------------------------------------------------------------------
295%% @spec terminate(Reason, State) -> void().
296%%
297%% @doc
298%% Whenever an event handler is deleted from an event manager,
299%% this function is called. It should be the opposite of Module:init/1 and
300%% do any necessary cleaning up.
301%% @end
302%%--------------------------------------------------------------------
303terminate(normal, _State) ->
304 ok;
305terminate(Reason, _State) ->
306 format("*****************~n~n frick, error logger terminating: ~p~n~n*****************~n~n", [Reason]),
307 ok.
308
309%%--------------------------------------------------------------------
310%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}.
311%%
312%% @doc
313%% Convert process state when code is changed
314%% @end
315%%--------------------------------------------------------------------
316code_change(_OldVsn, State, _Extra) ->
317 {ok, State}.
318
319%%====================================================================
320%%% Internal functions
321%%====================================================================
Christopher Piro68940292007-10-02 00:35:12 +0000322
323%% how to output
324format(Format, Data) ->
325 io:format(Format, Data).
326
327%% convenience
328sformat(Format, Data) ->
329 thrift_utils:sformat(Format, Data).
330
331reconfig1(State, Amendments) ->
332 case thrift:config(thrift_logger) of
333 {value, Config} ->
334 Config1 = lists:keysort(1, Config),
335 Amendments1 = lists:keysort(1, Amendments),
336 Config2 = lists:keymerge(1, Amendments1, Config1),
337
338 State1 = State#state{config=Config2},
339 {ok, State1};
340 _ ->
341 {error, State}
342 end.
343
344config(Item, State) ->
345 case thrift:config(Item, State#state.config) of
346 {value, V} ->
347 V;
348 Else ->
349 ?ERROR("config for ~p is unavailable: ~p", [Item, Else])
350 end.