rewrite from lager
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 

986 行
37 KiB

-module(rumUtil).
-include("eRum.hrl").
-include_lib("kernel/include/file.hrl").
-export([
levels/0
, levelToNum/1
, levelToChr/1
, numToLevel/1
, validateLogLevel/1
, configToMask/1
, atomCfgToLevels/1
, maskToLevels/1
, nowMs/0
, msToBinStr/0
, msToBinStr/1
, parseRotateSpec/1
, calcNextRotateMs/1
, calcNextRotateMs/2
, calcNextRotateDt/2
, validate_trace/1
, check_traces/4
, isLoggAble/3
, trace_filter/1
, trace_filter/2
, parsePath/1
, find_file/2
, checkHwm/1
, checkHwm/2
, makeInnerSinkName/1
, maybeFlush/2
, isFileChanged/3
, get_env/2
, get_opt/3
]).
-ifdef(TEST).
-export([
create_test_dir/0
, get_test_dir/0
, delete_test_dir/0
, set_dir_permissions/2
, safe_application_load/1
, safe_write_file/2
]).
-include_lib("eunit/include/eunit.hrl").
-endif.
levels() ->
[debug, info, notice, warning, error, critical, alert, emergency, none].
levelToNum(debug) -> ?DEBUG;
levelToNum(info) -> ?INFO;
levelToNum(notice) -> ?NOTICE;
levelToNum(warning) -> ?WARNING;
levelToNum(error) -> ?ERROR;
levelToNum(critical) -> ?CRITICAL;
levelToNum(alert) -> ?ALERT;
levelToNum(emergency) -> ?EMERGENCY;
levelToNum(none) -> ?LOG_NONE.
numToLevel(?DEBUG) -> debug;
numToLevel(?INFO) -> info;
numToLevel(?NOTICE) -> notice;
numToLevel(?WARNING) -> warning;
numToLevel(?ERROR) -> error;
numToLevel(?CRITICAL) -> critical;
numToLevel(?ALERT) -> alert;
numToLevel(?EMERGENCY) -> emergency;
numToLevel(?LOG_NONE) -> none.
levelToChr(debug) -> $D;
levelToChr(info) -> $I;
levelToChr(notice) -> $N;
levelToChr(warning) -> $W;
levelToChr(error) -> $E;
levelToChr(critical) -> $C;
levelToChr(alert) -> $A;
levelToChr(emergency) -> $M;
levelToChr(none) -> $ .
-spec validateLogLevel(atom()|string()) -> false | rumMaskLevel().
validateLogLevel(Level) ->
try rumUtil:configToMask(Level) of
Levels ->
Levels
catch
_:_ ->
false
end.
-spec configToMask(atom()|string()) -> rumMaskLevel().
configToMask(Conf) ->
Levels = atomCfgToLevels(Conf),
levelsToMask(Levels, 0).
-spec levelsToMask([rumAtomLevel()], rumMaskLevel()) -> rumMaskLevel().
levelsToMask([], Acc) ->
Acc;
levelsToMask([Level | Left], Acc) ->
levelsToMask(Left, levelToNum(Level) bor Acc).
-spec maskToLevels(rumMaskLevel()) -> [rumAtomLevel()].
maskToLevels(Mask) ->
maskToLevels(?RumLevels, Mask, []).
maskToLevels([], _Mask, Acc) ->
lists:reverse(Acc);
maskToLevels([Level | Levels], Mask, Acc) ->
case (levelToNum(Level) band Mask) /= 0 of
true ->
maskToLevels(Levels, Mask, [Level | Acc]);
_ ->
maskToLevels(Levels, Mask, Acc)
end.
-spec atomCfgToLevels(atom()) -> [rumAtomLevel()].
atomCfgToLevels(Cfg) ->
binCfgToLevels(atom_to_binary(Cfg, utf8)).
binCfgToLevels(<<"!", Rest/binary>>) ->
?RumLevels -- binCfgToLevels(Rest);
binCfgToLevels(<<"=<", Rest/binary>>) ->
riseInWhile(?RumLevels, levelBinToAtom(Rest), []);
binCfgToLevels(<<"<=", Rest/binary>>) ->
riseInWhile(?RumLevels, levelBinToAtom(Rest), []);
binCfgToLevels(<<">=", Rest/binary>>) ->
dropInWhile(?RumLevels, levelBinToAtom(Rest));
binCfgToLevels(<<"=>", Rest/binary>>) ->
dropInWhile(?RumLevels, levelBinToAtom(Rest));
binCfgToLevels(<<"=", Rest/binary>>) ->
[levelBinToAtom(Rest)];
binCfgToLevels(<<"<", Rest/binary>>) ->
riseOutWhile(?RumLevels, levelBinToAtom(Rest), []);
binCfgToLevels(<<">", Rest/binary>>) ->
dropOutWhile(?RumLevels, levelBinToAtom(Rest));
binCfgToLevels(Rest) ->
[levelBinToAtom(Rest)].
dropInWhile([], _Level) ->
[];
dropInWhile([CurLevel | Left] = Rest, Level) ->
case CurLevel == Level of
true ->
Rest;
_ ->
dropInWhile(Left, Level)
end.
dropOutWhile([], _Level) ->
[];
dropOutWhile([CurLevel | Left], Level) ->
case CurLevel == Level of
true ->
Left;
_ ->
dropOutWhile(Left, Level)
end.
riseInWhile([], _Level, Acc) ->
Acc;
riseInWhile([CurLevel | Left], Level, Acc) ->
case CurLevel == Level of
true ->
[CurLevel | Acc];
_ ->
riseInWhile(Left, Level, [CurLevel | Acc])
end.
riseOutWhile([], _Level, Acc) ->
Acc;
riseOutWhile([CurLevel | Left], Level, Acc) ->
case CurLevel == Level of
true ->
Acc;
_ ->
riseOutWhile(Left, Level, [CurLevel | Acc])
end.
levelBinToAtom(BinStr) ->
AtomLevel = binary_to_atom(BinStr, utf8),
case lists:member(AtomLevel, ?RumLevels) of
true ->
AtomLevel;
_ ->
erlang:error(badarg)
end.
nowMs() ->
erlang:system_time(millisecond).
msToBinStr() ->
msToBinStr(nowMs()).
msToBinStr(MsTick) ->
ThisSec = MsTick div 1000,
ThisMs = MsTick rem 1000,
{{Y, M, D}, {H, Mi, S}} = erlang:universaltime_to_localtime(erlang:posixtime_to_universaltime(ThisSec)),
<<(integer_to_binary(Y))/binary, "-", (i2b(M))/binary, "-", (i2b(D))/binary, " ", (i2b(H))/binary, ":", (i2b(Mi))/binary, ":", (i2b(S))/binary, ".", (i3b(ThisMs))/binary>>.
i2b(Num) ->
if
Num < 10 ->
<<"0", (integer_to_binary(Num))/binary>>;
true ->
integer_to_binary(Num)
end.
i3b(Num) ->
if
Num < 10 ->
<<"00", (integer_to_binary(Num))/binary>>;
Num < 100 ->
<<"0", (integer_to_binary(Num))/binary>>;
true ->
integer_to_binary(Num)
end.
%% last parse hour
parseRotateHourSpec([], DayOrMonthF, Hour, Minute, DayOrMonthV) ->
{DayOrMonthF, Hour, Minute, DayOrMonthV};
parseRotateHourSpec([$H, M1, M2], DayOrMonthF, Hour, _Minute, DayOrMonthV) when M1 >= $0, M1 =< $9, M2 >= $0, M2 =< $9 ->
Min = list_to_integer([M1, M2]),
?IIF(Min >= 0 andalso Min =< 59, {DayOrMonthF, Hour, Min, DayOrMonthV}, {error, invalid_date_spec});
parseRotateHourSpec([$H, M], DayOrMonthF, Hour, _Minute, DayOrMonthV) when M >= $0, M =< $9 ->
{DayOrMonthF, Hour, M - $0, DayOrMonthV};
parseRotateHourSpec(_, _DayOrMonth, _Hour, _Minute, _DayOrMonthV) ->
{error, invalid_date_spec}.
%% second parse day Default to 00:00:00 rotation
parseRotateDaySpec([], DayOrMonthF, Hour, Minute, DayOrMonthV) ->
{DayOrMonthF, Hour, Minute, DayOrMonthV};
parseRotateDaySpec([$D, D1, D2 | T], DayOrMonthF, _Hour, _Minute, DayOrMonthV) when D1 > $0, D1 < $9, D2 > $0, D2 < $9 ->
Day = list_to_integer([D1, D2]),
?IIF(Day >= 0 andalso Day =< 23, parseRotateHourSpec(T, DayOrMonthF, Day, 0, DayOrMonthV), {error, invalid_date_spec});
parseRotateDaySpec([$D, D | T], DayOrMonthF, _Hour, _Minute, DayOrMonthV) when D >= $0, D =< $9 ->
parseRotateHourSpec(T, DayOrMonthF, D - $0, 0, DayOrMonthV);
parseRotateDaySpec(T, DayOrMonth, Hour, Minute, DayOrMonthV) ->
parseRotateHourSpec(T, DayOrMonth, Hour, Minute, DayOrMonthV).
%% first parse date or week
parseRotateDateSpec([$$, $W, W | T], _DayOrMonthF, _Hour, _Minute, _DayOrMonthV) when W >= $1, W =< $7 ->
parseRotateDaySpec(T, day, 0, 0, W - $0);
parseRotateDateSpec([$$, $M, L | T], _DayOrMonthF, _Hour, _Minute, DayOrMonthV) when L == $L; L == $l ->
parseRotateDaySpec(T, last, 0, 0, DayOrMonthV);
parseRotateDateSpec([$$, $M, M1, M2 | T], _DayOrMonthF, _Hour, _Minute, _DayOrMonthV) when M1 >= $0, M1 =< $9, M2 >= $0, M2 =< $9 ->
Date = list_to_integer([M1, M2]),
?IIF(Date >= 1 andalso Date =< 31, parseRotateDaySpec(T, date, 0, 0, Date), {error, invalid_date_spec});
parseRotateDateSpec([$$, $M, M | T], _DayOrMonthF, _Hour, _Minute, _DayOrMonthV) when M >= $1, M =< $9 ->
parseRotateDaySpec(T, date, 0, 0, M - $0);
parseRotateDateSpec([$$ | T], DayOrMonthF, Hour, Minute, DayOrMonthV) ->
parseRotateDaySpec(T, DayOrMonthF, Hour, Minute, DayOrMonthV);
parseRotateDateSpec(_, _DayOrMonthF, _Hour, _Minute, _DayOrMonthV) ->
{error, invalid_date_spec}.
parseRotateSpec(Spec) ->
case parseRotateDateSpec(Spec, undefined, undefined, undefined, undefined) of
{error, _} = ErrRet ->
ErrRet;
{undefined, undefined, undefined, _} ->
{error, invalid_date_spec};
STuple ->
{ok, STuple}
end.
calcNextRotateMs(Spec) ->
{Date, Time} = NowDataTime = erlang:localtime(),
NextTime = calcNextRotate(Spec, Date, Time),
(rumTime:lDateTimeToSec(NextTime) - rumTime:lDateTimeToSec(NowDataTime)) * 1000.
calcNextRotateMs(Spec, NowDataTime) ->
{Date, Time} = NowDataTime,
NextTime = calcNextRotate(Spec, Date, Time),
(rumTime:lDateTimeToSec(NextTime) - rumTime:lDateTimeToSec(NowDataTime)) * 1000.
calcNextRotateDt(Spec, NowDataTime) ->
{Date, Time} = NowDataTime,
calcNextRotate(Spec, Date, Time).
calcNextRotate({undefined, SHour, SMinute, _SMonthV}, CurDate, CurTime) ->
case SHour of
undefined ->
{CurHour, CurMinute, _} = CurTime,
case CurMinute < SMinute of
true ->
%% rotation is this hour
{CurDate, {CurHour, SMinute, 0}};
_ ->
%% rotation is next hour
NexSec = rumTime:lDateTimeToSec({CurDate, {CurHour, SMinute, 0}}) + 3600,
rumTime:secToLDateTime(NexSec)
end;
_ ->
case CurTime < {SHour, SMinute, 0} of
true ->
%% rotation is this day
{CurDate, {SHour, SMinute, 0}};
_ ->
%% rotation is next day
NexSec = rumTime:lDateTimeToSec({CurDate, {SHour, SMinute, 0}}) + 86400,
rumTime:secToLDateTime(NexSec)
end
end;
calcNextRotate({day, SHour, SMinute, SDay}, CurDate, CurTime) ->
CurWeekDay = rumTime:weekDay(CurDate),
if
CurWeekDay < SDay ->
%% rotation is this week
DiffDays = SDay - CurWeekDay,
NexSec = rumTime:lDateTimeToSec({CurDate, {SHour, SMinute, 0}}) + (86400 * DiffDays),
rumTime:secToLDateTime(NexSec);
CurWeekDay > SDay ->
%% rotation is next week
DiffDays = ((7 - CurWeekDay) + SDay),
NexSec = rumTime:lDateTimeToSec({CurDate, {SHour, SMinute, 0}}) + (86400 * DiffDays),
rumTime:secToLDateTime(NexSec);
true ->
case CurTime < {SHour, SMinute, 0} of
true ->
%% rotation is this week
{CurDate, {SHour, SMinute, 0}};
_ ->
%% rotation is next week
NexSec = rumTime:lDateTimeToSec({CurDate, {SHour, SMinute, 0}}) + (86400 * 7),
rumTime:secToLDateTime(NexSec)
end
end;
calcNextRotate({last, SHour, SMinute, _SMonthV}, CurDate, CurTime) ->
{CurYear, CurMonth, CurDay} = CurDate,
CurMonthDay = rumTime:monthDay(CurYear, CurMonth),
case CurMonthDay == CurDay of
true ->
case CurTime < {SHour, SMinute, 0} of
true ->
%% rotation is this last month day
{CurDate, {SHour, SMinute, 0}};
_ ->
%% rotation is next last month day
NexSec = rumTime:lDateTimeToSec({CurDate, {23, 59, 59}}) + 1, %% 下个月1号凌晨
{NewNDate, _NewNTime} = rumTime:secToLDateTime(NexSec),
{NewNYear, NewNMonth, _} = NewNDate,
NewMonthDay = rumTime:monthDay(NewNYear, NewNMonth),
{{NewNYear, NewNMonth, NewMonthDay}, {SHour, SMinute, 0}}
end;
_ ->
%% rotation is this last month day
{{CurYear, CurMonth, CurMonthDay}, {SHour, SMinute, 0}}
end;
calcNextRotate({date, SHour, SMinute, SDate}, CurDate, CurTime) ->
{CurYear, CurMonth, CurDay} = CurDate,
if
CurDay < SDate ->
%% rotation is this month day
{{CurYear, CurMonth, SDate}, {SHour, SMinute, 0}};
CurDay > SDate ->
%% rotation is next month day
CurMonthDay = rumTime:monthDay(CurYear, CurMonth),
NexSec = rumTime:lDateTimeToSec({{CurYear, CurMonth, CurMonthDay}, {23, 59, 59}}) + 1,
{NewNDate, _NewNTime} = rumTime:secToLDateTime(NexSec),
{NewNYear, NewNMonth, _} = NewNDate,
{{NewNYear, NewNMonth, SDate}, {SHour, SMinute, 0}};
true ->
case CurTime < {SHour, SMinute, 0} of
true ->
%% rotation is this month day
{CurDate, {SHour, SMinute, 0}};
_ ->
%% rotation is next month day
CurMonthDay = rumTime:monthDay(CurYear, CurMonth),
NexSec = rumTime:lDateTimeToSec({{CurYear, CurMonth, CurMonthDay}, {23, 59, 59}}) + 1,
{NewNDate, _NewNTime} = rumTime:secToLDateTime(NexSec),
{NewNYear, NewNMonth, _} = NewNDate,
{{NewNYear, NewNMonth, SDate}, {SHour, SMinute, 0}}
end
end.
-spec trace_filter(Query :: 'none' | [tuple()]) -> {ok, any()}.
trace_filter(Query) ->
trace_filter(?RumDefTracer, 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 configToMask(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) when is_list(Filter) ->
lists:all(fun validate_trace_filter/1, Filter);
validate_trace_filter({Key, '*'}) when is_atom(Key) -> true;
validate_trace_filter({any, L}) when is_list(L) -> lists:all(fun validate_trace_filter/1, L);
validate_trace_filter({all, L}) when is_list(L) -> lists:all(fun validate_trace_filter/1, L);
validate_trace_filter({null, Bool}) when is_boolean(Bool) -> true;
validate_trace_filter({Key, _Value}) when is_atom(Key) -> true;
validate_trace_filter({Key, '=', _Value}) when is_atom(Key) -> true;
validate_trace_filter({Key, '!=', _Value}) when is_atom(Key) -> true;
validate_trace_filter({Key, '<', _Value}) when is_atom(Key) -> true;
validate_trace_filter({Key, '=<', _Value}) when is_atom(Key) -> true;
validate_trace_filter({Key, '>', _Value}) when is_atom(Key) -> true;
validate_trace_filter({Key, '>=', _Value}) when is_atom(Key) -> true;
validate_trace_filter(_) -> false.
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([{any, L} | T], Acc) ->
trace_acc(T, [glc:any(L) | Acc]);
trace_acc([{all, L} | T], Acc) ->
trace_acc(T, [glc:all(L) | Acc]);
trace_acc([{null, Bool} | T], Acc) ->
trace_acc(T, [glc:null(Bool) | 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:neq(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:gte(Key, Val) | Acc]);
trace_acc([{Key, '=<', Val} | T], Acc) ->
trace_acc(T, [glc:lte(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 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(?RumDefTracer, Made),
Match = glc_lib:matches(Filter, Made),
case Match of
true ->
Dest;
false ->
[]
end.
-spec isLoggAble(rumMsg:rumMsg(), rumMaskLevel(), term()) -> boolean().
isLoggAble(Msg, Mask, MyName) ->
(rumMsg:severity_as_int(Msg) band Mask) /= 0 orelse lists:member(MyName, rumMsg:destinations(Msg)).
parsePath(RelPath) ->
NewRelPath =
case rumUtil:get_env(logRoot, undefined) of
undefined ->
RelPath;
LogRoot ->
case filename:dirname(RelPath) of
"." ->
filename:join(LogRoot, RelPath);
false ->
RelPath
end
end,
filename:absname(NewRelPath).
%% Find a file among the already installed handlers.
%%
%% The file is already expanded (i.e. lager_util:expand_path already added the
%% "logRoot"), but the file paths inside Handlers are not.
find_file(_File1, _Handlers = []) ->
false;
find_file(File1, [{{lager_file_backend, File2}, _Handler, _Sink} = HandlerInfo | Handlers]) ->
File1Abs = File1,
File2Abs = lager_util:expand_path(File2),
case File1Abs =:= File2Abs of
true ->
% The file inside HandlerInfo is the same as the file we are looking
% for, so we are done.
HandlerInfo;
false ->
find_file(File1, Handlers)
end;
find_file(File1, [_HandlerInfo | Handlers]) ->
find_file(File1, Handlers).
%% conditionally check the HWM if the event would not have been filtered
checkHwm(Shaper = #rumShaper{filter = Filter}, Event) ->
case Filter(Event) of
true ->
{true, 0, Shaper};
_ ->
checkHwm(Shaper)
end.
%% 日志速率限制S i.e. 即传入消息的高水位标记
checkHwm(#rumShaper{id = Id, hwm = Hwm, mps = Mps, lastTime = LastTime, dropped = Drop, flushQueue = FlushQueue, flushThreshold = FlushThreshold, timer = Timer, filter = Filter} = Shaper) ->
if
Hwm == undefined ->
{true, 0, Shaper};
Mps < Hwm ->
NowTime = rumTime:now(),
case LastTime == NowTime of
true ->
{true, 0, Shaper#rumShaper{mps = Mps + 1}};
_ ->
%different second - reset mps
{true, 0, Shaper#rumShaper{dropped = 0, mps = 1, lastTime = NowTime}}
end;
true ->
%% are we still in the same second?
NowTimeMs = rumTime:nowMs(),
NowTime = NowTimeMs div 1000,
PastMs = NowTimeMs rem 1000,
case LastTime == NowTime of
true ->
%% still in same second, but have exceeded the high water mark
NewDrops = ?IIF(isNeedFlush(FlushQueue, FlushThreshold), dropMsg(NowTime, Filter, 0), 0),
NewTimer = ?IIF(erlang:read_timer(Timer) =/= false, Timer, erlang:send_after(1000 - PastMs, self(), {mShaperExpired, Id})),
{false, 0, Shaper#rumShaper{dropped = Drop + NewDrops + 1, timer = NewTimer}};
_ ->
_ = erlang:cancel_timer(Shaper#rumShaper.timer),
%% different second, reset all counters and allow it
{drop, Drop, Shaper#rumShaper{dropped = 0, mps = 1, lastTime = NowTime}}
end
end.
isNeedFlush(true, FlushThreshold) ->
case FlushThreshold of
0 ->
true;
_ ->
PInfo = process_info(self(), message_queue_len),
element(2, PInfo) > FlushThreshold
end;
isNeedFlush(_FlushQueue, _FlushThreshold) ->
false.
dropMsg(LastTime, Filter, Count) ->
CurTime = rumUtil:now(),
case CurTime == LastTime of
true ->
receive
%% we only discard gen_event notifications, because
%% otherwise we might discard gen_event internal
%% messages, such as trapped EXITs
{'$gen_info', Event} ->
NewCount = ?IIF(Filter(Event), Count, Count + 1),
dropMsg(LastTime, Filter, NewCount)
after 0 ->
Count
end;
_ ->
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'
makeInnerSinkName(Sink) ->
binary_to_atom(<<(atom_to_binary(Sink, utf8))/binary, "Event">>).
maybeFlush(undefined, #rumShaper{} = S) ->
S;
maybeFlush(Flag, #rumShaper{} = S) ->
S#rumShaper{flushQueue = Flag}.
-spec isFileChanged(FileName :: file:name_all(), Inode :: pos_integer(), Ctime :: file:date_time()) -> {boolean(), file:file_info() | undefined}.
isFileChanged(FileName, Inode, Ctime) ->
case file:read_file_info(FileName, [raw]) of
{ok, FileInfo} ->
case os:type() of
{win32, _} ->
% Note: on win32, Inode is always zero So check the file's ctime to see if it needs to be re-opened
{Ctime =/= FileInfo#file_info.ctime, FileInfo};
_ ->
{Inode =/= FileInfo#file_info.inode, FileInfo}
end;
_ ->
{true, undefined}
end.
-spec get_env(Par :: atom(), Def :: term()) -> Val :: term().
get_env(Key, Def) ->
case application:get_env(?RumAppName, Key) of
{ok, Val} ->
Val;
_ ->
Def
end.
get_opt(Key, Opts, Def) ->
case lists:keyfind(Key, 1, Opts) of
false ->
Def;
V ->
element(2, V)
end.
-ifdef(TEST).
parse_test() ->
?assertEqual({ok, {undefined, undefined, 0, undefined}}, rumUtil:parseRotateSpec("$H0")),
?assertEqual({ok, {undefined, undefined, 59, undefined}}, rumUtil:parseRotateSpec("$H59")),
?assertEqual({ok, {undefined, 0, 0, undefined}}, rumUtil:parseRotateSpec("$D0")),
?assertEqual({ok, {undefined, 23, 0, undefined}}, rumUtil:parseRotateSpec("$D23")),
?assertEqual({ok, {day, 23, 0, 7}}, rumUtil:parseRotateSpec("$W7D23")),
?assertEqual({ok, {day, 16, 0, 5}}, rumUtil:parseRotateSpec("$W5D16")),
?assertEqual({ok, {day, 12, 30, 7}}, rumUtil:parseRotateSpec("$W7D12H30")),
?assertEqual({ok, {date, 0, 0, 1}}, rumUtil:parseRotateSpec("$M1D0")),
?assertEqual({ok, {date, 6, 0, 5}}, rumUtil:parseRotateSpec("$M5D6")),
?assertEqual({ok, {date, 0, 0, 5}}, rumUtil:parseRotateSpec("$M5")),
?assertEqual({ok, {date, 0, 0, 31}}, rumUtil:parseRotateSpec("$M31")),
?assertEqual({ok, {date, 1, 0, 31}}, rumUtil:parseRotateSpec("$M31D1")),
?assertEqual({ok, {last, 0, 0, undefined}}, rumUtil:parseRotateSpec("$ML")),
?assertEqual({ok, {last, 0, 0, undefined}}, rumUtil:parseRotateSpec("$Ml")),
?assertEqual({ok, {day, 0, 0, 5}}, rumUtil:parseRotateSpec("$W5")),
?assertEqual({ok, {date, 12, 36, 5}}, rumUtil:parseRotateSpec("$M5D12H36")),
ok.
parse_fail_test() ->
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$H")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$H60")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$D")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$D24")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$W0")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$W0D1")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$M32")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$M32D1")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$D15M5")),
?assertEqual({error, invalid_date_spec}, rumUtil:parseRotateSpec("$M5W5")),
ok.
rotation_calculation_test() ->
?assertMatch({{2000, 1, 1}, {13, 0, 0}},
rumUtil:calcNextRotateDt({undefined, undefined, 0, 0}, {{2000, 1, 1}, {12, 34, 43}})),
?assertMatch({{2000, 1, 1}, {12, 45, 0}},
rumUtil:calcNextRotateDt({undefined, undefined, 45, 0}, {{2000, 1, 1}, {12, 34, 43}})),
?assertMatch({{2000, 1, 2}, {0, 0, 0}},
rumUtil:calcNextRotateDt({undefined, undefined, 0, 0}, {{2000, 1, 1}, {23, 45, 43}})),
?assertMatch({{2000, 1, 2}, {0, 0, 0}},
rumUtil:calcNextRotateDt({undefined, 0, 0, 0}, {{2000, 1, 1}, {12, 34, 43}})),
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
rumUtil:calcNextRotateDt({undefined, 16, 0, 0}, {{2000, 1, 1}, {12, 34, 43}})),
?assertMatch({{2000, 1, 2}, {12, 0, 0}},
rumUtil:calcNextRotateDt({undefined, 12, 0, 0}, {{2000, 1, 1}, {12, 34, 43}})),
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
rumUtil:calcNextRotateDt({date, 12, 0, 1}, {{2000, 1, 1}, {12, 34, 43}})),
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
rumUtil:calcNextRotateDt({date, 12, 0, 1}, {{2000, 1, 15}, {12, 34, 43}})),
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
rumUtil:calcNextRotateDt({date, 12, 0, 1}, {{2000, 1, 2}, {12, 34, 43}})),
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
rumUtil:calcNextRotateDt({date, 12, 0, 1}, {{2000, 1, 31}, {12, 34, 43}})),
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
rumUtil:calcNextRotateDt({date, 16, 0, 1}, {{2000, 1, 1}, {12, 34, 43}})),
?assertMatch({{2000, 1, 15}, {16, 0, 0}},
rumUtil:calcNextRotateDt({date, 16, 0, 15}, {{2000, 1, 1}, {12, 34, 43}})),
?assertMatch({{2000, 1, 31}, {16, 0, 0}},
rumUtil:calcNextRotateDt({last, 16, 0, 0}, {{2000, 1, 1}, {12, 34, 43}})),
?assertMatch({{2000, 1, 31}, {16, 0, 0}},
rumUtil:calcNextRotateDt({last, 16, 0, 0}, {{2000, 1, 31}, {12, 34, 43}})),
?assertMatch({{2000, 2, 29}, {16, 0, 0}},
rumUtil:calcNextRotateDt({last, 16, 0, 0}, {{2000, 1, 31}, {17, 34, 43}})),
?assertMatch({{2001, 2, 28}, {16, 0, 0}},
rumUtil:calcNextRotateDt({last, 16, 0, 0}, {{2001, 1, 31}, {17, 34, 43}})),
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
rumUtil:calcNextRotateDt({day, 16, 0, 6}, {{2000, 1, 1}, {12, 34, 43}})),
?assertMatch({{2000, 1, 8}, {16, 0, 0}},
rumUtil:calcNextRotateDt({day, 16, 0, 6}, {{2000, 1, 1}, {17, 34, 43}})),
?assertMatch({{2000, 1, 7}, {16, 0, 0}},
rumUtil:calcNextRotateDt({day, 16, 0, 5}, {{2000, 1, 1}, {17, 34, 43}})),
?assertMatch({{2000, 1, 3}, {16, 0, 0}},
rumUtil:calcNextRotateDt({day, 16, 0, 1}, {{2000, 1, 1}, {17, 34, 43}})),
?assertMatch({{2000, 1, 2}, {16, 0, 0}},
rumUtil:calcNextRotateDt({day, 16, 0, 7}, {{2000, 1, 1}, {17, 34, 43}})),
?assertMatch({{2000, 1, 9}, {16, 0, 0}},
rumUtil:calcNextRotateDt({day, 16, 0, 7}, {{2000, 1, 2}, {17, 34, 43}})),
?assertMatch({{2000, 2, 3}, {16, 0, 0}},
rumUtil:calcNextRotateDt({day, 16, 0, 4}, {{2000, 1, 29}, {17, 34, 43}})),
?assertMatch({{2000, 1, 7}, {16, 0, 0}},
rumUtil:calcNextRotateDt({day, 16, 0, 5}, {{2000, 1, 3}, {17, 34, 43}})),
?assertMatch({{2000, 1, 3}, {16, 0, 0}},
rumUtil:calcNextRotateDt({day, 16, 0, 1}, {{1999, 12, 28}, {17, 34, 43}})),
ok.
check_trace_test() ->
eRum:start(),
trace_filter(none),
%% match by module
?assertEqual([foo], check_traces([{module, ?MODULE}], ?EMERGENCY, [
{[{module, ?MODULE}], configToMask(emergency), foo},
{[{module, test}], configToMask(emergency), bar}], [])),
%% match by module, but other unsatisfyable attribute
?assertEqual([], check_traces([{module, ?MODULE}], ?EMERGENCY, [
{[{module, ?MODULE}, {foo, bar}], configToMask(emergency), foo},
{[{module, test}], configToMask(emergency), bar}], [])),
%% match by wildcard module
?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
{[{module, ?MODULE}, {foo, bar}], configToMask(emergency), foo},
{[{module, '*'}], configToMask(emergency), bar}], [])),
%% wildcard module, one trace with unsatisfyable attribute
?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
{[{module, '*'}, {foo, bar}], configToMask(emergency), foo},
{[{module, '*'}], configToMask(emergency), bar}], [])),
%% wildcard but not present custom trace attribute
?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
{[{module, '*'}, {foo, '*'}], configToMask(emergency), foo},
{[{module, '*'}], configToMask(emergency), bar}], [])),
%% wildcarding a custom attribute works when it is present
?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], ?EMERGENCY, [
{[{module, '*'}, {foo, '*'}], configToMask(emergency), foo},
{[{module, '*'}], configToMask(emergency), bar}], [])),
%% denied by level
?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
{[{module, '*'}, {foo, '*'}], configToMask(emergency), foo},
{[{module, '*'}], configToMask(emergency), bar}], [])),
%% allowed by level
?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
{[{module, '*'}, {foo, '*'}], configToMask(debug), foo},
{[{module, '*'}], configToMask(emergency), bar}], [])),
?assertEqual([anythingbutnotice, infoandbelow, infoonly], check_traces([{module, ?MODULE}], ?INFO, [
{[{module, '*'}], configToMask('=debug'), debugonly},
{[{module, '*'}], configToMask('=info'), infoonly},
{[{module, '*'}], configToMask('<=info'), infoandbelow},
{[{module, '*'}], configToMask('!=info'), anythingbutinfo},
{[{module, '*'}], configToMask('!=notice'), anythingbutnotice}
], [])),
application:stop(lager),
application:stop(goldrush),
ok.
is_loggable_test_() ->
[
{"Loggable by severity only", ?_assert(isLoggAble(rumMsg:new("", alert, [], []), 2, me))},
{"Not loggable by severity only", ?_assertNot(isLoggAble(rumMsg:new("", critical, [], []), 1, me))},
{"Loggable by severity with destination", ?_assert(isLoggAble(rumMsg:new("", alert, [], [you]), 2, me))},
{"Not loggable by severity with destination", ?_assertNot(isLoggAble(rumMsg:new("", critical, [], [you]), 1, me))},
{"Loggable by destination overriding severity", ?_assert(isLoggAble(rumMsg:new("", critical, [], [me]), 1, me))}
].
format_time_test_() ->
[
?_assertEqual("2012-10-04 11:16:23.002",
begin
{D, T} = msToBinStr({{2012, 10, 04}, {11, 16, 23, 2}}),
lists:flatten([D, $ , T])
end),
?_assertEqual("2012-10-04 11:16:23.999",
begin
{D, T} = msToBinStr({{2012, 10, 04}, {11, 16, 23, 999}}),
lists:flatten([D, $ , T])
end),
?_assertEqual("2012-10-04 11:16:23",
begin
{D, T} = msToBinStr({{2012, 10, 04}, {11, 16, 23}}),
lists:flatten([D, $ , T])
end),
?_assertEqual("2012-10-04 00:16:23.092 UTC",
begin
{D, T} = msToBinStr({utc, {{2012, 10, 04}, {0, 16, 23, 92}}}),
lists:flatten([D, $ , T])
end),
?_assertEqual("2012-10-04 11:16:23 UTC",
begin
{D, T} = msToBinStr({utc, {{2012, 10, 04}, {11, 16, 23}}}),
lists:flatten([D, $ , T])
end)
].
config_to_levels_test() ->
?assertEqual([none], atomCfgToLevels('none')),
?assertEqual(0, configToMask('none')),
?assertEqual([debug], atomCfgToLevels('=debug')),
?assertEqual([debug], atomCfgToLevels('<info')),
?assertEqual(levels() -- [debug], atomCfgToLevels('!=debug')),
?assertEqual(levels() -- [debug], atomCfgToLevels('>debug')),
?assertEqual(levels() -- [debug], atomCfgToLevels('>=info')),
?assertEqual(levels() -- [debug], atomCfgToLevels('=>info')),
?assertEqual([debug, info, notice], atomCfgToLevels('<=notice')),
?assertEqual([debug, info, notice], atomCfgToLevels('=<notice')),
?assertEqual([debug], atomCfgToLevels('<info')),
?assertEqual([debug], atomCfgToLevels('!info')),
?assertError(badarg, atomCfgToLevels(ok)),
?assertError(badarg, atomCfgToLevels('<=>info')),
?assertError(badarg, atomCfgToLevels('=<=info')),
?assertError(badarg, atomCfgToLevels('<==>=<=>info')),
%% double negatives DO work, however
?assertEqual([debug], atomCfgToLevels('!!=debug')),
?assertEqual(levels() -- [debug], atomCfgToLevels('!!!=debug')),
ok.
config_to_mask_test() ->
?assertEqual(0, configToMask('none')),
?assertEqual(?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, configToMask('debug')),
?assertEqual(?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, configToMask('warning')),
?assertEqual(?DEBUG bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, configToMask('!=info')),
ok.
mask_to_levels_test() ->
?assertEqual([], maskToLevels(0)),
?assertEqual([debug], maskToLevels(2#10000000)),
?assertEqual([debug, info], maskToLevels(2#11000000)),
?assertEqual([debug, info, emergency], maskToLevels(2#11000001)),
?assertEqual([debug, notice, error], maskToLevels(?DEBUG bor ?NOTICE bor ?ERROR)),
ok.
expand_path_test() ->
OldRootVal = application:get_env(lager, logRoot),
ok = application:unset_env(lager, logRoot),
?assertEqual(filename:absname("/foo/bar"), parsePath("/foo/bar")),
?assertEqual(filename:absname("foo/bar"), parsePath("foo/bar")),
ok = application:set_env(lager, logRoot, "log/dir"),
?assertEqual(filename:absname("/foo/bar"), parsePath("/foo/bar")), % Absolute path should not be changed
?assertEqual(filename:absname("log/dir/foo/bar"), parsePath("foo/bar")),
?assertEqual(filename:absname("log/dir/foo/bar"), parsePath("log/dir/foo/bar")), %% gh #304
case OldRootVal of
undefined -> application:unset_env(lager, logRoot);
{ok, Root} -> application:set_env(lager, logRoot, Root)
end,
ok.
sink_name_test_() ->
[
?_assertEqual(rumEvent, makeInnerSinkName(lager)),
?_assertEqual(audit_lager_event, makeInnerSinkName(audit))
].
create_test_dir() ->
{ok, Tmp} = get_temp_dir(),
Dir = filename:join([Tmp, "lager_test",
erlang:integer_to_list(erlang:phash2(os:timestamp()))]),
?assertEqual(ok, filelib:ensure_dir(Dir)),
TestDir = case file:make_dir(Dir) of
ok ->
Dir;
Err ->
?assertEqual({error, eexist}, Err),
create_test_dir()
end,
ok = application:set_env(lager, test_dir, TestDir),
{ok, TestDir}.
get_test_dir() ->
case application:get_env(lager, test_dir) of
undefined ->
create_test_dir();
{ok, _} = Res ->
Res
end.
get_temp_dir() ->
Tmp = case os:getenv("TEMP") of
false ->
case os:getenv("TMP") of
false -> "/tmp";
Dir1 -> Dir1
end;
Dir0 -> Dir0
end,
?assertEqual(true, filelib:is_dir(Tmp)),
{ok, Tmp}.
delete_test_dir() ->
{ok, TestDir} = get_test_dir(),
ok = delete_test_dir(TestDir).
delete_test_dir(TestDir) ->
ok = application:unset_env(lager, test_dir),
ok =
case os:type() of
{win32, _} ->
application:stop(lager),
do_delete_test_dir(TestDir);
{_, _} ->
do_delete_test_dir(TestDir)
end.
do_delete_test_dir(Dir) ->
ListRet = file:list_dir_all(Dir),
?assertMatch({ok, _}, ListRet),
{_, Entries} = ListRet,
lists:foreach(
fun(Entry) ->
FsElem = filename:join(Dir, Entry),
case filelib:is_dir(FsElem) of
true ->
delete_test_dir(FsElem);
_ ->
case file:delete(FsElem) of
ok -> ok;
Error ->
io:format(standard_error, "[ERROR]: error deleting file ~p~n", [FsElem]),
?assertEqual(ok, Error)
end
end
end, Entries),
?assertEqual(ok, file:del_dir(Dir)).
do_delete_file(_FsElem, 0) ->
?assert(false);
do_delete_file(FsElem, Attempts) ->
case file:delete(FsElem) of
ok -> ok;
_Error ->
do_delete_file(FsElem, Attempts - 1)
end.
set_dir_permissions(Perms, Dir) ->
do_set_dir_permissions(os:type(), Perms, Dir).
do_set_dir_permissions({win32, _}, _Perms, _Dir) ->
ok;
do_set_dir_permissions({unix, _}, Perms, Dir) ->
os:cmd("chmod -R " ++ Perms ++ " " ++ Dir),
ok.
safe_application_load(App) ->
case application:load(App) of
ok ->
ok;
{error, {already_loaded, App}} ->
ok;
Error ->
?assertEqual(ok, Error)
end.
safe_write_file(File, Content) ->
% Note: ensures that the new creation time is at least one second
% in the future
?assertEqual(ok, file:write_file(File, Content)),
Ctime0 = calendar:local_time(),
Ctime0Sec = calendar:datetime_to_gregorian_seconds(Ctime0),
Ctime1Sec = Ctime0Sec + 1,
Ctime1 = calendar:gregorian_seconds_to_datetime(Ctime1Sec),
{ok, FInfo0} = file:read_file_info(File, [raw]),
FInfo1 = FInfo0#file_info{ctime = Ctime1},
?assertEqual(ok, file:write_file_info(File, FInfo1, [raw])).
-endif.