-module(lager_rotator_default).
|
|
|
|
-include_lib("kernel/include/file.hrl").
|
|
|
|
-behaviour(lager_rotator_behaviour).
|
|
|
|
-export([
|
|
create_logfile/2, open_logfile/2, ensure_logfile/4, rotate_logfile/2
|
|
]).
|
|
|
|
-ifdef(TEST).
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
-endif.
|
|
|
|
create_logfile(Name, Buffer) ->
|
|
open_logfile(Name, Buffer).
|
|
|
|
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.
|
|
|
|
%% 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).
|
|
|
|
-ifdef(TEST).
|
|
|
|
rotate_file_test() ->
|
|
RotCount = 10,
|
|
TestDir = lager_util: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))),
|
|
lager_util:delete_test_dir(TestDir).
|
|
|
|
rotate_file_fail_test() ->
|
|
TestDir = lager_util: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)),
|
|
lager_util:delete_test_dir(TestDir).
|
|
|
|
-endif.
|