%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you 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. %% @doc Lager's application module. Not a lot to see here. %% @private -module(lager_app). -behaviour(application). -include("lager.hrl"). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. -export([start/0, start/2, start_handler/3, configure_sink/2, stop/1, boot/1]). %% The `application:get_env/3` compatibility wrapper was useful %% for other modules in r15 and before -export([get_env/3]). -define(FILENAMES, '__lager_file_backend_filenames'). -define(THROTTLE, lager_backend_throttle). -define(DEFAULT_HANDLER_CONF, [{lager_console_backend, [{level, info}]}, {lager_file_backend, [{file, "log/error.log"}, {level, error}, {size, 10485760}, {date, "$D0"}, {count, 5}] }, {lager_file_backend, [{file, "log/console.log"}, {level, info}, {size, 10485760}, {date, "$D0"}, {count, 5}] } ]). start() -> application:start(lager). start_throttle(Sink, Threshold, Window) -> _ = supervisor:start_child(lager_handler_watcher_sup, [Sink, ?THROTTLE, [Threshold, Window]]), ok. determine_async_behavior(_Sink, undefined, _Window) -> ok; determine_async_behavior(_Sink, Threshold, _Window) when not is_integer(Threshold) orelse Threshold < 0 -> error_logger:error_msg("Invalid value for 'async_threshold': ~p~n", [Threshold]), throw({error, bad_config}); determine_async_behavior(Sink, Threshold, undefined) -> start_throttle(Sink, Threshold, erlang:trunc(Threshold * 0.2)); determine_async_behavior(_Sink, Threshold, Window) when not is_integer(Window) orelse Window > Threshold orelse Window < 0 -> error_logger:error_msg( "Invalid value for 'async_threshold_window': ~p~n", [Window]), throw({error, bad_config}); determine_async_behavior(Sink, Threshold, Window) -> start_throttle(Sink, Threshold, Window). start_handlers(_Sink, undefined) -> ok; start_handlers(_Sink, Handlers) when not is_list(Handlers) -> error_logger:error_msg( "Invalid value for 'handlers' (must be list): ~p~n", [Handlers]), throw({error, bad_config}); start_handlers(Sink, Handlers) -> %% handlers failing to start are handled in the handler_watcher lager_config:global_set(handlers, lager_config:global_get(handlers, []) ++ lists:map(fun({Module, Config}) -> check_handler_config(Module, Config), start_handler(Sink, Module, Config); (_) -> throw({error, bad_config}) end, expand_handlers(Handlers))), ok. start_handler(Sink, Module, Config) -> {ok, Watcher} = supervisor:start_child(lager_handler_watcher_sup, [Sink, Module, Config]), {Module, Watcher, Sink}. check_handler_config({lager_file_backend, F}, Config) when is_list(Config); is_tuple(Config) -> Fs = case get(?FILENAMES) of undefined -> ordsets:new(); X -> X end, case ordsets:is_element(F, Fs) of true -> error_logger:error_msg( "Cannot have same file (~p) in multiple file backends~n", [F]), throw({error, bad_config}); false -> put(?FILENAMES, ordsets:add_element(F, Fs)) end, ok; check_handler_config(_Handler, Config) when is_list(Config) orelse is_atom(Config) -> ok; check_handler_config(Handler, _BadConfig) -> throw({error, {bad_config, Handler}}). clean_up_config_checks() -> erase(?FILENAMES). interpret_hwm(undefined) -> undefined; interpret_hwm(HWM) when not is_integer(HWM) orelse HWM < 0 -> _ = lager:log(warning, self(), "Invalid error_logger high water mark: ~p, disabling", [HWM]), undefined; interpret_hwm(HWM) -> HWM. maybe_install_sink_killer(_Sink, undefined, _ReinstallTimer) -> ok; maybe_install_sink_killer(Sink, HWM, undefined) -> maybe_install_sink_killer(Sink, HWM, 5000); maybe_install_sink_killer(Sink, HWM, ReinstallTimer) when is_integer(HWM) andalso is_integer(ReinstallTimer) andalso HWM >= 0 andalso ReinstallTimer >= 0 -> _ = supervisor:start_child(lager_handler_watcher_sup, [Sink, lager_manager_killer, [HWM, ReinstallTimer]]); maybe_install_sink_killer(_Sink, HWM, ReinstallTimer) -> error_logger:error_msg("Invalid value for 'killer_hwm': ~p or 'killer_reinstall_after': ~p", [HWM, ReinstallTimer]), throw({error, bad_config}). -spec start_error_logger_handler(boolean(), pos_integer(), list()) -> list(). start_error_logger_handler(false, _HWM, _Whitelist) -> []; start_error_logger_handler(true, HWM, WhiteList) -> GlStrategy = case application:get_env(lager, error_logger_groupleader_strategy) of undefined -> handle; {ok, GlStrategy0} when GlStrategy0 =:= handle; GlStrategy0 =:= ignore; GlStrategy0 =:= mirror -> GlStrategy0; {ok, BadGlStrategy} -> error_logger:error_msg( "Invalid value for 'error_logger_groupleader_strategy': ~p~n", [BadGlStrategy]), throw({error, bad_config}) end, case whereis(error_logger) of undefined -> %% On OTP 21 and above, error_logger is deprecated in favor of 'logger' %% As a band-aid, boot up error_logger anyway and install it as a logger handler %% we can't use error_logger:add_report_handler because we want supervision of the handler %% so we have to manually add the logger handler %% %% Longer term we should be installing a logger handler instead, but this will bridge the gap %% for now. _ = error_logger:start(), _ = logger:add_handler(error_logger,error_logger,#{level=>info,filter_default=>log}), ok = maybe_remove_logger_handler(); _ -> ok end, %% capture which handlers we removed from error_logger so we can restore them when lager stops OldHandlers = case supervisor:start_child(lager_handler_watcher_sup, [error_logger, error_logger_lager_h, [HWM, GlStrategy]]) of {ok, _} -> [begin error_logger:delete_report_handler(X), X end || X <- gen_event:which_handlers(error_logger) -- [error_logger_lager_h | WhiteList]]; {error, _} -> [] end, OldHandlers. %% On OTP 21.1 and higher we need to remove the `default' handler. %% But it might not exist, so we will wrap this in a try-catch %% block maybe_remove_logger_handler() -> try ok = logger:remove_handler(default) catch error:undef -> ok; Err:Reason -> error_logger:error_msg("calling logger:remove_handler(default) failed: ~p ~p", [Err, Reason]) end. configure_sink(Sink, SinkDef) -> lager_config:new_sink(Sink), ChildId = lager_util:make_internal_sink_name(Sink), _ = supervisor:start_child(lager_sup, {ChildId, {gen_event, start_link, [{local, Sink}]}, permanent, 5000, worker, dynamic}), determine_async_behavior(Sink, proplists:get_value(async_threshold, SinkDef), proplists:get_value(async_threshold_window, SinkDef) ), _ = maybe_install_sink_killer(Sink, proplists:get_value(killer_hwm, SinkDef), proplists:get_value(killer_reinstall_after, SinkDef)), start_handlers(Sink, proplists:get_value(handlers, SinkDef, [])), lager:update_loglevel_config(Sink). configure_extra_sinks(Sinks) -> lists:foreach(fun({Sink, Proplist}) -> configure_sink(Sink, Proplist) end, Sinks). -spec get_env(atom(), atom(), term()) -> term(). get_env(Application, Key, Default) -> application:get_env(Application, Key, Default). start(_StartType, _StartArgs) -> {ok, Pid} = lager_sup:start_link(), SavedHandlers = boot(), _ = boot('__all_extra'), _ = boot('__traces'), clean_up_config_checks(), {ok, Pid, SavedHandlers}. boot() -> %% Handle the default sink. determine_async_behavior(?DEFAULT_SINK, application:get_env(lager, async_threshold, undefined), application:get_env(lager, async_threshold_window, undefined)), _ = maybe_install_sink_killer(?DEFAULT_SINK, application:get_env(lager, killer_hwm, undefined), application:get_env(lager, killer_reinstall_after, undefined)), start_handlers(?DEFAULT_SINK, application:get_env(lager, handlers, ?DEFAULT_HANDLER_CONF)), lager:update_loglevel_config(?DEFAULT_SINK), SavedHandlers = start_error_logger_handler( application:get_env(lager, error_logger_redirect, true), interpret_hwm(application:get_env(lager, error_logger_hwm, 0)), application:get_env(lager, error_logger_whitelist, []) ), SavedHandlers. boot('__traces') -> _ = lager_util:trace_filter(none), ok = add_configured_traces(); boot('__all_extra') -> configure_extra_sinks(application:get_env(lager, extra_sinks, [])); boot(?DEFAULT_SINK) -> boot(); boot(Sink) -> AllSinksDef = application:get_env(lager, extra_sinks, []), boot_sink(Sink, lists:keyfind(Sink, 1, AllSinksDef)). boot_sink(Sink, {Sink, Def}) -> configure_sink(Sink, Def); boot_sink(Sink, false) -> configure_sink(Sink, []). stop(Handlers) -> lists:foreach(fun(Handler) -> error_logger:add_report_handler(Handler) end, Handlers), lager_config:cleanup(). expand_handlers([]) -> []; expand_handlers([{lager_file_backend, [{Key, _Value}|_]=Config}|T]) when is_atom(Key) -> %% this is definitely a new-style config, no expansion needed [maybe_make_handler_id(lager_file_backend, Config) | expand_handlers(T)]; expand_handlers([{lager_file_backend, Configs}|T]) -> ?INT_LOG(notice, "Deprecated lager_file_backend config detected, please consider updating it", []), [ {lager_file_backend:config_to_id(Config), Config} || Config <- Configs] ++ expand_handlers(T); expand_handlers([{Mod, Config}|T]) when is_atom(Mod) -> [maybe_make_handler_id(Mod, Config) | expand_handlers(T)]; expand_handlers([H|T]) -> [H | expand_handlers(T)]. add_configured_traces() -> Traces = case application:get_env(lager, traces) of undefined -> []; {ok, TraceVal} -> TraceVal end, lists:foreach(fun start_configured_trace/1, Traces), ok. start_configured_trace({Handler, Filter}) -> {ok, _} = lager:trace(Handler, Filter); start_configured_trace({Handler, Filter, Level}) when is_atom(Level) -> {ok, _} = lager:trace(Handler, Filter, Level). maybe_make_handler_id(Mod, Config) -> %% Allow the backend to generate a gen_event handler id, if it wants to. %% We don't use erlang:function_exported here because that requires the module %% already be loaded, which is unlikely at this phase of startup. Using code:load %% caused undesirable side-effects with generating code-coverage reports. try Mod:config_to_id(Config) of Id -> {Id, Config} catch error:undef -> {Mod, Config} end. -ifdef(TEST). application_config_mangling_test_() -> [ {"Explode the file backend handlers", ?_assertMatch( [{lager_console_backend, [{level, info}]}, {{lager_file_backend,"error.log"},{"error.log",error,10485760, "$D0",5}}, {{lager_file_backend,"console.log"},{"console.log",info,10485760, "$D0",5}} ], expand_handlers([{lager_console_backend, [{level, info}]}, {lager_file_backend, [ {"error.log", error, 10485760, "$D0", 5}, {"console.log", info, 10485760, "$D0", 5} ]}] )) }, {"Explode the short form of backend file handlers", ?_assertMatch( [{lager_console_backend, [{level, info}]}, {{lager_file_backend,"error.log"},{"error.log",error}}, {{lager_file_backend,"console.log"},{"console.log",info}} ], expand_handlers([{lager_console_backend, [{level, info}]}, {lager_file_backend, [ {"error.log", error}, {"console.log", info} ]}] )) }, {"Explode with formatter info", ?_assertMatch( [{{lager_file_backend,"test.log"}, [{"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]}, {{lager_file_backend,"test2.log"}, [{"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ", message, "\n"]}]}], expand_handlers([{lager_file_backend, [ [{"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}], [{"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ",message, "\n"]}] ] }]) ) }, {"Explode short form with short formatter info", ?_assertMatch( [{{lager_file_backend,"test.log"}, [{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]}, {{lager_file_backend,"test2.log"}, [{"test2.log",debug},{lager_default_formatter}]}], expand_handlers([{lager_file_backend, [ [{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}], [{"test2.log",debug},{lager_default_formatter}] ] }]) ) }, {"New form needs no expansion", ?_assertMatch([ {{lager_file_backend,"test.log"}, [{file, "test.log"}]}, {{lager_file_backend,"test2.log"}, [{file, "test2.log"}, {level, info}, {sync_on, none}]}, {{lager_file_backend,"test3.log"}, [{formatter, lager_default_formatter}, {file, "test3.log"}]} ], expand_handlers([ {lager_file_backend, [{file, "test.log"}]}, {lager_file_backend, [{file, "test2.log"}, {level, info}, {sync_on, none}]}, {lager_file_backend, [{formatter, lager_default_formatter},{file, "test3.log"}]} ]) ) } ]. check_handler_config_test_() -> Good = expand_handlers(?DEFAULT_HANDLER_CONF), Bad = expand_handlers([{lager_console_backend, [{level, info}]}, {lager_file_backend, [{file, "same_file.log"}]}, {lager_file_backend, [{file, "same_file.log"}, {level, info}]}]), AlsoBad = [{lager_logstash_backend, {level, info}, {output, {udp, "localhost", 5000}}, {format, json}, {json_encoder, jiffy}}], BadToo = [{fail, {fail}}], OldSchoolLagerGood = expand_handlers([{lager_console_backend, [{level, info}]}, {lager_file_backend, [ {"./log/error.log",error,10485760,"$D0",5}, {"./log/console.log",info,10485760,"$D0",5}, {"./log/debug.log",debug,10485760,"$D0",5} ]}]), NewConfigMissingList = expand_handlers([{foo_backend, {file, "same_file.log"}}]), [ {"lager_file_backend_good", ?_assertEqual([ok, ok, ok], [ check_handler_config(M,C) || {M,C} <- Good ]) }, {"lager_file_backend_bad", ?_assertThrow({error, bad_config}, [ check_handler_config(M,C) || {M,C} <- Bad ]) }, {"Invalid config dies", ?_assertThrow({error, bad_config}, start_handlers(foo, AlsoBad)) }, {"Invalid config dies", ?_assertThrow({error, {bad_config, _}}, start_handlers(foo, BadToo)) }, {"Old Lager config works", ?_assertEqual([ok, ok, ok, ok], [ check_handler_config(M, C) || {M, C} <- OldSchoolLagerGood]) }, {"New Config missing its list should fail", ?_assertThrow({error, {bad_config, foo_backend}}, [ check_handler_config(M, C) || {M, C} <- NewConfigMissingList]) } ]. -endif.