|
@ -20,12 +20,10 @@ |
|
|
|
|
|
|
|
|
-module(lager_util). |
|
|
-module(lager_util). |
|
|
|
|
|
|
|
|
-include_lib("kernel/include/file.hrl"). |
|
|
|
|
|
|
|
|
|
|
|
-export([ |
|
|
-export([ |
|
|
levels/0, level_to_num/1, level_to_chr/1, |
|
|
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, |
|
|
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, |
|
|
|
|
|
|
|
|
format_time/0, format_time/1, |
|
|
localtime_ms/0, localtime_ms/1, maybe_utc/1, parse_rotation_date_spec/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, |
|
|
calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3, |
|
|
trace_filter/1, trace_filter/2, expand_path/1, find_file/2, check_hwm/1, check_hwm/2, |
|
|
trace_filter/1, trace_filter/2, expand_path/1, find_file/2, check_hwm/1, check_hwm/2, |
|
@ -140,61 +138,6 @@ level_to_atom(String) -> |
|
|
erlang:error(badarg) |
|
|
erlang:error(badarg) |
|
|
end. |
|
|
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 |
|
|
%% returns localtime with milliseconds included |
|
|
localtime_ms() -> |
|
|
localtime_ms() -> |
|
|
Now = os:timestamp(), |
|
|
Now = os:timestamp(), |
|
@ -214,20 +157,6 @@ maybe_utc({Date, {H, M, S, Ms}}) -> |
|
|
{Date1, {H1, M1, S1, Ms}} |
|
|
{Date1, {H1, M1, S1, Ms}} |
|
|
end. |
|
|
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() -> |
|
|
format_time(maybe_utc(localtime_ms())). |
|
|
format_time(maybe_utc(localtime_ms())). |
|
|
|
|
|
|
|
@ -244,19 +173,34 @@ format_time({{Y, M, D}, {H, Mi, S}}) -> |
|
|
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)], |
|
|
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)], |
|
|
[i2l(H), $:, i2l(Mi), $:, i2l(S)]}. |
|
|
[i2l(H), $:, i2l(Mi), $:, i2l(S)]}. |
|
|
|
|
|
|
|
|
|
|
|
parse_rotation_hour_spec([], Res) -> |
|
|
|
|
|
{ok, Res}; |
|
|
|
|
|
parse_rotation_hour_spec([$H, M1, M2], Res) -> |
|
|
|
|
|
case list_to_integer([M1, M2]) of |
|
|
|
|
|
X when X >= 0, X =< 59 -> |
|
|
|
|
|
{ok, Res ++ [{minute, X}]}; |
|
|
|
|
|
_ -> |
|
|
|
|
|
{error, invalid_date_spec} |
|
|
|
|
|
end; |
|
|
|
|
|
parse_rotation_hour_spec([$H, M], Res) when M >= $0, M =< $9 -> |
|
|
|
|
|
{ok, Res ++ [{minute, M - 48}]}; |
|
|
|
|
|
parse_rotation_hour_spec(_,_) -> |
|
|
|
|
|
{error, invalid_date_spec}. |
|
|
|
|
|
|
|
|
|
|
|
%% Default to 00:00:00 rotation |
|
|
parse_rotation_day_spec([], Res) -> |
|
|
parse_rotation_day_spec([], Res) -> |
|
|
{ok, Res ++ [{hour, 0}]}; |
|
|
|
|
|
parse_rotation_day_spec([$D, D1, D2], Res) -> |
|
|
|
|
|
|
|
|
{ok, Res ++ [{hour ,0}]}; |
|
|
|
|
|
parse_rotation_day_spec([$D, D1, D2|T], Res) -> |
|
|
case list_to_integer([D1, D2]) of |
|
|
case list_to_integer([D1, D2]) of |
|
|
X when X >= 0, X =< 23 -> |
|
|
X when X >= 0, X =< 23 -> |
|
|
{ok, Res ++ [{hour, X}]}; |
|
|
|
|
|
|
|
|
parse_rotation_hour_spec(T, Res ++ [{hour, X}]); |
|
|
_ -> |
|
|
_ -> |
|
|
{error, invalid_date_spec} |
|
|
{error, invalid_date_spec} |
|
|
end; |
|
|
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_day_spec([$D, D|T], Res) when D >= $0, D =< $9 -> |
|
|
|
|
|
parse_rotation_hour_spec(T, Res ++ [{hour, D - 48 }]); |
|
|
|
|
|
parse_rotation_day_spec(X, Res) -> |
|
|
|
|
|
parse_rotation_hour_spec(X, Res). |
|
|
|
|
|
|
|
|
parse_rotation_date_spec([$$, $W, W|T]) when W >= $0, W =< $6 -> |
|
|
parse_rotation_date_spec([$$, $W, W|T]) when W >= $0, W =< $6 -> |
|
|
Week = W - 48, |
|
|
Week = W - 48, |
|
@ -295,6 +239,17 @@ calculate_next_rotation(Spec) -> |
|
|
|
|
|
|
|
|
calculate_next_rotation([], Now) -> |
|
|
calculate_next_rotation([], Now) -> |
|
|
Now; |
|
|
Now; |
|
|
|
|
|
calculate_next_rotation([{minute, X}|T], {{_, _, _}, {Hour, Minute, _}} = Now) when Minute < X -> |
|
|
|
|
|
%% rotation is this hour |
|
|
|
|
|
NewNow = setelement(2, Now, {Hour, X, 0}), |
|
|
|
|
|
calculate_next_rotation(T, NewNow); |
|
|
|
|
|
calculate_next_rotation([{minute, X}|T], Now) -> |
|
|
|
|
|
%% rotation is next hour |
|
|
|
|
|
Seconds = calendar:datetime_to_gregorian_seconds(Now) + 3600, |
|
|
|
|
|
DateTime = calendar:gregorian_seconds_to_datetime(Seconds), |
|
|
|
|
|
{_, {NewHour, _, _}} = DateTime, |
|
|
|
|
|
NewNow = setelement(2, DateTime, {NewHour, X, 0}), |
|
|
|
|
|
calculate_next_rotation(T, NewNow); |
|
|
calculate_next_rotation([{hour, X}|T], {{_, _, _}, {Hour, _, _}} = Now) when Hour < X -> |
|
|
calculate_next_rotation([{hour, X}|T], {{_, _, _}, {Hour, _, _}} = Now) when Hour < X -> |
|
|
%% rotation is today, sometime |
|
|
%% rotation is today, sometime |
|
|
NewNow = setelement(2, Now, {X, 0, 0}), |
|
|
NewNow = setelement(2, Now, {X, 0, 0}), |
|
@ -630,10 +585,13 @@ maybe_flush(Flag, #lager_shaper{} = S) when is_boolean(Flag) -> |
|
|
-ifdef(TEST). |
|
|
-ifdef(TEST). |
|
|
|
|
|
|
|
|
parse_test() -> |
|
|
parse_test() -> |
|
|
|
|
|
?assertEqual({ok, [{minute, 0}]}, parse_rotation_date_spec("$H0")), |
|
|
|
|
|
?assertEqual({ok, [{minute, 59}]}, parse_rotation_date_spec("$H59")), |
|
|
?assertEqual({ok, [{hour, 0}]}, parse_rotation_date_spec("$D0")), |
|
|
?assertEqual({ok, [{hour, 0}]}, parse_rotation_date_spec("$D0")), |
|
|
?assertEqual({ok, [{hour, 23}]}, parse_rotation_date_spec("$D23")), |
|
|
?assertEqual({ok, [{hour, 23}]}, parse_rotation_date_spec("$D23")), |
|
|
?assertEqual({ok, [{day, 0}, {hour, 23}]}, parse_rotation_date_spec("$W0D23")), |
|
|
?assertEqual({ok, [{day, 0}, {hour, 23}]}, parse_rotation_date_spec("$W0D23")), |
|
|
?assertEqual({ok, [{day, 5}, {hour, 16}]}, parse_rotation_date_spec("$W5D16")), |
|
|
?assertEqual({ok, [{day, 5}, {hour, 16}]}, parse_rotation_date_spec("$W5D16")), |
|
|
|
|
|
?assertEqual({ok, [{day, 0}, {hour, 12}, {minute, 30}]}, parse_rotation_date_spec("$W0D12H30")), |
|
|
?assertEqual({ok, [{date, 1}, {hour, 0}]}, parse_rotation_date_spec("$M1D0")), |
|
|
?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, 6}]}, parse_rotation_date_spec("$M5D6")), |
|
|
?assertEqual({ok, [{date, 5}, {hour, 0}]}, parse_rotation_date_spec("$M5")), |
|
|
?assertEqual({ok, [{date, 5}, {hour, 0}]}, parse_rotation_date_spec("$M5")), |
|
@ -645,6 +603,8 @@ parse_test() -> |
|
|
ok. |
|
|
ok. |
|
|
|
|
|
|
|
|
parse_fail_test() -> |
|
|
parse_fail_test() -> |
|
|
|
|
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$H")), |
|
|
|
|
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$H60")), |
|
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D")), |
|
|
?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("$D24")), |
|
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7")), |
|
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7")), |
|
@ -658,6 +618,12 @@ parse_fail_test() -> |
|
|
ok. |
|
|
ok. |
|
|
|
|
|
|
|
|
rotation_calculation_test() -> |
|
|
rotation_calculation_test() -> |
|
|
|
|
|
?assertMatch({{2000, 1, 1}, {13, 0, 0}}, |
|
|
|
|
|
calculate_next_rotation([{minute, 0}], {{2000, 1, 1}, {12, 34, 43}})), |
|
|
|
|
|
?assertMatch({{2000, 1, 1}, {12, 45, 0}}, |
|
|
|
|
|
calculate_next_rotation([{minute, 45}], {{2000, 1, 1}, {12, 34, 43}})), |
|
|
|
|
|
?assertMatch({{2000, 1, 2}, {0, 0, 0}}, |
|
|
|
|
|
calculate_next_rotation([{minute, 0}], {{2000, 1, 1}, {23, 45, 43}})), |
|
|
?assertMatch({{2000, 1, 2}, {0, 0, 0}}, |
|
|
?assertMatch({{2000, 1, 2}, {0, 0, 0}}, |
|
|
calculate_next_rotation([{hour, 0}], {{2000, 1, 1}, {12, 34, 43}})), |
|
|
calculate_next_rotation([{hour, 0}], {{2000, 1, 1}, {12, 34, 43}})), |
|
|
?assertMatch({{2000, 1, 1}, {16, 0, 0}}, |
|
|
?assertMatch({{2000, 1, 1}, {16, 0, 0}}, |
|
@ -707,48 +673,6 @@ rotation_calculation_test() -> |
|
|
calculate_next_rotation([{day, 1}, {hour, 16}], {{1999, 12, 28}, {17, 34, 43}})), |
|
|
calculate_next_rotation([{day, 1}, {hour, 16}], {{1999, 12, 28}, {17, 34, 43}})), |
|
|
ok. |
|
|
ok. |
|
|
|
|
|
|
|
|
rotate_file_test() -> |
|
|
|
|
|
RotCount = 10, |
|
|
|
|
|
TestDir = create_test_dir(), |
|
|
|
|
|
TestLog = filename:join(TestDir, "rotation.log"), |
|
|
|
|
|
Outer = fun(N) -> |
|
|
|
|
|
?assertEqual(ok, file:write_file(TestLog, erlang:integer_to_list(N))), |
|
|
|
|
|
Inner = fun(M) -> |
|
|
|
|
|
File = lists:flatten([TestLog, $., erlang:integer_to_list(M)]), |
|
|
|
|
|
?assert(filelib:is_regular(File)), |
|
|
|
|
|
%% check the expected value is in the file |
|
|
|
|
|
Number = erlang:list_to_binary(integer_to_list(N - M - 1)), |
|
|
|
|
|
?assertEqual({ok, Number}, file:read_file(File)) |
|
|
|
|
|
end, |
|
|
|
|
|
Count = erlang:min(N, RotCount), |
|
|
|
|
|
% The first time through, Count == 0, so the sequence is empty, |
|
|
|
|
|
% effectively skipping the inner loop so a rotation can occur that |
|
|
|
|
|
% creates the file that Inner looks for. |
|
|
|
|
|
% Don't shoot the messenger, it was worse before this refactoring. |
|
|
|
|
|
lists:foreach(Inner, lists:seq(0, Count-1)), |
|
|
|
|
|
rotate_logfile(TestLog, RotCount) |
|
|
|
|
|
end, |
|
|
|
|
|
lists:foreach(Outer, lists:seq(0, (RotCount * 2))), |
|
|
|
|
|
delete_test_dir(TestDir). |
|
|
|
|
|
|
|
|
|
|
|
rotate_file_fail_test() -> |
|
|
|
|
|
TestDir = create_test_dir(), |
|
|
|
|
|
TestLog = filename:join(TestDir, "rotation.log"), |
|
|
|
|
|
%% set known permissions on it |
|
|
|
|
|
os:cmd("chmod -R u+rwx " ++ TestDir), |
|
|
|
|
|
%% write a file |
|
|
|
|
|
file:write_file(TestLog, "hello"), |
|
|
|
|
|
%% hose up the permissions |
|
|
|
|
|
os:cmd("chmod u-w " ++ TestDir), |
|
|
|
|
|
?assertMatch({error, _}, rotate_logfile(TestLog, 10)), |
|
|
|
|
|
?assert(filelib:is_regular(TestLog)), |
|
|
|
|
|
%% fix the permissions |
|
|
|
|
|
os:cmd("chmod u+w " ++ TestDir), |
|
|
|
|
|
?assertMatch(ok, rotate_logfile(TestLog, 10)), |
|
|
|
|
|
?assert(filelib:is_regular(TestLog ++ ".0")), |
|
|
|
|
|
?assertEqual(false, filelib:is_regular(TestLog)), |
|
|
|
|
|
delete_test_dir(TestDir). |
|
|
|
|
|
|
|
|
|
|
|
check_trace_test() -> |
|
|
check_trace_test() -> |
|
|
lager:start(), |
|
|
lager:start(), |
|
|
trace_filter(none), |
|
|
trace_filter(none), |
|
|