Use Lager when compiling mod_rest, and include minimum in this repository (fixes #13)
This commit is contained in:
parent
429f3e2ced
commit
d98d55e1f7
|
@ -0,0 +1,102 @@
|
||||||
|
%% 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.
|
||||||
|
|
||||||
|
|
||||||
|
-define(DEFAULT_TRUNCATION, 4096).
|
||||||
|
-define(DEFAULT_TRACER, lager_default_tracer).
|
||||||
|
|
||||||
|
-define(LEVELS,
|
||||||
|
[debug, info, notice, warning, error, critical, alert, emergency, none]).
|
||||||
|
|
||||||
|
-define(DEBUG, 128).
|
||||||
|
-define(INFO, 64).
|
||||||
|
-define(NOTICE, 32).
|
||||||
|
-define(WARNING, 16).
|
||||||
|
-define(ERROR, 8).
|
||||||
|
-define(CRITICAL, 4).
|
||||||
|
-define(ALERT, 2).
|
||||||
|
-define(EMERGENCY, 1).
|
||||||
|
-define(LOG_NONE, 0).
|
||||||
|
|
||||||
|
-define(LEVEL2NUM(Level),
|
||||||
|
case Level of
|
||||||
|
debug -> ?DEBUG;
|
||||||
|
info -> ?INFO;
|
||||||
|
notice -> ?NOTICE;
|
||||||
|
warning -> ?WARNING;
|
||||||
|
error -> ?ERROR;
|
||||||
|
critical -> ?CRITICAL;
|
||||||
|
alert -> ?ALERT;
|
||||||
|
emergency -> ?EMERGENCY
|
||||||
|
end).
|
||||||
|
|
||||||
|
-define(NUM2LEVEL(Num),
|
||||||
|
case Num of
|
||||||
|
?DEBUG -> debug;
|
||||||
|
?INFO -> info;
|
||||||
|
?NOTICE -> notice;
|
||||||
|
?WARNING -> warning;
|
||||||
|
?ERROR -> error;
|
||||||
|
?CRITICAL -> critical;
|
||||||
|
?ALERT -> alert;
|
||||||
|
?EMERGENCY -> emergency
|
||||||
|
end).
|
||||||
|
|
||||||
|
-define(SHOULD_LOG(Level),
|
||||||
|
(lager_util:level_to_num(Level) band element(1, lager_config:get(loglevel, {?LOG_NONE, []}))) /= 0).
|
||||||
|
|
||||||
|
-define(NOTIFY(Level, Pid, Format, Args),
|
||||||
|
gen_event:notify(lager_event, {log, lager_msg:new(io_lib:format(Format, Args),
|
||||||
|
Level,
|
||||||
|
[{pid,Pid},{line,?LINE},{file,?FILE},{module,?MODULE}],
|
||||||
|
[])}
|
||||||
|
)).
|
||||||
|
|
||||||
|
%% FOR INTERNAL USE ONLY
|
||||||
|
%% internal non-blocking logging call
|
||||||
|
%% there's some special handing for when we try to log (usually errors) while
|
||||||
|
%% lager is still starting.
|
||||||
|
-ifdef(TEST).
|
||||||
|
-define(INT_LOG(Level, Format, Args),
|
||||||
|
case ?SHOULD_LOG(Level) of
|
||||||
|
true ->
|
||||||
|
?NOTIFY(Level, self(), Format, Args);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end).
|
||||||
|
-else.
|
||||||
|
-define(INT_LOG(Level, Format, Args),
|
||||||
|
Self = self(),
|
||||||
|
%% do this in a spawn so we don't cause a deadlock calling gen_event:which_handlers
|
||||||
|
%% from a gen_event handler
|
||||||
|
spawn(fun() ->
|
||||||
|
case catch(gen_event:which_handlers(lager_event)) of
|
||||||
|
X when X == []; X == {'EXIT', noproc}; X == [lager_backend_throttle] ->
|
||||||
|
%% there's no handlers yet or lager isn't running, try again
|
||||||
|
%% in half a second.
|
||||||
|
timer:sleep(500),
|
||||||
|
?NOTIFY(Level, Self, Format, Args);
|
||||||
|
_ ->
|
||||||
|
case ?SHOULD_LOG(Level) of
|
||||||
|
true ->
|
||||||
|
?NOTIFY(Level, Self, Format, Args);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)).
|
||||||
|
-endif.
|
||||||
|
|
|
@ -0,0 +1,279 @@
|
||||||
|
%% 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 The parse transform used for lager messages.
|
||||||
|
%% This parse transform rewrites functions calls to lager:Severity/1,2 into
|
||||||
|
%% a more complicated function that captures module, function, line, pid and
|
||||||
|
%% time as well. The entire function call is then wrapped in a case that
|
||||||
|
%% checks the lager_config 'loglevel' value, so the code isn't executed if
|
||||||
|
%% nothing wishes to consume the message.
|
||||||
|
|
||||||
|
-module(lager_transform).
|
||||||
|
|
||||||
|
-include("lager.hrl").
|
||||||
|
|
||||||
|
-export([parse_transform/2]).
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
parse_transform(AST, Options) ->
|
||||||
|
TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION),
|
||||||
|
Enable = proplists:get_value(lager_print_records_flag, Options, true),
|
||||||
|
put(print_records_flag, Enable),
|
||||||
|
put(truncation_size, TruncSize),
|
||||||
|
erlang:put(records, []),
|
||||||
|
%% .app file should either be in the outdir, or the same dir as the source file
|
||||||
|
guess_application(proplists:get_value(outdir, Options), hd(AST)),
|
||||||
|
walk_ast([], AST).
|
||||||
|
|
||||||
|
walk_ast(Acc, []) ->
|
||||||
|
case get(print_records_flag) of
|
||||||
|
true ->
|
||||||
|
insert_record_attribute(Acc);
|
||||||
|
false ->
|
||||||
|
lists:reverse(Acc)
|
||||||
|
end;
|
||||||
|
walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) ->
|
||||||
|
%% A wild parameterized module appears!
|
||||||
|
put(module, Module),
|
||||||
|
walk_ast([H|Acc], T);
|
||||||
|
walk_ast(Acc, [{attribute, _, module, Module}=H|T]) ->
|
||||||
|
put(module, Module),
|
||||||
|
walk_ast([H|Acc], T);
|
||||||
|
walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) ->
|
||||||
|
put(function, Name),
|
||||||
|
walk_ast([{function, Line, Name, Arity,
|
||||||
|
walk_clauses([], Clauses)}|Acc], T);
|
||||||
|
walk_ast(Acc, [{attribute, _, record, {Name, Fields}}=H|T]) ->
|
||||||
|
FieldNames = lists:map(fun({record_field, _, {atom, _, FieldName}}) ->
|
||||||
|
FieldName;
|
||||||
|
({record_field, _, {atom, _, FieldName}, _Default}) ->
|
||||||
|
FieldName
|
||||||
|
end, Fields),
|
||||||
|
stash_record({Name, FieldNames}),
|
||||||
|
walk_ast([H|Acc], T);
|
||||||
|
walk_ast(Acc, [H|T]) ->
|
||||||
|
walk_ast([H|Acc], T).
|
||||||
|
|
||||||
|
walk_clauses(Acc, []) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) ->
|
||||||
|
walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T).
|
||||||
|
|
||||||
|
walk_body(Acc, []) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
walk_body(Acc, [H|T]) ->
|
||||||
|
walk_body([transform_statement(H)|Acc], T).
|
||||||
|
|
||||||
|
transform_statement({call, Line, {remote, _Line1, {atom, _Line2, lager},
|
||||||
|
{atom, _Line3, Severity}}, Arguments0} = Stmt) ->
|
||||||
|
case lists:member(Severity, ?LEVELS) of
|
||||||
|
true ->
|
||||||
|
SeverityAsInt=lager_util:level_to_num(Severity),
|
||||||
|
DefaultAttrs0 = {cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, module}, {atom, Line, get(module)}]},
|
||||||
|
{cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, function}, {atom, Line, get(function)}]},
|
||||||
|
{cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, line},
|
||||||
|
{integer, Line, Line}]},
|
||||||
|
{cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, pid},
|
||||||
|
{call, Line, {atom, Line, pid_to_list}, [
|
||||||
|
{call, Line, {atom, Line ,self}, []}]}]},
|
||||||
|
{cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, node},
|
||||||
|
{call, Line, {atom, Line, node}, []}]},
|
||||||
|
%% get the metadata with lager:md(), this will always return a list so we can use it as the tail here
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}},
|
||||||
|
%{nil, Line}}}}}}},
|
||||||
|
DefaultAttrs = case erlang:get(application) of
|
||||||
|
undefined ->
|
||||||
|
DefaultAttrs0;
|
||||||
|
App ->
|
||||||
|
%% stick the application in the attribute list
|
||||||
|
concat_lists({cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, application},
|
||||||
|
{atom, Line, App}]},
|
||||||
|
{nil, Line}}, DefaultAttrs0)
|
||||||
|
end,
|
||||||
|
{Traces, Message, Arguments} = case Arguments0 of
|
||||||
|
[Format] ->
|
||||||
|
{DefaultAttrs, Format, {atom, Line, none}};
|
||||||
|
[Arg1, Arg2] ->
|
||||||
|
%% some ambiguity here, figure out if these arguments are
|
||||||
|
%% [Format, Args] or [Attr, Format].
|
||||||
|
%% The trace attributes will be a list of tuples, so check
|
||||||
|
%% for that.
|
||||||
|
case {element(1, Arg1), Arg1} of
|
||||||
|
{_, {cons, _, {tuple, _, _}, _}} ->
|
||||||
|
{concat_lists(Arg1, DefaultAttrs),
|
||||||
|
Arg2, {atom, Line, none}};
|
||||||
|
{Type, _} when Type == var;
|
||||||
|
Type == lc;
|
||||||
|
Type == call;
|
||||||
|
Type == record_field ->
|
||||||
|
%% crap, its not a literal. look at the second
|
||||||
|
%% argument to see if it is a string
|
||||||
|
case Arg2 of
|
||||||
|
{string, _, _} ->
|
||||||
|
{concat_lists(Arg1, DefaultAttrs),
|
||||||
|
Arg2, {atom, Line, none}};
|
||||||
|
_ ->
|
||||||
|
%% not a string, going to have to guess
|
||||||
|
%% it's the argument list
|
||||||
|
{DefaultAttrs, Arg1, Arg2}
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
{DefaultAttrs, Arg1, Arg2}
|
||||||
|
end;
|
||||||
|
[Attrs, Format, Args] ->
|
||||||
|
{concat_lists(Attrs, DefaultAttrs), Format, Args}
|
||||||
|
end,
|
||||||
|
%% Generate some unique variable names so we don't accidentaly export from case clauses.
|
||||||
|
%% Note that these are not actual atoms, but the AST treats variable names as atoms.
|
||||||
|
LevelVar = make_varname("__Level", Line),
|
||||||
|
TracesVar = make_varname("__Traces", Line),
|
||||||
|
PidVar = make_varname("__Pid", Line),
|
||||||
|
%% Wrap the call to lager_dispatch log in a case that will avoid doing any work if this message is not elegible for logging
|
||||||
|
%% case {whereis(lager_event(lager_event), lager_config:get(loglevel, {?LOG_NONE, []})} of
|
||||||
|
{'case', Line,
|
||||||
|
{tuple, Line,
|
||||||
|
[{call, Line, {atom, Line, whereis}, [{atom, Line, lager_event}]},
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lager_config}, {atom, Line, get}}, [{atom, Line, loglevel}, {tuple, Line, [{integer, Line, 0},{nil, Line}]}]}]},
|
||||||
|
[
|
||||||
|
%% {undefined, _} -> {error, lager_not_running}
|
||||||
|
{clause, Line,
|
||||||
|
[{tuple, Line, [{atom, Line, undefined}, {var, Line, '_'}]}],
|
||||||
|
[],
|
||||||
|
%% trick the linter into avoiding a 'term constructed by not used' error:
|
||||||
|
%% (fun() -> {error, lager_not_running} end)();
|
||||||
|
[{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}]},
|
||||||
|
%% If we care about the loglevel, or there's any traces installed, we have do more checking
|
||||||
|
%% {Level, Traces} when (Level band SeverityAsInt) /= 0 orelse Traces /= [] ->
|
||||||
|
{clause, Line,
|
||||||
|
[{tuple, Line, [{var, Line, PidVar}, {tuple, Line, [{var, Line, LevelVar}, {var, Line, TracesVar}]}]}],
|
||||||
|
[[{op, Line, 'orelse',
|
||||||
|
{op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}},
|
||||||
|
{op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]],
|
||||||
|
[
|
||||||
|
%% do the call to lager:dispatch_log
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, do_log}},
|
||||||
|
[
|
||||||
|
{atom,Line,Severity},
|
||||||
|
Traces,
|
||||||
|
Message,
|
||||||
|
Arguments,
|
||||||
|
{integer, Line, get(truncation_size)},
|
||||||
|
{integer, Line, SeverityAsInt},
|
||||||
|
{var, Line, LevelVar},
|
||||||
|
{var, Line, TracesVar},
|
||||||
|
{var, Line, PidVar}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]},
|
||||||
|
%% otherwise, do nothing
|
||||||
|
%% _ -> ok
|
||||||
|
{clause, Line, [{var, Line, '_'}],[],[{atom, Line, ok}]}
|
||||||
|
]};
|
||||||
|
false ->
|
||||||
|
Stmt
|
||||||
|
end;
|
||||||
|
transform_statement({call, Line, {remote, Line1, {atom, Line2, boston_lager},
|
||||||
|
{atom, Line3, Severity}}, Arguments}) ->
|
||||||
|
NewArgs = case Arguments of
|
||||||
|
[{string, L, Msg}] -> [{string, L, re:replace(Msg, "r", "h", [{return, list}, global])}];
|
||||||
|
[{string, L, Format}, Args] -> [{string, L, re:replace(Format, "r", "h", [{return, list}, global])}, Args];
|
||||||
|
Other -> Other
|
||||||
|
end,
|
||||||
|
transform_statement({call, Line, {remote, Line1, {atom, Line2, lager},
|
||||||
|
{atom, Line3, Severity}}, NewArgs});
|
||||||
|
transform_statement(Stmt) when is_tuple(Stmt) ->
|
||||||
|
list_to_tuple(transform_statement(tuple_to_list(Stmt)));
|
||||||
|
transform_statement(Stmt) when is_list(Stmt) ->
|
||||||
|
[transform_statement(S) || S <- Stmt];
|
||||||
|
transform_statement(Stmt) ->
|
||||||
|
Stmt.
|
||||||
|
|
||||||
|
make_varname(Prefix, Line) ->
|
||||||
|
list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)).
|
||||||
|
|
||||||
|
%% concat 2 list ASTs by replacing the terminating [] in A with the contents of B
|
||||||
|
concat_lists({var, Line, _Name}=Var, B) ->
|
||||||
|
%% concatenating a var with a cons
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||||
|
[{cons, Line, Var, B}]};
|
||||||
|
concat_lists({lc, Line, _Body, _Generator} = LC, B) ->
|
||||||
|
%% concatenating a LC with a cons
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||||
|
[{cons, Line, LC, B}]};
|
||||||
|
concat_lists({call, Line, _Function, _Args} = Call, B) ->
|
||||||
|
%% concatenating a call with a cons
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||||
|
[{cons, Line, Call, B}]};
|
||||||
|
concat_lists({record_field, Line, _Var, _Record, _Field} = Rec, B) ->
|
||||||
|
%% concatenating a record_field with a cons
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||||
|
[{cons, Line, Rec, B}]};
|
||||||
|
concat_lists({nil, _Line}, B) ->
|
||||||
|
B;
|
||||||
|
concat_lists({cons, Line, Element, Tail}, B) ->
|
||||||
|
{cons, Line, Element, concat_lists(Tail, B)}.
|
||||||
|
|
||||||
|
stash_record(Record) ->
|
||||||
|
Records = case erlang:get(records) of
|
||||||
|
undefined ->
|
||||||
|
[];
|
||||||
|
R ->
|
||||||
|
R
|
||||||
|
end,
|
||||||
|
erlang:put(records, [Record|Records]).
|
||||||
|
|
||||||
|
insert_record_attribute(AST) ->
|
||||||
|
lists:foldl(fun({attribute, Line, module, _}=E, Acc) ->
|
||||||
|
[E, {attribute, Line, lager_records, erlang:get(records)}|Acc];
|
||||||
|
(E, Acc) ->
|
||||||
|
[E|Acc]
|
||||||
|
end, [], AST).
|
||||||
|
|
||||||
|
guess_application(Dirname, Attr) when Dirname /= undefined ->
|
||||||
|
case find_app_file(Dirname) of
|
||||||
|
no_idea ->
|
||||||
|
%% try it based on source file directory (app.src most likely)
|
||||||
|
guess_application(undefined, Attr);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
guess_application(undefined, {attribute, _, file, {Filename, _}}) ->
|
||||||
|
Dir = filename:dirname(Filename),
|
||||||
|
find_app_file(Dir);
|
||||||
|
guess_application(_, _) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
find_app_file(Dir) ->
|
||||||
|
case filelib:wildcard(Dir++"/*.{app,app.src}") of
|
||||||
|
[] ->
|
||||||
|
no_idea;
|
||||||
|
[File] ->
|
||||||
|
case file:consult(File) of
|
||||||
|
{ok, [{application, Appname, _Attributes}|_]} ->
|
||||||
|
erlang:put(application, Appname);
|
||||||
|
_ ->
|
||||||
|
no_idea
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
%% multiple files, uh oh
|
||||||
|
no_idea
|
||||||
|
end.
|
|
@ -0,0 +1,710 @@
|
||||||
|
%% 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.
|
||||||
|
|
||||||
|
-module(lager_util).
|
||||||
|
|
||||||
|
-include_lib("kernel/include/file.hrl").
|
||||||
|
|
||||||
|
-export([levels/0, level_to_num/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]).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
-include("lager.hrl").
|
||||||
|
|
||||||
|
levels() ->
|
||||||
|
[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.
|
||||||
|
|
||||||
|
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] ++
|
||||||
|
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} ->
|
||||||
|
case file:read_file_info(Name) of
|
||||||
|
{ok, FInfo} ->
|
||||||
|
Inode = FInfo#file_info.inode,
|
||||||
|
{ok, {FD, Inode, FInfo#file_info.size}};
|
||||||
|
X -> X
|
||||||
|
end;
|
||||||
|
Y -> Y
|
||||||
|
end;
|
||||||
|
Z -> Z
|
||||||
|
end.
|
||||||
|
|
||||||
|
ensure_logfile(Name, FD, Inode, Buffer) ->
|
||||||
|
case file:read_file_info(Name) of
|
||||||
|
{ok, FInfo} ->
|
||||||
|
Inode2 = FInfo#file_info.inode,
|
||||||
|
case Inode == Inode2 of
|
||||||
|
true ->
|
||||||
|
{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),
|
||||||
|
case open_logfile(Name, Buffer) of
|
||||||
|
{ok, {FD2, Inode3, Size}} ->
|
||||||
|
%% inode changed, file was probably moved and
|
||||||
|
%% recreated
|
||||||
|
{ok, {FD2, Inode3, Size}};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
%% delayed write can cause file:close not to do a close
|
||||||
|
_ = file:close(FD),
|
||||||
|
_ = file:close(FD),
|
||||||
|
case open_logfile(Name, Buffer) of
|
||||||
|
{ok, {FD2, Inode3, Size}} ->
|
||||||
|
%% file was removed
|
||||||
|
{ok, {FD2, Inode3, Size}};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% returns localtime with milliseconds included
|
||||||
|
localtime_ms() ->
|
||||||
|
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}}} ->
|
||||||
|
{utc, {Date1, {H1, M1, S1, Ms}}};
|
||||||
|
{Date1, {H1, M1, S1}} ->
|
||||||
|
{Date1, {H1, M1, S1, Ms}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% renames failing are OK
|
||||||
|
rotate_logfile(File, 0) ->
|
||||||
|
file:delete(File);
|
||||||
|
rotate_logfile(File, 1) ->
|
||||||
|
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)),
|
||||||
|
rotate_logfile(File, Count - 1).
|
||||||
|
|
||||||
|
format_time() ->
|
||||||
|
format_time(maybe_utc(localtime_ms())).
|
||||||
|
|
||||||
|
format_time({utc, {{Y, M, D}, {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}}) ->
|
||||||
|
{[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}}}) ->
|
||||||
|
{[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}}) ->
|
||||||
|
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
|
||||||
|
[i2l(H), $:, i2l(Mi), $:, i2l(S)]}.
|
||||||
|
|
||||||
|
parse_rotation_day_spec([], Res) ->
|
||||||
|
{ok, Res ++ [{hour, 0}]};
|
||||||
|
parse_rotation_day_spec([$D, D1, D2], Res) ->
|
||||||
|
case list_to_integer([D1, D2]) of
|
||||||
|
X when X >= 0, X =< 23 ->
|
||||||
|
{ok, Res ++ [{hour, X}]};
|
||||||
|
_ ->
|
||||||
|
{error, invalid_date_spec}
|
||||||
|
end;
|
||||||
|
parse_rotation_day_spec([$D, D], Res) when D >= $0, D =< $9 ->
|
||||||
|
{ok, Res ++ [{hour, D - 48}]};
|
||||||
|
parse_rotation_day_spec(_, _) ->
|
||||||
|
{error, invalid_date_spec}.
|
||||||
|
|
||||||
|
parse_rotation_date_spec([$$, $W, W|T]) when W >= $0, W =< $6 ->
|
||||||
|
Week = W - 48,
|
||||||
|
parse_rotation_day_spec(T, [{day, Week}]);
|
||||||
|
parse_rotation_date_spec([$$, $M, L|T]) when L == $L; L == $l ->
|
||||||
|
%% last day in month.
|
||||||
|
parse_rotation_day_spec(T, [{date, last}]);
|
||||||
|
parse_rotation_date_spec([$$, $M, M1, M2|[$D|_]=T]) ->
|
||||||
|
case list_to_integer([M1, M2]) of
|
||||||
|
X when X >= 1, X =< 31 ->
|
||||||
|
parse_rotation_day_spec(T, [{date, X}]);
|
||||||
|
_ ->
|
||||||
|
{error, invalid_date_spec}
|
||||||
|
end;
|
||||||
|
parse_rotation_date_spec([$$, $M, M|[$D|_]=T]) ->
|
||||||
|
parse_rotation_day_spec(T, [{date, M - 48}]);
|
||||||
|
parse_rotation_date_spec([$$, $M, M1, M2]) ->
|
||||||
|
case list_to_integer([M1, M2]) of
|
||||||
|
X when X >= 1, X =< 31 ->
|
||||||
|
{ok, [{date, X}, {hour, 0}]};
|
||||||
|
_ ->
|
||||||
|
{error, invalid_date_spec}
|
||||||
|
end;
|
||||||
|
parse_rotation_date_spec([$$, $M, M]) ->
|
||||||
|
{ok, [{date, M - 48}, {hour, 0}]};
|
||||||
|
parse_rotation_date_spec([$$|X]) when X /= [] ->
|
||||||
|
parse_rotation_day_spec(X, []);
|
||||||
|
parse_rotation_date_spec(_) ->
|
||||||
|
{error, invalid_date_spec}.
|
||||||
|
|
||||||
|
calculate_next_rotation(Spec) ->
|
||||||
|
Now = calendar:local_time(),
|
||||||
|
Later = calculate_next_rotation(Spec, Now),
|
||||||
|
calendar:datetime_to_gregorian_seconds(Later) -
|
||||||
|
calendar:datetime_to_gregorian_seconds(Now).
|
||||||
|
|
||||||
|
calculate_next_rotation([], Now) ->
|
||||||
|
Now;
|
||||||
|
calculate_next_rotation([{hour, X}|T], {{_, _, _}, {Hour, _, _}} = Now) when Hour < X ->
|
||||||
|
%% rotation is today, sometime
|
||||||
|
NewNow = setelement(2, Now, {X, 0, 0}),
|
||||||
|
calculate_next_rotation(T, NewNow);
|
||||||
|
calculate_next_rotation([{hour, X}|T], {{_, _, _}, _} = Now) ->
|
||||||
|
%% rotation is not today
|
||||||
|
Seconds = calendar:datetime_to_gregorian_seconds(Now) + 86400,
|
||||||
|
DateTime = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||||
|
NewNow = setelement(2, DateTime, {X, 0, 0}),
|
||||||
|
calculate_next_rotation(T, NewNow);
|
||||||
|
calculate_next_rotation([{day, Day}|T], {Date, _Time} = Now) ->
|
||||||
|
DoW = calendar:day_of_the_week(Date),
|
||||||
|
AdjustedDay = case Day of
|
||||||
|
0 -> 7;
|
||||||
|
X -> X
|
||||||
|
end,
|
||||||
|
case AdjustedDay of
|
||||||
|
DoW -> %% rotation is today
|
||||||
|
OldDate = element(1, Now),
|
||||||
|
case calculate_next_rotation(T, Now) of
|
||||||
|
{OldDate, _} = NewNow -> NewNow;
|
||||||
|
{NewDate, _} ->
|
||||||
|
%% rotation *isn't* today! rerun the calculation
|
||||||
|
NewNow = {NewDate, {0, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, Day}|T], NewNow)
|
||||||
|
end;
|
||||||
|
Y when Y > DoW -> %% rotation is later this week
|
||||||
|
PlusDays = Y - DoW,
|
||||||
|
Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
|
||||||
|
{NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||||
|
NewNow = {NewDate, {0, 0, 0}},
|
||||||
|
calculate_next_rotation(T, NewNow);
|
||||||
|
Y when Y < DoW -> %% rotation is next week
|
||||||
|
PlusDays = ((7 - DoW) + Y),
|
||||||
|
Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
|
||||||
|
{NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||||
|
NewNow = {NewDate, {0, 0, 0}},
|
||||||
|
calculate_next_rotation(T, NewNow)
|
||||||
|
end;
|
||||||
|
calculate_next_rotation([{date, last}|T], {{Year, Month, Day}, _} = Now) ->
|
||||||
|
Last = calendar:last_day_of_the_month(Year, Month),
|
||||||
|
case Last == Day of
|
||||||
|
true -> %% doing rotation today
|
||||||
|
OldDate = element(1, Now),
|
||||||
|
case calculate_next_rotation(T, Now) of
|
||||||
|
{OldDate, _} = NewNow -> NewNow;
|
||||||
|
{NewDate, _} ->
|
||||||
|
%% rotation *isn't* today! rerun the calculation
|
||||||
|
NewNow = {NewDate, {0, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, last}|T], NewNow)
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
NewNow = setelement(1, Now, {Year, Month, Last}),
|
||||||
|
calculate_next_rotation(T, NewNow)
|
||||||
|
end;
|
||||||
|
calculate_next_rotation([{date, Date}|T], {{_, _, Date}, _} = Now) ->
|
||||||
|
%% rotation is today
|
||||||
|
OldDate = element(1, Now),
|
||||||
|
case calculate_next_rotation(T, Now) of
|
||||||
|
{OldDate, _} = NewNow -> NewNow;
|
||||||
|
{NewDate, _} ->
|
||||||
|
%% rotation *isn't* today! rerun the calculation
|
||||||
|
NewNow = setelement(1, Now, NewDate),
|
||||||
|
calculate_next_rotation([{date, Date}|T], NewNow)
|
||||||
|
end;
|
||||||
|
calculate_next_rotation([{date, Date}|T], {{Year, Month, Day}, _} = Now) ->
|
||||||
|
PlusDays = case Date of
|
||||||
|
X when X < Day -> %% rotation is next month
|
||||||
|
Last = calendar:last_day_of_the_month(Year, Month),
|
||||||
|
(Last - Day);
|
||||||
|
X when X > Day -> %% rotation is later this month
|
||||||
|
X - Day
|
||||||
|
end,
|
||||||
|
Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
|
||||||
|
NewNow = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||||
|
calculate_next_rotation(T, NewNow).
|
||||||
|
|
||||||
|
-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_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 ->
|
||||||
|
{ok, {Filter, L, Destination}}
|
||||||
|
catch
|
||||||
|
_:_ ->
|
||||||
|
{error, invalid_level}
|
||||||
|
end;
|
||||||
|
validate_trace(_) ->
|
||||||
|
{error, invalid_trace}.
|
||||||
|
|
||||||
|
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, [{_, {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}) 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).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
|
||||||
|
parse_test() ->
|
||||||
|
?assertEqual({ok, [{hour, 0}]}, parse_rotation_date_spec("$D0")),
|
||||||
|
?assertEqual({ok, [{hour, 23}]}, parse_rotation_date_spec("$D23")),
|
||||||
|
?assertEqual({ok, [{day, 0}, {hour, 23}]}, parse_rotation_date_spec("$W0D23")),
|
||||||
|
?assertEqual({ok, [{day, 5}, {hour, 16}]}, parse_rotation_date_spec("$W5D16")),
|
||||||
|
?assertEqual({ok, [{date, 1}, {hour, 0}]}, parse_rotation_date_spec("$M1D0")),
|
||||||
|
?assertEqual({ok, [{date, 5}, {hour, 6}]}, parse_rotation_date_spec("$M5D6")),
|
||||||
|
?assertEqual({ok, [{date, 5}, {hour, 0}]}, parse_rotation_date_spec("$M5")),
|
||||||
|
?assertEqual({ok, [{date, 31}, {hour, 0}]}, parse_rotation_date_spec("$M31")),
|
||||||
|
?assertEqual({ok, [{date, 31}, {hour, 1}]}, parse_rotation_date_spec("$M31D1")),
|
||||||
|
?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$ML")),
|
||||||
|
?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$Ml")),
|
||||||
|
?assertEqual({ok, [{day, 5}, {hour, 0}]}, parse_rotation_date_spec("$W5")),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
parse_fail_test() ->
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D24")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7D1")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32D1")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D15M5")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M5W5")),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
rotation_calculation_test() ->
|
||||||
|
?assertMatch({{2000, 1, 2}, {0, 0, 0}},
|
||||||
|
calculate_next_rotation([{hour, 0}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 2}, {12, 0, 0}},
|
||||||
|
calculate_next_rotation([{hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 15}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 2}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 31}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 1}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 15}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 15}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 31}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 31}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 29}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2001, 2, 28}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, last}, {hour, 16}], {{2001, 1, 31}, {17, 34, 43}})),
|
||||||
|
|
||||||
|
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 8}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 7}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 3}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 1}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 2}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 9}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 2}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 3}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 4}, {hour, 16}], {{2000, 1, 29}, {17, 34, 43}})),
|
||||||
|
|
||||||
|
?assertMatch({{2000, 1, 7}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 3}, {17, 34, 43}})),
|
||||||
|
|
||||||
|
?assertMatch({{2000, 1, 3}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 1}, {hour, 16}], {{1999, 12, 28}, {17, 34, 43}})),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
rotate_file_test() ->
|
||||||
|
file:delete("rotation.log"),
|
||||||
|
[file:delete(["rotation.log.", integer_to_list(N)]) || N <- lists:seq(0, 9)],
|
||||||
|
[begin
|
||||||
|
file:write_file("rotation.log", integer_to_list(N)),
|
||||||
|
Count = case N > 10 of
|
||||||
|
true -> 10;
|
||||||
|
_ -> N
|
||||||
|
end,
|
||||||
|
[begin
|
||||||
|
FileName = ["rotation.log.", integer_to_list(M)],
|
||||||
|
?assert(filelib:is_regular(FileName)),
|
||||||
|
%% check the expected value is in the file
|
||||||
|
Number = list_to_binary(integer_to_list(N - M - 1)),
|
||||||
|
?assertEqual({ok, Number}, file:read_file(FileName))
|
||||||
|
end
|
||||||
|
|| M <- lists:seq(0, Count-1)],
|
||||||
|
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() ->
|
||||||
|
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.
|
||||||
|
|
||||||
|
-endif.
|
|
@ -1 +1,4 @@
|
||||||
{'src/mod_rest', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
{'../ejabberd-dev/src/lager_transform', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'../ejabberd-dev/src/lager_util', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/mod_rest', [{outdir, "ebin"},{i,"../ejabberd-dev/include"},{pa, "../ejabberd-dev/ebin"},{d,'LAGER'}]}.
|
||||||
|
|
Loading…
Reference in New Issue