1%% Copyright (c) 2011-2012 Basho Technologies, Inc.  All Rights Reserved.
2%%
3%% This file is provided to you under the Apache License,
4%% Version 2.0 (the "License"); you may not use this file
5%% except in compliance with the License.  You may obtain
6%% a copy of the License at
7%%
8%%   http://www.apache.org/licenses/LICENSE-2.0
9%%
10%% Unless required by applicable law or agreed to in writing,
11%% software distributed under the License is distributed on an
12%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13%% KIND, either express or implied.  See the License for the
14%% specific language governing permissions and limitations
15%% under the License.
16
17%% @doc Lager's application module. Not a lot to see here.
18
19%% @private
20
21-module(lager_app).
22
23-behaviour(application).
24-include("lager.hrl").
25-ifdef(TEST).
26-compile([export_all]).
27-include_lib("eunit/include/eunit.hrl").
28-endif.
29-export([start/0,
30         start/2,
31         start_handler/3,
32         configure_sink/2,
33         stop/1,
34         boot/1]).
35
36%% The `application:get_env/3` compatibility wrapper is useful
37%% for other modules
38-export([get_env/3]).
39
40-define(FILENAMES, '__lager_file_backend_filenames').
41-define(THROTTLE, lager_backend_throttle).
42-define(DEFAULT_HANDLER_CONF,
43        [{lager_console_backend, info},
44         {lager_file_backend,
45          [{file, "log/error.log"}, {level, error},
46           {size, 10485760}, {date, "$D0"}, {count, 5}]
47         },
48         {lager_file_backend,
49          [{file, "log/console.log"}, {level, info},
50           {size, 10485760}, {date, "$D0"}, {count, 5}]
51         }
52        ]).
53
54start() ->
55    application:start(lager).
56
57start_throttle(Sink, Threshold, Window) ->
58    _ = supervisor:start_child(lager_handler_watcher_sup,
59                               [Sink, ?THROTTLE, [Threshold, Window]]),
60    ok.
61
62determine_async_behavior(_Sink, undefined, _Window) ->
63    ok;
64determine_async_behavior(_Sink, Threshold, _Window) when not is_integer(Threshold) orelse Threshold < 0 ->
65    error_logger:error_msg("Invalid value for 'async_threshold': ~p~n",
66                           [Threshold]),
67    throw({error, bad_config});
68determine_async_behavior(Sink, Threshold, undefined) ->
69    start_throttle(Sink, Threshold, erlang:trunc(Threshold * 0.2));
70determine_async_behavior(_Sink, Threshold, Window) when not is_integer(Window) orelse Window > Threshold orelse Window < 0 ->
71    error_logger:error_msg(
72      "Invalid value for 'async_threshold_window': ~p~n", [Window]),
73    throw({error, bad_config});
74determine_async_behavior(Sink, Threshold, Window) ->
75    start_throttle(Sink, Threshold, Window).
76
77start_handlers(_Sink, undefined) ->
78    ok;
79start_handlers(_Sink, Handlers) when not is_list(Handlers) ->
80    error_logger:error_msg(
81      "Invalid value for 'handlers' (must be list): ~p~n", [Handlers]),
82    throw({error, bad_config});
83start_handlers(Sink, Handlers) ->
84    %% handlers failing to start are handled in the handler_watcher
85    lager_config:global_set(handlers,
86                            lager_config:global_get(handlers, []) ++
87                            lists:map(fun({Module, Config}) ->
88                                              check_handler_config(Module, Config),
89                                              start_handler(Sink, Module, Config);
90                                          (_) ->
91                                              throw({error, bad_config})
92                                      end,
93                                      expand_handlers(Handlers))),
94    ok.
95
96start_handler(Sink, Module, Config) ->
97    {ok, Watcher} = supervisor:start_child(lager_handler_watcher_sup,
98                                           [Sink, Module, Config]),
99    {Module, Watcher, Sink}.
100
101check_handler_config({lager_file_backend, F}, Config) when is_list(Config); is_tuple(Config) ->
102    Fs = case get(?FILENAMES) of
103        undefined -> ordsets:new();
104        X -> X
105    end,
106    case ordsets:is_element(F, Fs) of
107        true ->
108            error_logger:error_msg(
109              "Cannot have same file (~p) in multiple file backends~n", [F]),
110            throw({error, bad_config});
111        false ->
112            put(?FILENAMES,
113                ordsets:add_element(F, Fs))
114    end,
115    ok;
116check_handler_config(_Handler, Config) when is_list(Config) orelse is_atom(Config) ->
117    ok;
118check_handler_config(Handler, _BadConfig) ->
119    throw({error, {bad_config, Handler}}).
120
121clean_up_config_checks() ->
122    erase(?FILENAMES).
123
124interpret_hwm(undefined) ->
125    undefined;
126interpret_hwm(HWM) when not is_integer(HWM) orelse HWM < 0 ->
127    _ = lager:log(warning, self(), "Invalid error_logger high water mark: ~p, disabling", [HWM]),
128    undefined;
129interpret_hwm(HWM) ->
130    HWM.
131
132maybe_install_sink_killer(_Sink, undefined, _ReinstallTimer) -> ok;
133maybe_install_sink_killer(Sink, HWM, undefined) -> maybe_install_sink_killer(Sink, HWM, 5000);
134maybe_install_sink_killer(Sink, HWM, ReinstallTimer) when is_integer(HWM) andalso is_integer(ReinstallTimer)
135                                                        andalso HWM >= 0 andalso ReinstallTimer >= 0 ->
136    _ = supervisor:start_child(lager_handler_watcher_sup, [Sink, lager_manager_killer,
137                                                           [HWM, ReinstallTimer]]);
138maybe_install_sink_killer(_Sink, HWM, ReinstallTimer) ->
139    error_logger:error_msg("Invalid value for 'killer_hwm': ~p or 'killer_reinstall_after': ~p", [HWM, ReinstallTimer]),
140    throw({error, bad_config}).
141
142-spec start_error_logger_handler(boolean(), pos_integer(), list()) -> list().
143start_error_logger_handler(false, _HWM, _Whitelist) ->
144    [];
145start_error_logger_handler(true, HWM, WhiteList) ->
146    GlStrategy = case application:get_env(lager, error_logger_groupleader_strategy) of
147                    undefined ->
148                        handle;
149                    {ok, GlStrategy0} when
150                            GlStrategy0 =:= handle;
151                            GlStrategy0 =:= ignore;
152                            GlStrategy0 =:= mirror ->
153                        GlStrategy0;
154                    {ok, BadGlStrategy} ->
155                        error_logger:error_msg(
156                          "Invalid value for 'error_logger_groupleader_strategy': ~p~n",
157                          [BadGlStrategy]),
158                        throw({error, bad_config})
159                end,
160
161
162    _ = case supervisor:start_child(lager_handler_watcher_sup, [error_logger, error_logger_lager_h, [HWM, GlStrategy]]) of
163        {ok, _} ->
164            [begin error_logger:delete_report_handler(X), X end ||
165                X <- gen_event:which_handlers(error_logger) -- [error_logger_lager_h | WhiteList]];
166        {error, _} ->
167            []
168    end,
169
170    Handlers = case application:get_env(lager, handlers) of
171        undefined ->
172            [{lager_console_backend, info},
173             {lager_file_backend, [{file, "log/error.log"},   {level, error}, {size, 10485760}, {date, "$D0"}, {count, 5}]},
174             {lager_file_backend, [{file, "log/console.log"}, {level, info}, {size, 10485760}, {date, "$D0"}, {count, 5}]}];
175        {ok, Val} ->
176            Val
177    end,
178    Handlers.
179
180configure_sink(Sink, SinkDef) ->
181    lager_config:new_sink(Sink),
182    ChildId = lager_util:make_internal_sink_name(Sink),
183    _ = supervisor:start_child(lager_sup,
184                               {ChildId,
185                                {gen_event, start_link,
186                                 [{local, Sink}]},
187                                permanent, 5000, worker, dynamic}),
188    determine_async_behavior(Sink, proplists:get_value(async_threshold, SinkDef),
189                             proplists:get_value(async_threshold_window, SinkDef)
190                            ),
191    _ = maybe_install_sink_killer(Sink, proplists:get_value(killer_hwm, SinkDef),
192                              proplists:get_value(killer_reinstall_after, SinkDef)),
193    start_handlers(Sink,
194                   proplists:get_value(handlers, SinkDef, [])),
195
196    lager:update_loglevel_config(Sink).
197
198
199configure_extra_sinks(Sinks) ->
200    lists:foreach(fun({Sink, Proplist}) -> configure_sink(Sink, Proplist) end,
201                  Sinks).
202
203-spec get_env(atom(), atom()) -> term().
204get_env(Application, Key) ->
205    get_env(Application, Key, undefined).
206
207%% R15 doesn't know about application:get_env/3
208-spec get_env(atom(), atom(), term()) -> term().
209get_env(Application, Key, Default) ->
210    get_env_default(application:get_env(Application, Key), Default).
211
212-spec get_env_default('undefined' | {'ok', term()}, term()) -> term().
213get_env_default(undefined, Default) ->
214    Default;
215get_env_default({ok, Value}, _Default) ->
216    Value.
217
218start(_StartType, _StartArgs) ->
219    {ok, Pid} = lager_sup:start_link(),
220    SavedHandlers = boot(),
221    _ = boot('__all_extra'),
222    _ = boot('__traces'),
223    clean_up_config_checks(),
224    {ok, Pid, SavedHandlers}.
225
226boot() ->
227    %% Handle the default sink.
228    determine_async_behavior(?DEFAULT_SINK,
229                             get_env(lager, async_threshold),
230                             get_env(lager, async_threshold_window)),
231
232    _ = maybe_install_sink_killer(?DEFAULT_SINK, get_env(lager, killer_hwm),
233                              get_env(lager, killer_reinstall_after)),
234
235    start_handlers(?DEFAULT_SINK,
236                   get_env(lager, handlers, ?DEFAULT_HANDLER_CONF)),
237
238    lager:update_loglevel_config(?DEFAULT_SINK),
239
240    SavedHandlers = start_error_logger_handler(
241                      get_env(lager, error_logger_redirect, true),
242                      interpret_hwm(get_env(lager, error_logger_hwm, 0)),
243                      get_env(lager, error_logger_whitelist, [])
244                     ),
245
246    SavedHandlers.
247
248boot('__traces') ->
249    _ = lager_util:trace_filter(none),
250    ok = add_configured_traces();
251
252boot('__all_extra') ->
253    configure_extra_sinks(get_env(lager, extra_sinks, []));
254
255boot(?DEFAULT_SINK) -> boot();
256boot(Sink) ->
257    AllSinksDef = get_env(lager, extra_sinks, []),
258    boot_sink(Sink, lists:keyfind(Sink, 1, AllSinksDef)).
259
260boot_sink(Sink, {Sink, Def}) ->
261    configure_sink(Sink, Def);
262boot_sink(Sink, false) ->
263    configure_sink(Sink, []).
264
265stop(Handlers) ->
266    lists:foreach(fun(Handler) ->
267          error_logger:add_report_handler(Handler)
268      end, Handlers).
269
270expand_handlers([]) ->
271    [];
272expand_handlers([{lager_file_backend, [{Key, _Value}|_]=Config}|T]) when is_atom(Key) ->
273    %% this is definitely a new-style config, no expansion needed
274    [maybe_make_handler_id(lager_file_backend, Config) | expand_handlers(T)];
275expand_handlers([{lager_file_backend, Configs}|T]) ->
276    ?INT_LOG(notice, "Deprecated lager_file_backend config detected, please consider updating it", []),
277    [ {lager_file_backend:config_to_id(Config), Config} || Config <- Configs] ++
278      expand_handlers(T);
279expand_handlers([{Mod, Config}|T]) when is_atom(Mod) ->
280    [maybe_make_handler_id(Mod, Config) | expand_handlers(T)];
281expand_handlers([H|T]) ->
282    [H | expand_handlers(T)].
283
284add_configured_traces() ->
285    Traces = case application:get_env(lager, traces) of
286        undefined ->
287            [];
288        {ok, TraceVal} ->
289            TraceVal
290    end,
291
292    lists:foreach(fun start_configured_trace/1, Traces),
293    ok.
294
295start_configured_trace({Handler, Filter}) ->
296    {ok, _} = lager:trace(Handler, Filter);
297start_configured_trace({Handler, Filter, Level}) when is_atom(Level) ->
298    {ok, _} = lager:trace(Handler, Filter, Level).
299
300maybe_make_handler_id(Mod, Config) ->
301    %% Allow the backend to generate a gen_event handler id, if it wants to.
302    %% We don't use erlang:function_exported here because that requires the module
303    %% already be loaded, which is unlikely at this phase of startup. Using code:load
304    %% caused undesirable side-effects with generating code-coverage reports.
305    try Mod:config_to_id(Config) of
306        Id ->
307            {Id, Config}
308    catch
309        error:undef ->
310            {Mod, Config}
311    end.
312
313-ifdef(TEST).
314application_config_mangling_test_() ->
315    [
316        {"Explode the file backend handlers",
317            ?_assertMatch(
318                [{lager_console_backend, info},
319                    {{lager_file_backend,"error.log"},{"error.log",error,10485760, "$D0",5}},
320                    {{lager_file_backend,"console.log"},{"console.log",info,10485760, "$D0",5}}
321                ],
322                expand_handlers([{lager_console_backend, info},
323                        {lager_file_backend, [
324                                {"error.log", error, 10485760, "$D0", 5},
325                                {"console.log", info, 10485760, "$D0", 5}
326                            ]}]
327                ))
328        },
329        {"Explode the short form of backend file handlers",
330            ?_assertMatch(
331                [{lager_console_backend, info},
332                    {{lager_file_backend,"error.log"},{"error.log",error}},
333                    {{lager_file_backend,"console.log"},{"console.log",info}}
334                ],
335                expand_handlers([{lager_console_backend, info},
336                        {lager_file_backend, [
337                                {"error.log", error},
338                                {"console.log", info}
339                            ]}]
340                ))
341        },
342        {"Explode with formatter info",
343            ?_assertMatch(
344                [{{lager_file_backend,"test.log"},  [{"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]},
345                    {{lager_file_backend,"test2.log"}, [{"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ", message, "\n"]}]}],
346                expand_handlers([{lager_file_backend, [
347                                [{"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}],
348                                [{"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ",message, "\n"]}]
349                            ]
350                        }])
351            )
352        },
353        {"Explode short form with short formatter info",
354            ?_assertMatch(
355                [{{lager_file_backend,"test.log"},  [{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]},
356                    {{lager_file_backend,"test2.log"}, [{"test2.log",debug},{lager_default_formatter}]}],
357                expand_handlers([{lager_file_backend, [
358                                [{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}],
359                                [{"test2.log",debug},{lager_default_formatter}]
360                            ]
361                        }])
362            )
363        },
364        {"New form needs no expansion",
365            ?_assertMatch([
366                    {{lager_file_backend,"test.log"},  [{file, "test.log"}]},
367                    {{lager_file_backend,"test2.log"}, [{file, "test2.log"}, {level, info}, {sync_on, none}]},
368                    {{lager_file_backend,"test3.log"}, [{formatter, lager_default_formatter}, {file, "test3.log"}]}
369                ],
370                expand_handlers([
371                        {lager_file_backend, [{file, "test.log"}]},
372                        {lager_file_backend, [{file, "test2.log"}, {level, info}, {sync_on, none}]},
373                        {lager_file_backend, [{formatter, lager_default_formatter},{file, "test3.log"}]}
374                    ])
375            )
376        }
377    ].
378
379check_handler_config_test_() ->
380    Good = expand_handlers(?DEFAULT_HANDLER_CONF),
381    Bad  = expand_handlers([{lager_console_backend, info},
382            {lager_file_backend, [{file, "same_file.log"}]},
383            {lager_file_backend, [{file, "same_file.log"}, {level, info}]}]),
384    AlsoBad = [{lager_logstash_backend,
385                                    {level, info},
386                                    {output, {udp, "localhost", 5000}},
387                                    {format, json},
388                                    {json_encoder, jiffy}}],
389    BadToo = [{fail, {fail}}],
390    OldSchoolLagerGood = expand_handlers([{lager_console_backend,info},
391                                          {lager_file_backend, [
392                                              {"./log/error.log",error,10485760,"$D0",5},
393                                              {"./log/console.log",info,10485760,"$D0",5},
394                                              {"./log/debug.log",debug,10485760,"$D0",5}
395                                          ]}]),
396    NewConfigMissingList = expand_handlers([{foo_backend, {file, "same_file.log"}}]),
397    [
398        {"lager_file_backend_good",
399         ?_assertEqual([ok, ok, ok], [ check_handler_config(M,C) || {M,C} <- Good ])
400        },
401        {"lager_file_backend_bad",
402         ?_assertThrow({error, bad_config}, [ check_handler_config(M,C) || {M,C} <- Bad ])
403        },
404        {"Invalid config dies",
405         ?_assertThrow({error, bad_config}, start_handlers(foo, AlsoBad))
406        },
407        {"Invalid config dies",
408         ?_assertThrow({error, {bad_config, _}}, start_handlers(foo, BadToo))
409        },
410        {"Old Lager config works",
411         ?_assertEqual([ok, ok, ok, ok], [ check_handler_config(M, C) || {M, C} <- OldSchoolLagerGood])
412        },
413        {"New Config missing its list should fail",
414         ?_assertThrow({error, {bad_config, foo_backend}}, [ check_handler_config(M, C) || {M, C} <- NewConfigMissingList])
415        }
416    ].
417-endif.
418