summaryrefslogtreecommitdiff
path: root/deps/lager/src/lager_util.erl
diff options
context:
space:
mode:
Diffstat (limited to 'deps/lager/src/lager_util.erl')
-rw-r--r--deps/lager/src/lager_util.erl549
1 files changed, 438 insertions, 111 deletions
diff --git a/deps/lager/src/lager_util.erl b/deps/lager/src/lager_util.erl
index f05d0e6..76fdda6 100644
--- a/deps/lager/src/lager_util.erl
+++ b/deps/lager/src/lager_util.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
@@ -18,45 +18,128 @@
-include_lib("kernel/include/file.hrl").
--export([levels/0, level_to_num/1, num_to_level/1, open_logfile/2,
- ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1,
- localtime_ms/0, maybe_utc/1, parse_rotation_date_spec/1,
- calculate_next_rotation/1, validate_trace/1, check_traces/4]).
--export([check_f_traces/4]).
+-export([levels/0, level_to_num/1, level_to_chr/1,
+ num_to_level/1, config_to_mask/1, config_to_levels/1, mask_to_levels/1,
+ open_logfile/2, ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1,
+ localtime_ms/0, localtime_ms/1, maybe_utc/1, parse_rotation_date_spec/1,
+ calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3,
+ trace_filter/1, trace_filter/2, expand_path/1, check_hwm/1, make_internal_sink_name/1]).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
+-include("lager.hrl").
+
levels() ->
- [debug, info, notice, warning, error, critical, alert, emergency].
-
-level_to_num(debug) -> 7;
-level_to_num(info) -> 6;
-level_to_num(notice) -> 5;
-level_to_num(warning) -> 4;
-level_to_num(error) -> 3;
-level_to_num(critical) -> 2;
-level_to_num(alert) -> 1;
-level_to_num(emergency) -> 0;
-level_to_num(none) -> -1.
-
-num_to_level(7) -> debug;
-num_to_level(6) -> info;
-num_to_level(5) -> notice;
-num_to_level(4) -> warning;
-num_to_level(3) -> error;
-num_to_level(2) -> critical;
-num_to_level(1) -> alert;
-num_to_level(0) -> emergency;
-num_to_level(-1) -> none.
+ [debug, info, notice, warning, error, critical, alert, emergency, none].
+
+level_to_num(debug) -> ?DEBUG;
+level_to_num(info) -> ?INFO;
+level_to_num(notice) -> ?NOTICE;
+level_to_num(warning) -> ?WARNING;
+level_to_num(error) -> ?ERROR;
+level_to_num(critical) -> ?CRITICAL;
+level_to_num(alert) -> ?ALERT;
+level_to_num(emergency) -> ?EMERGENCY;
+level_to_num(none) -> ?LOG_NONE.
+
+level_to_chr(debug) -> $D;
+level_to_chr(info) -> $I;
+level_to_chr(notice) -> $N;
+level_to_chr(warning) -> $W;
+level_to_chr(error) -> $E;
+level_to_chr(critical) -> $C;
+level_to_chr(alert) -> $A;
+level_to_chr(emergency) -> $M;
+level_to_chr(none) -> $ .
+
+num_to_level(?DEBUG) -> debug;
+num_to_level(?INFO) -> info;
+num_to_level(?NOTICE) -> notice;
+num_to_level(?WARNING) -> warning;
+num_to_level(?ERROR) -> error;
+num_to_level(?CRITICAL) -> critical;
+num_to_level(?ALERT) -> alert;
+num_to_level(?EMERGENCY) -> emergency;
+num_to_level(?LOG_NONE) -> none.
+
+-spec config_to_mask(atom()|string()) -> {'mask', integer()}.
+config_to_mask(Conf) ->
+ Levels = config_to_levels(Conf),
+ {mask, lists:foldl(fun(Level, Acc) ->
+ level_to_num(Level) bor Acc
+ end, 0, Levels)}.
+
+-spec mask_to_levels(non_neg_integer()) -> [lager:log_level()].
+mask_to_levels(Mask) ->
+ mask_to_levels(Mask, levels(), []).
+
+mask_to_levels(_Mask, [], Acc) ->
+ lists:reverse(Acc);
+mask_to_levels(Mask, [Level|Levels], Acc) ->
+ NewAcc = case (level_to_num(Level) band Mask) /= 0 of
+ true ->
+ [Level|Acc];
+ false ->
+ Acc
+ end,
+ mask_to_levels(Mask, Levels, NewAcc).
+
+-spec config_to_levels(atom()|string()) -> [lager:log_level()].
+config_to_levels(Conf) when is_atom(Conf) ->
+ config_to_levels(atom_to_list(Conf));
+config_to_levels([$! | Rest]) ->
+ levels() -- config_to_levels(Rest);
+config_to_levels([$=, $< | Rest]) ->
+ [_|Levels] = config_to_levels_int(Rest),
+ lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
+config_to_levels([$<, $= | Rest]) ->
+ [_|Levels] = config_to_levels_int(Rest),
+ lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
+config_to_levels([$>, $= | Rest]) ->
+ config_to_levels_int(Rest);
+config_to_levels([$=, $> | Rest]) ->
+ config_to_levels_int(Rest);
+config_to_levels([$= | Rest]) ->
+ [level_to_atom(Rest)];
+config_to_levels([$< | Rest]) ->
+ Levels = config_to_levels_int(Rest),
+ lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
+config_to_levels([$> | Rest]) ->
+ [_|Levels] = config_to_levels_int(Rest),
+ lists:filter(fun(E) -> lists:member(E, Levels) end, levels());
+config_to_levels(Conf) ->
+ config_to_levels_int(Conf).
+
+%% internal function to break the recursion loop
+config_to_levels_int(Conf) ->
+ Level = level_to_atom(Conf),
+ lists:dropwhile(fun(E) -> E /= Level end, levels()).
+
+level_to_atom(String) ->
+ Levels = levels(),
+ try list_to_existing_atom(String) of
+ Atom ->
+ case lists:member(Atom, Levels) of
+ true ->
+ Atom;
+ false ->
+ erlang:error(badarg)
+ end
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
open_logfile(Name, Buffer) ->
case filelib:ensure_dir(Name) of
ok ->
Options = [append, raw] ++
- if Buffer == true -> [delayed_write];
- true -> []
+ case Buffer of
+ {Size, Interval} when is_integer(Interval), Interval >= 0, is_integer(Size), Size >= 0 ->
+ [{delayed_write, Size, Interval}];
+ _ -> []
end,
case file:open(Name, Options) of
{ok, FD} ->
@@ -80,8 +163,8 @@ ensure_logfile(Name, FD, Inode, Buffer) ->
{ok, {FD, Inode, FInfo#file_info.size}};
false ->
%% delayed write can cause file:close not to do a close
- file:close(FD),
- file:close(FD),
+ _ = file:close(FD),
+ _ = file:close(FD),
case open_logfile(Name, Buffer) of
{ok, {FD2, Inode3, Size}} ->
%% inode changed, file was probably moved and
@@ -93,8 +176,8 @@ ensure_logfile(Name, FD, Inode, Buffer) ->
end;
_ ->
%% delayed write can cause file:close not to do a close
- file:close(FD),
- file:close(FD),
+ _ = file:close(FD),
+ _ = file:close(FD),
case open_logfile(Name, Buffer) of
{ok, {FD2, Inode3, Size}} ->
%% file was removed
@@ -106,10 +189,15 @@ ensure_logfile(Name, FD, Inode, Buffer) ->
%% returns localtime with milliseconds included
localtime_ms() ->
- {_, _, Micro} = Now = os:timestamp(),
+ Now = os:timestamp(),
+ localtime_ms(Now).
+
+localtime_ms(Now) ->
+ {_, _, Micro} = Now,
{Date, {Hours, Minutes, Seconds}} = calendar:now_to_local_time(Now),
{Date, {Hours, Minutes, Seconds, Micro div 1000 rem 1000}}.
+
maybe_utc({Date, {H, M, S, Ms}}) ->
case lager_stdlib:maybe_utc({Date, {H, M, S}}) of
{utc, {Date1, {H1, M1, S1}}} ->
@@ -118,31 +206,35 @@ maybe_utc({Date, {H, M, S, Ms}}) ->
{Date1, {H1, M1, S1, Ms}}
end.
+%% renames failing are OK
rotate_logfile(File, 0) ->
file:delete(File);
rotate_logfile(File, 1) ->
- file:rename(File, File++".0"),
- rotate_logfile(File, 0);
+ case file:rename(File, File++".0") of
+ ok ->
+ ok;
+ _ ->
+ rotate_logfile(File, 0)
+ end;
rotate_logfile(File, Count) ->
- file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++
- integer_to_list(Count - 1)),
+ _ = file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++ integer_to_list(Count - 1)),
rotate_logfile(File, Count - 1).
format_time() ->
format_time(maybe_utc(localtime_ms())).
format_time({utc, {{Y, M, D}, {H, Mi, S, Ms}}}) ->
- {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
- io_lib:format("~2..0b:~2..0b:~2..0b.~3..0b UTC", [H, Mi, S, Ms])};
+ {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
+ [i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms), $ , $U, $T, $C]};
format_time({{Y, M, D}, {H, Mi, S, Ms}}) ->
- {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
- io_lib:format("~2..0b:~2..0b:~2..0b.~3..0b", [H, Mi, S, Ms])};
+ {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
+ [i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms)]};
format_time({utc, {{Y, M, D}, {H, Mi, S}}}) ->
- {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
- io_lib:format("~2..0b:~2..0b:~2..0b UTC", [H, Mi, S])};
+ {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
+ [i2l(H), $:, i2l(Mi), $:, i2l(S), $ , $U, $T, $C]};
format_time({{Y, M, D}, {H, Mi, S}}) ->
- {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
- io_lib:format("~2..0b:~2..0b:~2..0b", [H, Mi, S])}.
+ {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
+ [i2l(H), $:, i2l(Mi), $:, i2l(S)]}.
parse_rotation_day_spec([], Res) ->
{ok, Res ++ [{hour, 0}]};
@@ -272,23 +364,33 @@ calculate_next_rotation([{date, Date}|T], {{Year, Month, Day}, _} = Now) ->
NewNow = calendar:gregorian_seconds_to_datetime(Seconds),
calculate_next_rotation(T, NewNow).
-validate_trace({Filter, Level, {Destination, ID}}) when is_list(Filter), is_atom(Level), is_atom(Destination) ->
+-spec trace_filter(Query :: 'none' | [tuple()]) -> {ok, any()}.
+trace_filter(Query) ->
+ trace_filter(?DEFAULT_TRACER, Query).
+
+%% TODO: Support multiple trace modules
+%-spec trace_filter(Module :: atom(), Query :: 'none' | [tuple()]) -> {ok, any()}.
+trace_filter(Module, Query) when Query == none; Query == [] ->
+ {ok, _} = glc:compile(Module, glc:null(false));
+trace_filter(Module, Query) when is_list(Query) ->
+ {ok, _} = glc:compile(Module, glc_lib:reduce(trace_any(Query))).
+
+validate_trace({Filter, Level, {Destination, ID}}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
case validate_trace({Filter, Level, Destination}) of
{ok, {F, L, D}} ->
{ok, {F, L, {D, ID}}};
Error ->
Error
end;
-validate_trace({Filter, Level, Destination}) when is_list(Filter), is_atom(Level), is_atom(Destination) ->
- try level_to_num(Level) of
+validate_trace({Filter, Level, Destination}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
+ ValidFilter = validate_trace_filter(Filter),
+ try config_to_mask(Level) of
+ _ when not ValidFilter ->
+ {error, invalid_trace};
+ L when is_list(Filter) ->
+ {ok, {trace_all(Filter), L, Destination}};
L ->
- case lists:all(fun({Key, _Value}) when is_atom(Key) -> true; (_) ->
- false end, Filter) of
- true ->
- {ok, {Filter, L, Destination}};
- _ ->
- {error, invalid_filter}
- end
+ {ok, {Filter, L, Destination}}
catch
_:_ ->
{error, invalid_level}
@@ -296,51 +398,140 @@ validate_trace({Filter, Level, Destination}) when is_list(Filter), is_atom(Level
validate_trace(_) ->
{error, invalid_trace}.
-
-check_f_traces(_, _, [], Acc) ->
- lists:flatten(Acc);
-check_f_traces(AttrFun, Level, [{_, FilterLevel, _}|Flows], Acc)
- when Level > FilterLevel ->
- check_f_traces(AttrFun, Level, Flows, Acc);
-check_f_traces(AttrFun, Level, [Flow|Flows], Acc) ->
- check_f_traces(AttrFun, Level, Flows, [check_f_trace(AttrFun, Flow)|Acc]).
-
-check_f_trace(AttrFun, {Filter, _Level, Dest}) ->
- case lists:all(AttrFun, Filter) of
- true -> Dest;
- false -> []
- end.
-
+validate_trace_filter(Filter) when is_tuple(Filter), is_atom(element(1, Filter)) =:= false ->
+ false;
+validate_trace_filter(Filter) ->
+ case lists:all(fun({Key, '*'}) when is_atom(Key) -> true;
+ ({Key, '!'}) when is_atom(Key) -> true;
+ ({Key, _Value}) when is_atom(Key) -> true;
+ ({Key, '=', _Value}) when is_atom(Key) -> true;
+ ({Key, '<', _Value}) when is_atom(Key) -> true;
+ ({Key, '>', _Value}) when is_atom(Key) -> true;
+ (_) -> false end, Filter) of
+ true ->
+ true;
+ _ ->
+ false
+ end.
+
+trace_all(Query) ->
+ glc:all(trace_acc(Query)).
+
+trace_any(Query) ->
+ glc:any(Query).
+
+trace_acc(Query) ->
+ trace_acc(Query, []).
+
+trace_acc([], Acc) ->
+ lists:reverse(Acc);
+trace_acc([{Key, '*'}|T], Acc) ->
+ trace_acc(T, [glc:wc(Key)|Acc]);
+trace_acc([{Key, '!'}|T], Acc) ->
+ trace_acc(T, [glc:nf(Key)|Acc]);
+trace_acc([{Key, Val}|T], Acc) ->
+ trace_acc(T, [glc:eq(Key, Val)|Acc]);
+trace_acc([{Key, '=', Val}|T], Acc) ->
+ trace_acc(T, [glc:eq(Key, Val)|Acc]);
+trace_acc([{Key, '>', Val}|T], Acc) ->
+ trace_acc(T, [glc:gt(Key, Val)|Acc]);
+trace_acc([{Key, '<', Val}|T], Acc) ->
+ trace_acc(T, [glc:lt(Key, Val)|Acc]).
+
check_traces(_, _, [], Acc) ->
lists:flatten(Acc);
-check_traces(Attrs, Level, [{_, FilterLevel, _}|Flows], Acc) when Level > FilterLevel ->
+check_traces(Attrs, Level, [{_, {mask, FilterLevel}, _}|Flows], Acc) when (Level band FilterLevel) == 0 ->
check_traces(Attrs, Level, Flows, Acc);
check_traces(Attrs, Level, [{Filter, _, _}|Flows], Acc) when length(Attrs) < length(Filter) ->
check_traces(Attrs, Level, Flows, Acc);
check_traces(Attrs, Level, [Flow|Flows], Acc) ->
check_traces(Attrs, Level, Flows, [check_trace(Attrs, Flow)|Acc]).
-check_trace(Attrs, {Filter, _Level, Dest}) ->
- case check_trace_iter(Attrs, Filter) of
- true ->
- Dest;
- false ->
- []
+check_trace(Attrs, {Filter, _Level, Dest}) when is_list(Filter) ->
+ check_trace(Attrs, {trace_all(Filter), _Level, Dest});
+
+check_trace(Attrs, {Filter, _Level, Dest}) when is_tuple(Filter) ->
+ Made = gre:make(Attrs, [list]),
+ glc:handle(?DEFAULT_TRACER, Made),
+ Match = glc_lib:matches(Filter, Made),
+ case Match of
+ true ->
+ Dest;
+ false ->
+ []
+ end.
+
+-spec is_loggable(lager_msg:lager_msg(), non_neg_integer()|{'mask', non_neg_integer()}, term()) -> boolean().
+is_loggable(Msg, {mask, Mask}, MyName) ->
+ %% using syslog style comparison flags
+ %S = lager_msg:severity_as_int(Msg),
+ %?debugFmt("comparing masks ~.2B and ~.2B -> ~p~n", [S, Mask, S band Mask]),
+ (lager_msg:severity_as_int(Msg) band Mask) /= 0 orelse
+ lists:member(MyName, lager_msg:destinations(Msg));
+is_loggable(Msg ,SeverityThreshold,MyName) ->
+ lager_msg:severity_as_int(Msg) =< SeverityThreshold orelse
+ lists:member(MyName, lager_msg:destinations(Msg)).
+
+i2l(I) when I < 10 -> [$0, $0+I];
+i2l(I) -> integer_to_list(I).
+i3l(I) when I < 100 -> [$0 | i2l(I)];
+i3l(I) -> integer_to_list(I).
+
+%% When log_root option is provided, get the real path to a file
+expand_path(RelPath) ->
+ case application:get_env(lager, log_root) of
+ {ok, LogRoot} when is_list(LogRoot) -> % Join relative path
+ filename:join(LogRoot, RelPath);
+ undefined -> % No log_root given, keep relative path
+ RelPath
+ end.
+
+%% Log rate limit, i.e. high water mark for incoming messages
+
+check_hwm(Shaper = #lager_shaper{hwm = undefined}) ->
+ {true, 0, Shaper};
+check_hwm(Shaper = #lager_shaper{mps = Mps, hwm = Hwm}) when Mps < Hwm ->
+ %% haven't hit high water mark yet, just log it
+ {true, 0, Shaper#lager_shaper{mps=Mps+1}};
+check_hwm(Shaper = #lager_shaper{lasttime = Last, dropped = Drop}) ->
+ %% are we still in the same second?
+ {M, S, _} = Now = os:timestamp(),
+ case Last of
+ {M, S, _} ->
+ %% still in same second, but have exceeded the high water mark
+ NewDrops = discard_messages(Now, 0),
+ {false, 0, Shaper#lager_shaper{dropped=Drop+NewDrops}};
+ _ ->
+ %% different second, reset all counters and allow it
+ {true, Drop, Shaper#lager_shaper{dropped = 0, mps=1, lasttime = Now}}
end.
-check_trace_iter(_, []) ->
- true;
-check_trace_iter(Attrs, [{Key, Match}|T]) ->
- case lists:keyfind(Key, 1, Attrs) of
- {Key, _} when Match == '*' ->
- check_trace_iter(Attrs, T);
- {Key, Match} ->
- check_trace_iter(Attrs, T);
+discard_messages(Second, Count) ->
+ {M, S, _} = os:timestamp(),
+ case Second of
+ {M, S, _} ->
+ receive
+ %% we only discard gen_event notifications, because
+ %% otherwise we might discard gen_event internal
+ %% messages, such as trapped EXITs
+ {notify, _Event} ->
+ discard_messages(Second, Count+1)
+ after 0 ->
+ Count
+ end;
_ ->
- false
+ Count
end.
+%% @private Build an atom for the gen_event process based on a sink name.
+%% For historical reasons, the default gen_event process for lager itself is named
+%% `lager_event'. For all other sinks, it is SinkName++`_lager_event'
+make_internal_sink_name(lager) ->
+ ?DEFAULT_SINK;
+make_internal_sink_name(Sink) ->
+ list_to_atom(atom_to_list(Sink) ++ "_lager_event").
+
-ifdef(TEST).
parse_test() ->
@@ -441,31 +632,167 @@ rotate_file_test() ->
rotate_logfile("rotation.log", 10)
end || N <- lists:seq(0, 20)].
+rotate_file_fail_test() ->
+ %% make sure the directory exists
+ ?assertEqual(ok, filelib:ensure_dir("rotation/rotation.log")),
+ %% fix the permissions on it
+ os:cmd("chown -R u+rwx rotation"),
+ %% delete any old files
+ [ok = file:delete(F) || F <- filelib:wildcard("rotation/*")],
+ %% write a file
+ file:write_file("rotation/rotation.log", "hello"),
+ %% hose up the permissions
+ os:cmd("chown u-w rotation"),
+ ?assertMatch({error, _}, rotate_logfile("rotation.log", 10)),
+ ?assert(filelib:is_regular("rotation/rotation.log")),
+ os:cmd("chown u+w rotation"),
+ ?assertMatch(ok, rotate_logfile("rotation/rotation.log", 10)),
+ ?assert(filelib:is_regular("rotation/rotation.log.0")),
+ ?assertEqual(false, filelib:is_regular("rotation/rotation.log")),
+ ok.
+
check_trace_test() ->
- ?assertEqual([foo], check_traces([{module, ?MODULE}], 0, [{[{module, ?MODULE}],
- 0, foo},
- {[{module, test}], 0, bar}], [])),
- ?assertEqual([], check_traces([{module, ?MODULE}], 0, [{[{module, ?MODULE},
- {foo, bar}], 0, foo},
- {[{module, test}], 0, bar}], [])),
- ?assertEqual([bar], check_traces([{module, ?MODULE}], 0, [{[{module, ?MODULE},
- {foo, bar}], 0, foo},
- {[{module, '*'}], 0, bar}], [])),
- ?assertEqual([bar], check_traces([{module, ?MODULE}], 0, [{[{module, '*'},
- {foo, bar}], 0, foo},
- {[{module, '*'}], 0, bar}], [])),
- ?assertEqual([bar], check_traces([{module, ?MODULE}], 0, [{[{module, '*'},
- {foo, '*'}], 0, foo},
- {[{module, '*'}], 0, bar}], [])),
- ?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], 0, [{[{module, '*'},
- {foo, '*'}], 0, foo},
- {[{module, '*'}], 0, bar}], [])),
- ?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], 6, [{[{module, '*'},
- {foo, '*'}], 0, foo},
- {[{module, '*'}], 0, bar}], [])),
- ?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], 6, [{[{module, '*'},
- {foo, '*'}], 7, foo},
- {[{module, '*'}], 0, bar}], [])),
+ lager:start(),
+ trace_filter(none),
+ %% match by module
+ ?assertEqual([foo], check_traces([{module, ?MODULE}], ?EMERGENCY, [
+ {[{module, ?MODULE}], config_to_mask(emergency), foo},
+ {[{module, test}], config_to_mask(emergency), bar}], [])),
+ %% match by module, but other unsatisfyable attribute
+ ?assertEqual([], check_traces([{module, ?MODULE}], ?EMERGENCY, [
+ {[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
+ {[{module, test}], config_to_mask(emergency), bar}], [])),
+ %% match by wildcard module
+ ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
+ {[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ %% wildcard module, one trace with unsatisfyable attribute
+ ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
+ {[{module, '*'}, {foo, bar}], config_to_mask(emergency), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ %% wildcard but not present custom trace attribute
+ ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
+ {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ %% wildcarding a custom attribute works when it is present
+ ?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], ?EMERGENCY, [
+ {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ %% denied by level
+ ?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
+ {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ %% allowed by level
+ ?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
+ {[{module, '*'}, {foo, '*'}], config_to_mask(debug), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ ?assertEqual([anythingbutnotice, infoandbelow, infoonly], check_traces([{module, ?MODULE}], ?INFO, [
+ {[{module, '*'}], config_to_mask('=debug'), debugonly},
+ {[{module, '*'}], config_to_mask('=info'), infoonly},
+ {[{module, '*'}], config_to_mask('<=info'), infoandbelow},
+ {[{module, '*'}], config_to_mask('!=info'), anythingbutinfo},
+ {[{module, '*'}], config_to_mask('!=notice'), anythingbutnotice}
+ ], [])),
+ application:stop(lager),
+ application:stop(goldrush),
+ ok.
+
+is_loggable_test_() ->
+ [
+ {"Loggable by severity only", ?_assert(is_loggable(lager_msg:new("", alert, [], []),2,me))},
+ {"Not loggable by severity only", ?_assertNot(is_loggable(lager_msg:new("", critical, [], []),1,me))},
+ {"Loggable by severity with destination", ?_assert(is_loggable(lager_msg:new("", alert, [], [you]),2,me))},
+ {"Not loggable by severity with destination", ?_assertNot(is_loggable(lager_msg:new("", critical, [], [you]),1,me))},
+ {"Loggable by destination overriding severity", ?_assert(is_loggable(lager_msg:new("", critical, [], [me]),1,me))}
+ ].
+
+format_time_test_() ->
+ [
+ ?_assertEqual("2012-10-04 11:16:23.002",
+ begin
+ {D, T} = format_time({{2012,10,04},{11,16,23,2}}),
+ lists:flatten([D,$ ,T])
+ end),
+ ?_assertEqual("2012-10-04 11:16:23.999",
+ begin
+ {D, T} = format_time({{2012,10,04},{11,16,23,999}}),
+ lists:flatten([D,$ ,T])
+ end),
+ ?_assertEqual("2012-10-04 11:16:23",
+ begin
+ {D, T} = format_time({{2012,10,04},{11,16,23}}),
+ lists:flatten([D,$ ,T])
+ end),
+ ?_assertEqual("2012-10-04 00:16:23.092 UTC",
+ begin
+ {D, T} = format_time({utc, {{2012,10,04},{0,16,23,92}}}),
+ lists:flatten([D,$ ,T])
+ end),
+ ?_assertEqual("2012-10-04 11:16:23 UTC",
+ begin
+ {D, T} = format_time({utc, {{2012,10,04},{11,16,23}}}),
+ lists:flatten([D,$ ,T])
+ end)
+ ].
+
+config_to_levels_test() ->
+ ?assertEqual([none], config_to_levels('none')),
+ ?assertEqual({mask, 0}, config_to_mask('none')),
+ ?assertEqual([debug], config_to_levels('=debug')),
+ ?assertEqual([debug], config_to_levels('<info')),
+ ?assertEqual(levels() -- [debug], config_to_levels('!=debug')),
+ ?assertEqual(levels() -- [debug], config_to_levels('>debug')),
+ ?assertEqual(levels() -- [debug], config_to_levels('>=info')),
+ ?assertEqual(levels() -- [debug], config_to_levels('=>info')),
+ ?assertEqual([debug, info, notice], config_to_levels('<=notice')),
+ ?assertEqual([debug, info, notice], config_to_levels('=<notice')),
+ ?assertEqual([debug], config_to_levels('<info')),
+ ?assertEqual([debug], config_to_levels('!info')),
+ ?assertError(badarg, config_to_levels(ok)),
+ ?assertError(badarg, config_to_levels('<=>info')),
+ ?assertError(badarg, config_to_levels('=<=info')),
+ ?assertError(badarg, config_to_levels('<==>=<=>info')),
+ %% double negatives DO work, however
+ ?assertEqual([debug], config_to_levels('!!=debug')),
+ ?assertEqual(levels() -- [debug], config_to_levels('!!!=debug')),
ok.
+config_to_mask_test() ->
+ ?assertEqual({mask, 0}, config_to_mask('none')),
+ ?assertEqual({mask, ?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('debug')),
+ ?assertEqual({mask, ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('warning')),
+ ?assertEqual({mask, ?DEBUG bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('!=info')),
+ ok.
+
+mask_to_levels_test() ->
+ ?assertEqual([], mask_to_levels(0)),
+ ?assertEqual([debug], mask_to_levels(2#10000000)),
+ ?assertEqual([debug, info], mask_to_levels(2#11000000)),
+ ?assertEqual([debug, info, emergency], mask_to_levels(2#11000001)),
+ ?assertEqual([debug, notice, error], mask_to_levels(?DEBUG bor ?NOTICE bor ?ERROR)),
+ ok.
+
+expand_path_test() ->
+ OldRootVal = application:get_env(lager, log_root),
+
+ ok = application:unset_env(lager, log_root),
+ ?assertEqual("/foo/bar", expand_path("/foo/bar")),
+ ?assertEqual("foo/bar", expand_path("foo/bar")),
+
+ ok = application:set_env(lager, log_root, "log/dir"),
+ ?assertEqual("/foo/bar", expand_path("/foo/bar")), % Absolute path should not be changed
+ ?assertEqual("log/dir/foo/bar", expand_path("foo/bar")),
+
+ case OldRootVal of
+ undefined -> application:unset_env(lager, log_root);
+ {ok, Root} -> application:set_env(lager, log_root, Root)
+ end,
+ ok.
+
+sink_name_test_() ->
+ [
+ ?_assertEqual(lager_event, make_internal_sink_name(lager)),
+ ?_assertEqual(audit_lager_event, make_internal_sink_name(audit))
+ ].
+
-endif.