blob: 6deb222b80361b2e0d15fe89eb7b3abb29d6c354 [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%%
143handle_event1({What, _Gleader, {Pid, Format, Data}}, State) when is_list(Format) ->
144 Symbol = case What of
145 error -> "!!";
146 warning_msg -> "**";
147 info_msg -> "..";
148 _Else -> "??"
149 end,
150
151 case Format of
152 "** Generic server ~p terminating \n** Last message in was ~p~n** When Server state == ~p~n** Reason for termination == ~n** ~p~n" ->
153 %% v- Pid is a pattern match, not a bind
Christopher Piro68940292007-10-02 00:35:12 +0000154 [Ref, LastMessage, Obj, Reason] = Data,
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000155
156 %% TODO: move as much logic as possible out of thrift_logger
Christopher Pirofa0c8572007-08-11 01:15:57 +0000157 Ignore =
158 begin
159 is_tuple(Reason) andalso
160 size(Reason) >= 1 andalso element(1, Reason) == timeout
161 end
162 orelse
163 begin
164 case thrift_utils:unnest_record(Reason, tTransportException) of
165 error -> false;
166 {ok, TTE} ->
167 oop:get(TTE, type) == ?tTransportException_NOT_OPEN andalso
168 oop:get(TTE, message) == "in tSocket:read/2: gen_tcp:recv"
169 end
170 end,
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000171
172 case Ignore of
173 true ->
174 ok;
175 false ->
176 Format1 = "** gen_server terminating in message ~p~n** State = ~s~n** Reason = ~s~n",
177 Message = sformat(Format1, [LastMessage, oop:inspect(Obj), oop:inspect(Reason)]), %% TODO(cpiro): hope Reason is an object?
Christopher Piro68940292007-10-02 00:35:12 +0000178 handle_event2(Symbol, Ref, "", Message, State)
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000179 end;
180 _ ->
181 Message = sformat(Format, Data),
Christopher Piro68940292007-10-02 00:35:12 +0000182 handle_event2(Symbol, Ref, "", Message, State)
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000183 end,
184 {ok, State};
185
186handle_event1({What, _Gleader, {Pid, Type, Report}}, State) ->
187 Symbol = case What of
188 error_report -> "!!";
189 warning_report -> "**";
190 info_report -> "..";
191 _Else -> "??"
192 end,
193
194 case Type of
195 {thrift_info, TI} ->
196 %% should we show it?
Christopher Piro68940292007-10-02 00:35:12 +0000197 case not lists:member(TI, ?CONFIG(omit)) of
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000198 true ->
199 Message = handle_thrift_info(TI, Report, State),
200 handle_event2(Symbol, Pid, "", Message, State);
201 false ->
202 ok
203 end;
204 crash_report ->
205 %% [Cruft|_] = Report, %%
206 %% {value, {_, Reason}} = lists:keysearch(error_info, 1, Cruft), %%
207 %% {value, {_, {_, _, [_,_,_,_, Obj, []]}}} = lists:keysearch(initial_call, 1, Cruft), %%
208 %% sformat("state == ~s~nreason ==~s", [oop:inspect(Obj), oop:inspect(Reason)]), %%
209 ok;
210 progress ->
211 ok;
212
213 _ ->
214 Message = sformat("|| ~s", [oop:inspect(Report)]),
215 handle_event2(Symbol, Pid, Type, Message, State)
216 end,
217 {ok, State};
218
219handle_event1(_Event, State) ->
220 handle_event2("??", "<?.?.?>", "", _Event, State),
221 {ok, State}.
222
223handle_event(Event, State) ->
224 try
225 handle_event1(Event, State)
226 catch
227 _:E ->
228 format("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~n error logger error:~n ~p~n Event = ~p~n State = ~p~n ~p~n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~n",
229 [E, Event, State, erlang:get_stacktrace()]),
230 {ok, State}
231 end.
232
233%% thrift info handlers
234handle_thrift_info(oop_new, {Args, Class, Object}, State) ->
235 %% arg Class can't come first! Then it'd look like a Class object
236 L = io_lib:format("~p:new(~s) = ~s", [Class, thrift_utils:unbrack(Args), oop:inspect(Object)]),
237 lists:flatten(L);
238
239handle_thrift_info(server_listening, {Port}, State) ->
240 sformat("server listening on port ~p", [Port]);
241
242handle_thrift_info(req_processed, {Value}, State) ->
243 sformat("request: ~p", [Value]);
244
245handle_thrift_info(conn_accepted, {AddrString}, State) ->
246 sformat("connection accepted from ~s", [AddrString]);
247
Christopher Piroee598842007-08-03 23:34:55 +0000248handle_thrift_info(conn_timeout, {AddrString}, State) ->
249 sformat("connection timed out from ~s", [AddrString]);
250
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000251handle_thrift_info(conn_closed, {AddrString}, State) ->
252 sformat("connection closed from ~s", [AddrString]);
253
254handle_thrift_info(Else, Report, State) ->
255 sformat("~p ~s", [Else, oop:inspect(Report)]).
256
257%%--------------------------------------------------------------------
258%% @spec handle_call(Request, State) -> {ok, Reply, State} |
Christopher Piro68940292007-10-02 00:35:12 +0000259%% {swap_handler, Reply, Args1, State1,
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000260%% Mod2, Args2} |
261%% {remove_handler, Reply}.
262%%
263%% @doc
264%% Whenever an event manager receives a request sent using
265%% gen_event:call/3,4, this function is called for the specified event
266%% handler to handle the request.
267%% @end
268%%--------------------------------------------------------------------
Christopher Piro68940292007-10-02 00:35:12 +0000269handle_call({reconfig, Amendments}, State) ->
270 {OkOrError, State1} = reconfig1(State, Amendments),
271 format(".. reconfig was ~p~n", [OkOrError]),
272 {ok, OkOrError, State1};
273
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000274handle_call(_Request, State) ->
275 Reply = ok,
276 {ok, Reply, State}.
277
278%%--------------------------------------------------------------------
279%% @spec handle_info(Info, State) -> {ok, State} |
280%% {swap_handler, Args1, State1, Mod2, Args2} |
281%% remove_handler.
282%%
283%% @doc
284%% This function is called for each installed event handler when
285%% an event manager receives any other message than an event or a synchronous
286%% request (or a system message).
287%% @end
288%%--------------------------------------------------------------------
289handle_info(_Info, State) ->
290 {ok, State}.
291
292%%--------------------------------------------------------------------
293%% @spec terminate(Reason, State) -> void().
294%%
295%% @doc
296%% Whenever an event handler is deleted from an event manager,
297%% this function is called. It should be the opposite of Module:init/1 and
298%% do any necessary cleaning up.
299%% @end
300%%--------------------------------------------------------------------
301terminate(normal, _State) ->
302 ok;
303terminate(Reason, _State) ->
304 format("*****************~n~n frick, error logger terminating: ~p~n~n*****************~n~n", [Reason]),
305 ok.
306
307%%--------------------------------------------------------------------
308%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}.
309%%
310%% @doc
311%% Convert process state when code is changed
312%% @end
313%%--------------------------------------------------------------------
314code_change(_OldVsn, State, _Extra) ->
315 {ok, State}.
316
317%%====================================================================
318%%% Internal functions
319%%====================================================================
Christopher Piro68940292007-10-02 00:35:12 +0000320
321%% how to output
322format(Format, Data) ->
323 io:format(Format, Data).
324
325%% convenience
326sformat(Format, Data) ->
327 thrift_utils:sformat(Format, Data).
328
329reconfig1(State, Amendments) ->
330 case thrift:config(thrift_logger) of
331 {value, Config} ->
332 Config1 = lists:keysort(1, Config),
333 Amendments1 = lists:keysort(1, Amendments),
334 Config2 = lists:keymerge(1, Amendments1, Config1),
335
336 State1 = State#state{config=Config2},
337 {ok, State1};
338 _ ->
339 {error, State}
340 end.
341
342config(Item, State) ->
343 case thrift:config(Item, State#state.config) of
344 {value, V} ->
345 V;
346 Else ->
347 ?ERROR("config for ~p is unavailable: ~p", [Item, Else])
348 end.