-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.