diff options
Diffstat (limited to 'deps/lager/src/lager.erl')
-rw-r--r-- | deps/lager/src/lager.erl | 685 |
1 files changed, 436 insertions, 249 deletions
diff --git a/deps/lager/src/lager.erl b/deps/lager/src/lager.erl index db40103..90386c3 100644 --- a/deps/lager/src/lager.erl +++ b/deps/lager/src/lager.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% 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 @@ -20,26 +20,20 @@ -include("lager.hrl"). +-define(LAGER_MD_KEY, '__lager_metadata'). +-define(TRACE_SINK, '__trace_sink'). + %% API -export([start/0, - log/8, log_dest/9, log/3, log/4, - trace_file/2, trace_file/3, trace_console/1, trace_console/2, - clear_all_traces/0, stop_trace/1, status/0, - get_loglevel/1, set_loglevel/2, set_loglevel/3, get_loglevels/0, - minimum_loglevel/1, posix_error/1, - safe_format/3, safe_format_chop/3,dispatch_log/8, - dispatch_log1/9]). - -%% Fallback when parse transform is not available --export([debug/1,debug/2,debug/3]). --export([info/1,info/2,info/3]). --export([notice/1,notice/2,notice/3]). --export([warning/1,warning/2,warning/3]). --export([error/1,error/2,error/3]). --export([critical/1,critical/2,critical/3]). --export([alert/1,alert/2,alert/3]). --export([emergency/1,emergency/2,emergency/3]). --export([none/1,none/2,none/3]). + log/3, log/4, log/5, + log_unsafe/4, + md/0, md/1, + trace/2, trace/3, trace_file/2, trace_file/3, trace_file/4, trace_console/1, trace_console/2, + list_all_sinks/0, clear_all_traces/0, stop_trace/1, stop_trace/3, status/0, + get_loglevel/1, get_loglevel/2, set_loglevel/2, set_loglevel/3, set_loglevel/4, get_loglevels/1, + update_loglevel_config/1, posix_error/1, set_loghwm/2, set_loghwm/3, set_loghwm/4, + safe_format/3, safe_format_chop/3, unsafe_format/2, dispatch_log/5, dispatch_log/7, dispatch_log/9, + do_log/9, do_log/10, do_log_unsafe/10, pr/2, pr/3]). -type log_level() :: debug | info | notice | warning | error | critical | alert | emergency. -type log_level_number() :: 0..7. @@ -57,125 +51,194 @@ start(App) -> start_ok(_App, ok) -> ok; start_ok(_App, {error, {already_started, _App}}) -> ok; -start_ok(App, {error, {not_started, Dep}}) -> +start_ok(App, {error, {not_started, Dep}}) -> ok = start(Dep), start(App); -start_ok(App, {error, Reason}) -> +start_ok(App, {error, Reason}) -> erlang:error({app_start_failed, App, Reason}). --spec dispatch_log(log_level(), atom(), atom(), pos_integer(), pid(), list(), string(), list()) -> - ok | {error, lager_not_running}. -%% Still used by dyn_log -dispatch_log(Severity, Module, Function, Line, Pid, Traces, Format, Args) -> - {LevelThreshold,TraceFilters} = lager_mochiglobal:get(loglevel,{?LOG_NONE,[]}), - Result= - case LevelThreshold >= lager_util:level_to_num(Severity) of - true -> lager:log(Severity,Module,Function,Line,Pid, - lager_util:maybe_utc(lager_util:localtime_ms()), - Format,Args); - _ -> ok +%% @doc Get lager metadata for current process +-spec md() -> [{atom(), any()}]. +md() -> + case erlang:get(?LAGER_MD_KEY) of + undefined -> []; + MD -> MD + end. + +%% @doc Set lager metadata for current process. +%% Will badarg if you don't supply a list of {key, value} tuples keyed by atoms. +-spec md([{atom(), any()},...]) -> ok. +md(NewMD) when is_list(NewMD) -> + %% make sure its actually a real proplist + case lists:all( + fun({Key, _Value}) when is_atom(Key) -> true; + (_) -> false + end, NewMD) of + true -> + erlang:put(?LAGER_MD_KEY, NewMD), + ok; + false -> + erlang:error(badarg) + end; +md(_) -> + erlang:error(badarg). + + +-spec dispatch_log(atom(), log_level(), list(), string(), list() | none, pos_integer(), safe | unsafe) -> ok | {error, lager_not_running} | {error, {sink_not_configured, atom()}}. +%% this is the same check that the parse transform bakes into the module at compile time +%% see lager_transform (lines 173-216) +dispatch_log(Sink, Severity, Metadata, Format, Args, Size, Safety) when is_atom(Severity)-> + SeverityAsInt=lager_util:level_to_num(Severity), + case {whereis(Sink), whereis(?DEFAULT_SINK), lager_config:get({Sink, loglevel}, {?LOG_NONE, []})} of + {undefined, undefined, _} -> {error, lager_not_running}; + {undefined, _LagerEventPid0, _} -> {error, {sink_not_configured, Sink}}; + {SinkPid, _LagerEventPid1, {Level, Traces}} when Safety =:= safe andalso ( (Level band SeverityAsInt) /= 0 orelse Traces /= [] ) -> + do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Sink, SinkPid); + {SinkPid, _LagerEventPid1, {Level, Traces}} when Safety =:= unsafe andalso ( (Level band SeverityAsInt) /= 0 orelse Traces /= [] ) -> + do_log_unsafe(Severity, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Sink, SinkPid); + _ -> ok + end. + +%% @private Should only be called externally from code generated from the parse transform +do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid) when is_atom(Severity) -> + FormatFun = fun() -> safe_format_chop(Format, Args, Size) end, + do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun). + +do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun) -> + {Destinations, TraceSinkPid} = case TraceFilters of + [] -> + {[], undefined}; + _ -> + {lager_util:check_traces(Metadata,SeverityAsInt,TraceFilters,[]), whereis(?TRACE_SINK)} end, - case TraceFilters of - [] -> Result; - Match when is_list(Match) -> - lager:log_dest(Severity,Module,Function,Line,Pid, - lager_util:maybe_utc(lager_util:localtime_ms()), - lager_util:check_traces(Traces, - lager_util:level_to_num(Severity), - TraceFilters, - []), - Format,Args); - _ -> ok + case (LevelThreshold band SeverityAsInt) /= 0 orelse Destinations /= [] of + true -> + Msg = case Args of + A when is_list(A) -> + FormatFun(); + _ -> + Format + end, + LagerMsg = lager_msg:new(Msg, + Severity, Metadata, Destinations), + case lager_config:get({Sink, async}, false) of + true -> + gen_event:notify(SinkPid, {log, LagerMsg}); + false -> + gen_event:sync_notify(SinkPid, {log, LagerMsg}) + end, + case TraceSinkPid /= undefined of + true -> + gen_event:notify(TraceSinkPid, {log, LagerMsg}); + false -> + ok + end; + false -> + ok end. -%% -%% Like dispatch_log/8 but uses a new transform where -%% level is checked before arguments are evaluated. -%% -dispatch_log1([],Severity,Mod,Fun,Line,Pid,_FTraces,Format,Args) -> - lager:log(Severity,Mod,Fun,Line,Pid, - lager_util:maybe_utc(lager_util:localtime_ms()), - Format,Args); -dispatch_log1(Match,Severity,Mod,Fun,Line,Pid,FTraces,Format,Args) - when is_list(Match) -> - lager:log(Severity,Mod,Fun,Line,Pid, - lager_util:maybe_utc(lager_util:localtime_ms()), - Format,Args), - lager:log_dest(Severity,Mod,Fun,Line,Pid, - lager_util:maybe_utc(lager_util:localtime_ms()), - lager_util:check_f_traces(FTraces, - lager_util:level_to_num(Severity), - Match,[]), - Format,Args); -dispatch_log1(_,_Severity,_Mod,_Func,_Line,_Pid,_FTraces,_Format,_Args) -> - ok. +%% @private Should only be called externally from code generated from the parse transform +%% Specifically, it would be level ++ `_unsafe' as in `info_unsafe'. +do_log_unsafe(Severity, Metadata, Format, Args, _Size, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid) when is_atom(Severity) -> + FormatFun = fun() -> unsafe_format(Format, Args) end, + do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun). -%% @private --spec log(log_level(), atom(), atom(), pos_integer(), pid(), tuple(), string(), list()) -> - ok | {error, lager_not_running}. -log(Level, Module, Function, Line, Pid, Time, Format, Args) -> - Timestamp = lager_util:format_time(Time), - Msg = [["[", atom_to_list(Level), "] "], - io_lib:format("~p@~p:~p:~p ", [Pid, Module, Function, Line]), - safe_format_chop(Format, Args, 4096)], - safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}). +%% backwards compatible with beams compiled with lager 1.x +dispatch_log(Severity, _Module, _Function, _Line, _Pid, Metadata, Format, Args, Size) -> + dispatch_log(Severity, Metadata, Format, Args, Size). -%% @private --spec log_dest(log_level(), atom(), atom(), pos_integer(), pid(), tuple(), list(), string(), list()) -> - ok | {error, lager_not_running}. -log_dest(_Level, _Module, _Function, _Line, _Pid, _Time, [], _Format, _Args) -> - ok; -log_dest(Level, Module, Function, Line, Pid, Time, Dest, Format, Args) -> - Timestamp = lager_util:format_time(Time), - Msg = [["[", atom_to_list(Level), "] "], - io_lib:format("~p@~p:~p:~p ", [Pid, Module, Function, Line]), - safe_format_chop(Format, Args, 4096)], - safe_notify({log, Dest, lager_util:level_to_num(Level), Timestamp, Msg}). +%% backwards compatible with beams compiled with lager 2.x +dispatch_log(Severity, Metadata, Format, Args, Size) -> + dispatch_log(?DEFAULT_SINK, Severity, Metadata, Format, Args, Size, safe). +%% backwards compatible with beams compiled with lager 2.x +do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, SinkPid) -> + do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, + LevelThreshold, TraceFilters, ?DEFAULT_SINK, SinkPid). + + +%% TODO: +%% Consider making log2/4 that takes the Level, Pid and Message params of log/3 +%% along with a Sink param?? %% @doc Manually log a message into lager without using the parse transform. --spec log(log_level(), pid(), list()) -> ok | {error, lager_not_running}. -log(Level, Pid, Message) -> - Timestamp = lager_util:format_time(), - Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]), - safe_format_chop("~s", [Message], 4096)], - safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}). +-spec log(log_level(), pid() | atom() | [tuple(),...], list()) -> ok | {error, lager_not_running}. +log(Level, Pid, Message) when is_pid(Pid); is_atom(Pid) -> + dispatch_log(Level, [{pid,Pid}], Message, [], ?DEFAULT_TRUNCATION); +log(Level, Metadata, Message) when is_list(Metadata) -> + dispatch_log(Level, Metadata, Message, [], ?DEFAULT_TRUNCATION). + +%% @doc Manually log a message into lager without using the parse transform. +-spec log(log_level(), pid() | atom() | [tuple(),...], string(), list()) -> ok | {error, lager_not_running}. +log(Level, Pid, Format, Args) when is_pid(Pid); is_atom(Pid) -> + dispatch_log(Level, [{pid,Pid}], Format, Args, ?DEFAULT_TRUNCATION); +log(Level, Metadata, Format, Args) when is_list(Metadata) -> + dispatch_log(Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION). + +log_unsafe(Level, Metadata, Format, Args) when is_list(Metadata) -> + dispatch_log(?DEFAULT_SINK, Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION, unsafe). + %% @doc Manually log a message into lager without using the parse transform. --spec log(log_level(), pid(), string(), list()) -> ok | {error, lager_not_running}. -log(Level, Pid, Format, Args) -> - Timestamp = lager_util:format_time(), - Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]), - safe_format_chop(Format, Args, 4096)], - safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}). +-spec log(atom(), log_level(), pid() | atom() | [tuple(),...], string(), list()) -> ok | {error, lager_not_running}. +log(Sink, Level, Pid, Format, Args) when is_pid(Pid); is_atom(Pid) -> + dispatch_log(Sink, Level, [{pid,Pid}], Format, Args, ?DEFAULT_TRUNCATION, safe); +log(Sink, Level, Metadata, Format, Args) when is_list(Metadata) -> + dispatch_log(Sink, Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION, safe). + +validate_trace_filters(Filters, Level, Backend) -> + Sink = proplists:get_value(sink, Filters, ?DEFAULT_SINK), + {Sink, + lager_util:validate_trace({ + proplists:delete(sink, Filters), + Level, + Backend + }) + }. trace_file(File, Filter) -> - trace_file(File, Filter, debug). + trace_file(File, Filter, debug, []). + +trace_file(File, Filter, Level) when is_atom(Level) -> + trace_file(File, Filter, Level, []); + +trace_file(File, Filter, Options) when is_list(Options) -> + trace_file(File, Filter, debug, Options). -trace_file(File, Filter, Level) -> - Trace0 = {Filter, Level, {lager_file_backend, File}}, - case lager_util:validate_trace(Trace0) of - {ok, Trace} -> - Handlers = gen_event:which_handlers(lager_event), +trace_file(File, Filter, Level, Options) -> + FileName = lager_util:expand_path(File), + case validate_trace_filters(Filter, Level, {lager_file_backend, FileName}) of + {Sink, {ok, Trace}} -> + Handlers = lager_config:global_get(handlers, []), %% check if this file backend is already installed - case lists:member({lager_file_backend, File}, Handlers) of - false -> - %% install the handler - supervisor:start_child(lager_handler_watcher_sup, - [lager_event, {lager_file_backend, File}, {File, none}]); - _ -> - ok - end, - %% install the trace. - {MinLevel, Traces} = lager_mochiglobal:get(loglevel), - case lists:member(Trace, Traces) of - false -> - lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]}); - _ -> ok + Res = case lists:keyfind({lager_file_backend, FileName}, 1, Handlers) of + false -> + %% install the handler + LogFileConfig = + lists:keystore(level, 1, + lists:keystore(file, 1, + Options, + {file, FileName}), + {level, none}), + HandlerInfo = + lager_app:start_handler(Sink, {lager_file_backend, FileName}, + LogFileConfig), + lager_config:global_set(handlers, [HandlerInfo|Handlers]), + {ok, installed}; + {_Watcher, _Handler, Sink} -> + {ok, exists}; + {_Watcher, _Handler, _OtherSink} -> + {error, file_in_use} end, - {ok, Trace}; - Error -> + case Res of + {ok, _} -> + add_trace_to_loglevel_config(Trace, Sink), + {ok, {{lager_file_backend, FileName}, Filter, Level}}; + {error, _} = E -> + E + end; + {_Sink, Error} -> Error end. @@ -183,30 +246,46 @@ trace_console(Filter) -> trace_console(Filter, debug). trace_console(Filter, Level) -> - Trace0 = {Filter, Level, lager_console_backend}, - case lager_util:validate_trace(Trace0) of - {ok, Trace} -> - {MinLevel, Traces} = lager_mochiglobal:get(loglevel), - case lists:member(Trace, Traces) of - false -> - lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]}); - _ -> ok - end, - {ok, Trace}; - Error -> + trace(lager_console_backend, Filter, Level). + +trace(Backend, Filter) -> + trace(Backend, Filter, debug). + +trace({lager_file_backend, File}, Filter, Level) -> + trace_file(File, Filter, Level); + +trace(Backend, Filter, Level) -> + case validate_trace_filters(Filter, Level, Backend) of + {Sink, {ok, Trace}} -> + add_trace_to_loglevel_config(Trace, Sink), + {ok, {Backend, Filter, Level}}; + {_Sink, Error} -> Error end. -stop_trace({_Filter, _Level, Target} = Trace) -> - {MinLevel, Traces} = lager_mochiglobal:get(loglevel), +stop_trace(Backend, Filter, Level) -> + case validate_trace_filters(Filter, Level, Backend) of + {Sink, {ok, Trace}} -> + stop_trace_int(Trace, Sink); + {_Sink, Error} -> + Error + end. + +stop_trace({Backend, Filter, Level}) -> + stop_trace(Backend, Filter, Level). + +stop_trace_int({Backend, _Filter, _Level} = Trace, Sink) -> + {Level, Traces} = lager_config:get({Sink, loglevel}), NewTraces = lists:delete(Trace, Traces), - lager_mochiglobal:put(loglevel, {MinLevel, NewTraces}), - case get_loglevel(Target) of + _ = lager_util:trace_filter([ element(1, T) || T <- NewTraces ]), + %MinLevel = minimum_loglevel(get_loglevels() ++ get_trace_levels(NewTraces)), + lager_config:set({Sink, loglevel}, {Level, NewTraces}), + case get_loglevel(Sink, Backend) of none -> %% check no other traces point here - case lists:keyfind(Target, 3, NewTraces) of + case lists:keyfind(Backend, 3, NewTraces) of false -> - gen_event:delete_handler(lager_event, Target, []); + gen_event:delete_handler(Sink, Backend, []); _ -> ok end; @@ -215,64 +294,136 @@ stop_trace({_Filter, _Level, Target} = Trace) -> end, ok. +list_all_sinks() -> + sets:to_list( + lists:foldl(fun({_Watcher, _Handler, Sink}, Set) -> + sets:add_element(Sink, Set) + end, + sets:new(), + lager_config:global_get(handlers, []))). + +clear_traces_by_sink(Sinks) -> + lists:foreach(fun(S) -> + {Level, _Traces} = + lager_config:get({S, loglevel}), + lager_config:set({S, loglevel}, + {Level, []}) + end, + Sinks). + clear_all_traces() -> - {MinLevel, _Traces} = lager_mochiglobal:get(loglevel), - lager_mochiglobal:put(loglevel, {MinLevel, []}), - [begin - case get_loglevel(Handler) of - none -> - gen_event:delete_handler(lager_event, Handler, []); - _ -> - ok - end - end || Handler <- gen_event:which_handlers(lager_event)], - ok. + Handlers = lager_config:global_get(handlers, []), + clear_traces_by_sink(list_all_sinks()), + _ = lager_util:trace_filter(none), + lager_config:global_set(handlers, + lists:filter( + fun({Handler, _Watcher, Sink}) -> + case get_loglevel(Sink, Handler) of + none -> + gen_event:delete_handler(Sink, Handler, []), + false; + _ -> + true + end + end, Handlers)). + +find_traces(Sinks) -> + lists:foldl(fun(S, Acc) -> + {_Level, Traces} = lager_config:get({S, loglevel}), + Acc ++ lists:map(fun(T) -> {S, T} end, Traces) + end, + [], + Sinks). status() -> - Handlers = gen_event:which_handlers(lager_event), + Handlers = lager_config:global_get(handlers, []), + Sinks = lists:sort(list_all_sinks()), + Traces = find_traces(Sinks), + TraceCount = case length(Traces) of + 0 -> 1; + N -> N + end, Status = ["Lager status:\n", [begin - Level = get_loglevel(Handler), + Level = get_loglevel(Sink, Handler), case Handler of {lager_file_backend, File} -> - io_lib:format("File ~s at level ~p\n", [File, Level]); + io_lib:format("File ~s (~s) at level ~p\n", [File, Sink, Level]); lager_console_backend -> - io_lib:format("Console at level ~p\n", [Level]); + io_lib:format("Console (~s) at level ~p\n", [Sink, Level]); _ -> [] end - end || Handler <- Handlers], + end || {Handler, _Watcher, Sink} <- lists:sort(fun({_, _, S1}, + {_, _, S2}) -> S1 =< S2 end, + Handlers)], "Active Traces:\n", [begin - io_lib:format("Tracing messages matching ~p at level ~p to ~p\n", - [Filter, lager_util:num_to_level(Level), Destination]) - end || {Filter, Level, Destination} <- element(2, lager_mochiglobal:get(loglevel))]], + LevelName = case Level of + {mask, Mask} -> + case lager_util:mask_to_levels(Mask) of + [] -> none; + Levels -> hd(Levels) + end; + Num -> + lager_util:num_to_level(Num) + end, + io_lib:format("Tracing messages matching ~p (sink ~s) at level ~p to ~p\n", + [Filter, Sink, LevelName, Destination]) + end || {Sink, {Filter, Level, Destination}} <- Traces], + [ + "Tracing Reductions:\n", + case ?DEFAULT_TRACER:info('query') of + {null, false} -> ""; + Query -> io_lib:format("~p~n", [Query]) + end + ], + [ + "Tracing Statistics:\n ", + [ begin + [" ", atom_to_list(Table), ": ", + integer_to_list(?DEFAULT_TRACER:info(Table) div TraceCount), + "\n"] + end || Table <- [input, output, filter] ] + ]], io:put_chars(Status). + %% @doc Set the loglevel for a particular backend. set_loglevel(Handler, Level) when is_atom(Level) -> - Reply = gen_event:call(lager_event, Handler, {set_loglevel, Level}, infinity), - %% recalculate min log level - MinLog = minimum_loglevel(get_loglevels()), - {_, Traces} = lager_mochiglobal:get(loglevel), - lager_mochiglobal:put(loglevel, {MinLog, Traces}), - Reply. + set_loglevel(?DEFAULT_SINK, Handler, undefined, Level). %% @doc Set the loglevel for a particular backend that has multiple identifiers %% (eg. the file backend). set_loglevel(Handler, Ident, Level) when is_atom(Level) -> - io:format("handler: ~p~n", [{Handler, Ident}]), - Reply = gen_event:call(lager_event, {Handler, Ident}, {set_loglevel, Level}, infinity), - %% recalculate min log level - MinLog = minimum_loglevel(get_loglevels()), - {_, Traces} = lager_mochiglobal:get(loglevel), - lager_mochiglobal:put(loglevel, {MinLog, Traces}), + set_loglevel(?DEFAULT_SINK, Handler, Ident, Level). + +%% @doc Set the loglevel for a particular sink's backend that potentially has +%% multiple identifiers. (Use `undefined' if it doesn't have any.) +set_loglevel(Sink, Handler, Ident, Level) when is_atom(Level) -> + HandlerArg = case Ident of + undefined -> Handler; + _ -> {Handler, Ident} + end, + Reply = gen_event:call(Sink, HandlerArg, {set_loglevel, Level}, infinity), + update_loglevel_config(Sink), Reply. -%% @doc Get the loglevel for a particular backend. In the case that the backend -%% has multiple identifiers, the lowest is returned + +%% @doc Get the loglevel for a particular backend on the default sink. In the case that the backend +%% has multiple identifiers, the lowest is returned. get_loglevel(Handler) -> - case gen_event:call(lager_event, Handler, get_loglevel, infinity) of + get_loglevel(?DEFAULT_SINK, Handler). + +%% @doc Get the loglevel for a particular sink's backend. In the case that the backend +%% has multiple identifiers, the lowest is returned. +get_loglevel(Sink, Handler) -> + case gen_event:call(Sink, Handler, get_loglevel, infinity) of + {mask, Mask} -> + case lager_util:mask_to_levels(Mask) of + [] -> none; + Levels -> hd(Levels) + end; X when is_integer(X) -> lager_util:num_to_level(X); Y -> Y @@ -286,28 +437,58 @@ posix_error(Error) when is_atom(Error) -> Message -> Message end; posix_error(Error) -> - safe_format_chop("~p", [Error], 4096). + safe_format_chop("~p", [Error], ?DEFAULT_TRUNCATION). %% @private -get_loglevels() -> - [gen_event:call(lager_event, Handler, get_loglevel, infinity) || - Handler <- gen_event:which_handlers(lager_event)]. +get_loglevels(Sink) -> + [gen_event:call(Sink, Handler, get_loglevel, infinity) || + Handler <- gen_event:which_handlers(Sink)]. + +%% @doc Set the loghwm for the default sink. +set_loghwm(Handler, Hwm) when is_integer(Hwm) -> + set_loghwm(?DEFAULT_SINK, Handler, Hwm). + +%% @doc Set the loghwm for a particular backend. +set_loghwm(Sink, Handler, Hwm) when is_integer(Hwm) -> + gen_event:call(Sink, Handler, {set_loghwm, Hwm}, infinity). + +%% @doc Set the loghwm (log high water mark) for file backends with multiple identifiers +set_loghwm(Sink, Handler, Ident, Hwm) when is_integer(Hwm) -> + gen_event:call(Sink, {Handler, Ident}, {set_loghwm, Hwm}, infinity). %% @private -minimum_loglevel([]) -> - -1; %% lower than any log level, logging off -minimum_loglevel(Levels) -> - erlang:hd(lists:reverse(lists:sort(Levels))). - -safe_notify(Event) -> - case whereis(lager_event) of - undefined -> - %% lager isn't running - {error, lager_not_running}; - Pid -> - gen_event:sync_notify(Pid, Event) +add_trace_to_loglevel_config(Trace, Sink) -> + {MinLevel, Traces} = lager_config:get({Sink, loglevel}), + case lists:member(Trace, Traces) of + false -> + NewTraces = [Trace|Traces], + _ = lager_util:trace_filter([ element(1, T) || T <- NewTraces]), + lager_config:set({Sink, loglevel}, {MinLevel, [Trace|Traces]}); + _ -> + ok end. +%% @doc recalculate min log level +update_loglevel_config(error_logger) -> + %% Not a sink under our control, part of the Erlang logging + %% utility that error_logger_lager_h attaches to + true; +update_loglevel_config(Sink) -> + {_, Traces} = lager_config:get({Sink, loglevel}, {ignore_me, []}), + MinLog = minimum_loglevel(get_loglevels(Sink)), + lager_config:set({Sink, loglevel}, {MinLog, Traces}). + +%% @private +minimum_loglevel(Levels) -> + lists:foldl(fun({mask, Mask}, Acc) -> + Mask bor Acc; + (Level, Acc) when is_integer(Level) -> + {mask, Mask} = lager_util:config_to_mask(lager_util:num_to_level(Level)), + Mask bor Acc; + (_, Acc) -> + Acc + end, 0, Levels). + %% @doc Print the format string `Fmt' with `Args' safely with a size %% limit of `Limit'. If the format string is invalid, or not enough %% arguments are supplied 'FORMAT ERROR' is printed with the offending @@ -317,8 +498,7 @@ safe_format(Fmt, Args, Limit) -> safe_format(Fmt, Args, Limit, []). safe_format(Fmt, Args, Limit, Options) -> - try lager_trunc_io:format(Fmt, Args, Limit, Options) of - Result -> Result + try lager_trunc_io:format(Fmt, Args, Limit, Options) catch _:_ -> lager_trunc_io:format("FORMAT ERROR: ~p ~p", [Fmt, Args], Limit) end. @@ -326,70 +506,77 @@ safe_format(Fmt, Args, Limit, Options) -> %% @private safe_format_chop(Fmt, Args, Limit) -> safe_format(Fmt, Args, Limit, [{chomp, true}]). + +%% @private Print the format string `Fmt' with `Args' without a size limit. +%% This is unsafe because the output of this function is unbounded. %% -%% when code is not compiled with parse_transform the following code -%% is used instead. maybe warn about the fact? +%% Log messages with unbounded size will kill your application dead as +%% OTP mechanisms stuggle to cope with them. So this function is +%% intended <b>only</b> for messages which have a reasonable bounded +%% size before they're formatted. %% -debug(Fmt) -> dyn_log(debug, [], Fmt, []). -debug(Fmt,Args) -> dyn_log(debug, [], Fmt, Args). -debug(Attrs,Fmt,Args) -> dyn_log(debug, Attrs, Fmt, Args). - -info(Fmt) -> dyn_log(info, [], Fmt, []). -info(Fmt,Args) -> dyn_log(info, [], Fmt, Args). -info(Attrs,Fmt,Args) -> dyn_log(info, Attrs, Fmt, Args). - -notice(Fmt) -> dyn_log(notice, [], Fmt, []). -notice(Fmt,Args) -> dyn_log(notice, [], Fmt, Args). -notice(Attrs,Fmt,Args) -> dyn_log(notice, Attrs, Fmt, Args). - -warning(Fmt) -> dyn_log(warning, [], Fmt, []). -warning(Fmt,Args) -> dyn_log(warning, [], Fmt, Args). -warning(Attrs,Fmt,Args) -> dyn_log(warning, Attrs, Fmt, Args). - -error(Fmt) -> dyn_log(error, [], Fmt, []). -error(Fmt,Args) -> dyn_log(error, [], Fmt, Args). -error(Attrs,Fmt,Args) -> dyn_log(error, Attrs, Fmt, Args). - -critical(Fmt) -> dyn_log(critical, [], Fmt, []). -critical(Fmt,Args) -> dyn_log(critical, [], Fmt, Args). -critical(Attrs,Fmt,Args) -> dyn_log(critical, Attrs, Fmt, Args). - -alert(Fmt) -> dyn_log(alert, [], Fmt, []). -alert(Fmt,Args) -> dyn_log(alert, [], Fmt, Args). -alert(Attrs,Fmt,Args) -> dyn_log(alert, Attrs, Fmt, Args). - -emergency(Fmt) -> dyn_log(emergency, [], Fmt, []). -emergency(Fmt,Args) -> dyn_log(emergency, [], Fmt, Args). -emergency(Attrs,Fmt,Args) -> dyn_log(emergency, Attrs, Fmt, Args). - -none(Fmt) -> dyn_log(none, [], Fmt, []). -none(Fmt,Args) -> dyn_log(none, [], Fmt, Args). -none(Attrs,Fmt,Args) -> dyn_log(none, Attrs, Fmt, Args). - -%% @private --spec dyn_log(log_level(), list(), string(), list()) -> - ok | {error, lager_not_running}. +%% If the format string is invalid or not enough arguments are +%% supplied a 'FORMAT ERROR' message is printed instead with the +%% offending arguments. The caller is NOT crashed. +unsafe_format(Fmt, Args) -> + try io_lib:format(Fmt, Args) + catch + _:_ -> io_lib:format("FORMAT ERROR: ~p ~p", [Fmt, Args]) + end. -dyn_log(Severity, Attrs, Fmt, Args) -> - try erlang:error(fail) of - _ -> strange +%% @doc Print a record lager found during parse transform +pr(Record, Module) when is_tuple(Record), is_atom(element(1, Record)) -> + pr(Record, Module, []); +pr(Record, _) -> + Record. + +%% @doc Print a record lager found during parse transform +pr(Record, Module, Options) when is_tuple(Record), is_atom(element(1, Record)), is_list(Options) -> + try + case is_record_known(Record, Module) of + false -> + Record; + {RecordName, RecordFields} -> + {'$lager_record', RecordName, + zip(RecordFields, tl(tuple_to_list(Record)), Module, Options, [])} + end catch - error:_ -> - case erlang:get_stacktrace() of - [_,{M,F,_A}|_] -> - dispatch_log(Severity, M, F, 0, self(), - [{module,M},{function,F}, - {pid,pid_to_list(self())}| - Attrs], - Fmt, Args); - [_,{M,F,_A,Loc}|_] -> - L = proplists:get_value(line,Loc,0), - dispatch_log(Severity, M, F, L, self(), - [{module,M},{function,F}, - {line,L},{pid,pid_to_list(self())}| - Attrs], - Fmt, Args); - [] -> - erlang:display({Severity, Fmt}) - end + error:undef -> + Record + end; +pr(Record, _, _) -> + Record. + +zip([FieldName|RecordFields], [FieldValue|Record], Module, Options, ToReturn) -> + Compress = lists:member(compress, Options), + case is_tuple(FieldValue) andalso + tuple_size(FieldValue) > 0 andalso + is_atom(element(1, FieldValue)) andalso + is_record_known(FieldValue, Module) of + false when Compress andalso FieldValue =:= undefined -> + zip(RecordFields, Record, Module, Options, ToReturn); + false -> + zip(RecordFields, Record, Module, Options, [{FieldName, FieldValue}|ToReturn]); + _Else -> + F = {FieldName, pr(FieldValue, Module, Options)}, + zip(RecordFields, Record, Module, Options, [F|ToReturn]) + end; +zip([], [], _Module, _Compress, ToReturn) -> + lists:reverse(ToReturn). + +is_record_known(Record, Module) -> + Name = element(1, Record), + Attrs = Module:module_info(attributes), + case lists:keyfind(lager_records, 1, Attrs) of + false -> false; + {lager_records, Records} -> + case lists:keyfind(Name, 1, Records) of + false -> false; + {Name, RecordFields} -> + case (tuple_size(Record) - 1) =:= length(RecordFields) of + false -> false; + true -> {Name, RecordFields} + end + end end. + |