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