You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

194 rivejä
6.5 KiB

6 vuotta sitten
  1. -module(lager_rotator_default).
  2. -include_lib("kernel/include/file.hrl").
  3. -behaviour(lager_rotator_behaviour).
  4. -export([
  5. create_logfile/2, open_logfile/2, ensure_logfile/5, rotate_logfile/2
  6. ]).
  7. -ifdef(TEST).
  8. -include_lib("eunit/include/eunit.hrl").
  9. -endif.
  10. create_logfile(Name, Buffer) ->
  11. open_logfile(Name, Buffer).
  12. open_logfile(Name, Buffer) ->
  13. case filelib:ensure_dir(Name) of
  14. ok ->
  15. Options = [append, raw] ++
  16. case Buffer of
  17. {Size0, Interval} when is_integer(Interval), Interval >= 0, is_integer(Size0), Size0 >= 0 ->
  18. [{delayed_write, Size0, Interval}];
  19. _ -> []
  20. end,
  21. case file:open(Name, Options) of
  22. {ok, FD} ->
  23. case file:read_file_info(Name, [raw]) of
  24. {ok, FInfo0} ->
  25. Inode = FInfo0#file_info.inode,
  26. {ok, Ctime} = maybe_update_ctime(Name, FInfo0),
  27. Size1 = FInfo0#file_info.size,
  28. {ok, {FD, Inode, Ctime, Size1}};
  29. X -> X
  30. end;
  31. Y -> Y
  32. end;
  33. Z -> Z
  34. end.
  35. ensure_logfile(Name, undefined, _Inode, _Ctime, Buffer) ->
  36. open_logfile(Name, Buffer);
  37. ensure_logfile(Name, FD, Inode0, Ctime0, Buffer) ->
  38. case lager_util:has_file_changed(Name, Inode0, Ctime0) of
  39. {true, _FInfo} ->
  40. reopen_logfile(Name, FD, Buffer);
  41. {_, FInfo} ->
  42. {ok, {FD, Inode0, Ctime0, FInfo#file_info.size}}
  43. end.
  44. reopen_logfile(Name, FD0, Buffer) ->
  45. %% delayed write can cause file:close not to do a close
  46. _ = file:close(FD0),
  47. _ = file:close(FD0),
  48. case open_logfile(Name, Buffer) of
  49. {ok, {_FD1, _Inode, _Size, _Ctime}=FileInfo} ->
  50. %% inode changed, file was probably moved and
  51. %% recreated
  52. {ok, FileInfo};
  53. Error ->
  54. Error
  55. end.
  56. %% renames failing are OK
  57. rotate_logfile(File, 0) ->
  58. %% open the file in write-only mode to truncate/create it
  59. case file:open(File, [write]) of
  60. {ok, FD} ->
  61. _ = file:close(FD),
  62. {ok, _Ctime} = maybe_update_ctime(File),
  63. ok;
  64. Error ->
  65. Error
  66. end;
  67. rotate_logfile(File0, 1) ->
  68. File1 = File0 ++ ".0",
  69. _ = file:rename(File0, File1),
  70. rotate_logfile(File0, 0);
  71. rotate_logfile(File0, Count) ->
  72. File1 = File0 ++ "." ++ integer_to_list(Count - 2),
  73. File2 = File0 ++ "." ++ integer_to_list(Count - 1),
  74. _ = file:rename(File1, File2),
  75. rotate_logfile(File0, Count - 1).
  76. maybe_update_ctime(Name) ->
  77. case file:read_file_info(Name, [raw]) of
  78. {ok, FInfo} ->
  79. maybe_update_ctime(Name, FInfo);
  80. _ ->
  81. {ok, calendar:local_time()}
  82. end.
  83. maybe_update_ctime(Name, FInfo) ->
  84. {OsType, _} = os:type(),
  85. do_update_ctime(OsType, Name, FInfo).
  86. do_update_ctime(win32, Name, FInfo0) ->
  87. % Note: we force the creation time to be the current time.
  88. % On win32 this may prevent the ctime from being updated:
  89. % https://stackoverflow.com/q/8804342/1466825
  90. NewCtime = calendar:local_time(),
  91. FInfo1 = FInfo0#file_info{ctime = NewCtime},
  92. ok = file:write_file_info(Name, FInfo1, [raw]),
  93. {ok, NewCtime};
  94. do_update_ctime(_, _Name, FInfo) ->
  95. {ok, FInfo#file_info.ctime}.
  96. -ifdef(TEST).
  97. rotate_file_test() ->
  98. RotCount = 10,
  99. {ok, TestDir} = lager_util:create_test_dir(),
  100. TestLog = filename:join(TestDir, "rotation.log"),
  101. Outer = fun(N) ->
  102. ?assertEqual(ok, lager_util:safe_write_file(TestLog, erlang:integer_to_list(N))),
  103. Inner = fun(M) ->
  104. File = lists:flatten([TestLog, $., erlang:integer_to_list(M)]),
  105. ?assert(filelib:is_regular(File)),
  106. %% check the expected value is in the file
  107. Number = erlang:list_to_binary(integer_to_list(N - M - 1)),
  108. ?assertEqual({ok, Number}, file:read_file(File))
  109. end,
  110. Count = erlang:min(N, RotCount),
  111. % The first time through, Count == 0, so the sequence is empty,
  112. % effectively skipping the inner loop so a rotation can occur that
  113. % creates the file that Inner looks for.
  114. % Don't shoot the messenger, it was worse before this refactoring.
  115. lists:foreach(Inner, lists:seq(0, Count-1)),
  116. rotate_logfile(TestLog, RotCount)
  117. end,
  118. lists:foreach(Outer, lists:seq(0, (RotCount * 2))),
  119. lager_util:delete_test_dir(TestDir).
  120. rotate_file_zero_count_test() ->
  121. %% Test that a rotation count of 0 simply truncates the file
  122. {ok, TestDir} = lager_util:create_test_dir(),
  123. TestLog = filename:join(TestDir, "rotation.log"),
  124. ?assertMatch(ok, rotate_logfile(TestLog, 0)),
  125. ?assertNot(filelib:is_regular(TestLog ++ ".0")),
  126. ?assertEqual(true, filelib:is_regular(TestLog)),
  127. ?assertEqual(1, length(filelib:wildcard(TestLog++"*"))),
  128. %% assert the new file is 0 size:
  129. case file:read_file_info(TestLog, [raw]) of
  130. {ok, FInfo} ->
  131. ?assertEqual(0, FInfo#file_info.size);
  132. _ ->
  133. ?assert(false)
  134. end,
  135. lager_util:delete_test_dir(TestDir).
  136. rotate_file_fail_test() ->
  137. {ok, TestDir} = lager_util:create_test_dir(),
  138. TestLog = filename:join(TestDir, "rotation.log"),
  139. %% set known permissions on it
  140. ok = lager_util:set_dir_permissions("u+rwx", TestDir),
  141. %% write a file
  142. ?assertEqual(ok, lager_util:safe_write_file(TestLog, "hello")),
  143. case os:type() of
  144. {win32, _} -> ok;
  145. _ ->
  146. %% hose up the permissions
  147. ok = lager_util:set_dir_permissions("u-w", TestDir),
  148. ?assertMatch({error, _}, rotate_logfile(TestLog, 10))
  149. end,
  150. %% check we still only have one file, rotation.log
  151. ?assertEqual([TestLog], filelib:wildcard(TestLog++"*")),
  152. ?assert(filelib:is_regular(TestLog)),
  153. %% fix the permissions
  154. ok = lager_util:set_dir_permissions("u+w", TestDir),
  155. ?assertMatch(ok, rotate_logfile(TestLog, 10)),
  156. ?assert(filelib:is_regular(TestLog ++ ".0")),
  157. ?assertEqual(true, filelib:is_regular(TestLog)),
  158. ?assertEqual(2, length(filelib:wildcard(TestLog++"*"))),
  159. %% assert the new file is 0 size:
  160. case file:read_file_info(TestLog, [raw]) of
  161. {ok, FInfo} ->
  162. ?assertEqual(0, FInfo#file_info.size);
  163. _ ->
  164. ?assert(false)
  165. end,
  166. %% check that the .0 file now has the contents "hello"
  167. ?assertEqual({ok, <<"hello">>}, file:read_file(TestLog++".0")),
  168. lager_util:delete_test_dir(TestDir).
  169. -endif.