PageRenderTime 36ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/mochilogfile2.erl

http://github.com/basho/mochiweb
Erlang | 158 lines | 108 code | 13 blank | 37 comment | 0 complexity | 703baab6e79b69b13fbab386ca24ae41 MD5 | raw file
Possible License(s): MIT
  1. %% @author Bob Ippolito <bob@mochimedia.com>
  2. %% @copyright 2010 Mochi Media, Inc.
  3. %%
  4. %% Permission is hereby granted, free of charge, to any person obtaining a
  5. %% copy of this software and associated documentation files (the "Software"),
  6. %% to deal in the Software without restriction, including without limitation
  7. %% the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. %% and/or sell copies of the Software, and to permit persons to whom the
  9. %% Software is furnished to do so, subject to the following conditions:
  10. %%
  11. %% The above copyright notice and this permission notice shall be included in
  12. %% all copies or substantial portions of the Software.
  13. %%
  14. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  17. %% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. %% DEALINGS IN THE SOFTWARE.
  21. %% @doc Write newline delimited log files, ensuring that if a truncated
  22. %% entry is found on log open then it is fixed before writing. Uses
  23. %% delayed writes and raw files for performance.
  24. -module(mochilogfile2).
  25. -author('bob@mochimedia.com').
  26. -export([open/1, write/2, close/1, name/1]).
  27. %% @spec open(Name) -> Handle
  28. %% @doc Open the log file Name, creating or appending as necessary. All data
  29. %% at the end of the file will be truncated until a newline is found, to
  30. %% ensure that all records are complete.
  31. open(Name) ->
  32. {ok, FD} = file:open(Name, [raw, read, write, delayed_write, binary]),
  33. fix_log(FD),
  34. {?MODULE, Name, FD}.
  35. %% @spec name(Handle) -> string()
  36. %% @doc Return the path of the log file.
  37. name({?MODULE, Name, _FD}) ->
  38. Name.
  39. %% @spec write(Handle, IoData) -> ok
  40. %% @doc Write IoData to the log file referenced by Handle.
  41. write({?MODULE, _Name, FD}, IoData) ->
  42. ok = file:write(FD, [IoData, $\n]),
  43. ok.
  44. %% @spec close(Handle) -> ok
  45. %% @doc Close the log file referenced by Handle.
  46. close({?MODULE, _Name, FD}) ->
  47. ok = file:sync(FD),
  48. ok = file:close(FD),
  49. ok.
  50. fix_log(FD) ->
  51. {ok, Location} = file:position(FD, eof),
  52. Seek = find_last_newline(FD, Location),
  53. {ok, Seek} = file:position(FD, Seek),
  54. ok = file:truncate(FD),
  55. ok.
  56. %% Seek backwards to the last valid log entry
  57. find_last_newline(_FD, N) when N =< 1 ->
  58. 0;
  59. find_last_newline(FD, Location) ->
  60. case file:pread(FD, Location - 1, 1) of
  61. {ok, <<$\n>>} ->
  62. Location;
  63. {ok, _} ->
  64. find_last_newline(FD, Location - 1)
  65. end.
  66. %%
  67. %% Tests
  68. %%
  69. -ifdef(TEST).
  70. -include_lib("eunit/include/eunit.hrl").
  71. name_test() ->
  72. D = mochitemp:mkdtemp(),
  73. FileName = filename:join(D, "open_close_test.log"),
  74. H = open(FileName),
  75. ?assertEqual(
  76. FileName,
  77. name(H)),
  78. close(H),
  79. file:delete(FileName),
  80. file:del_dir(D),
  81. ok.
  82. open_close_test() ->
  83. D = mochitemp:mkdtemp(),
  84. FileName = filename:join(D, "open_close_test.log"),
  85. OpenClose = fun () ->
  86. H = open(FileName),
  87. ?assertEqual(
  88. true,
  89. filelib:is_file(FileName)),
  90. ok = close(H),
  91. ?assertEqual(
  92. {ok, <<>>},
  93. file:read_file(FileName)),
  94. ok
  95. end,
  96. OpenClose(),
  97. OpenClose(),
  98. file:delete(FileName),
  99. file:del_dir(D),
  100. ok.
  101. write_test() ->
  102. D = mochitemp:mkdtemp(),
  103. FileName = filename:join(D, "write_test.log"),
  104. F = fun () ->
  105. H = open(FileName),
  106. write(H, "test line"),
  107. close(H),
  108. ok
  109. end,
  110. F(),
  111. ?assertEqual(
  112. {ok, <<"test line\n">>},
  113. file:read_file(FileName)),
  114. F(),
  115. ?assertEqual(
  116. {ok, <<"test line\ntest line\n">>},
  117. file:read_file(FileName)),
  118. file:delete(FileName),
  119. file:del_dir(D),
  120. ok.
  121. fix_log_test() ->
  122. D = mochitemp:mkdtemp(),
  123. FileName = filename:join(D, "write_test.log"),
  124. file:write_file(FileName, <<"first line good\nsecond line bad">>),
  125. F = fun () ->
  126. H = open(FileName),
  127. write(H, "test line"),
  128. close(H),
  129. ok
  130. end,
  131. F(),
  132. ?assertEqual(
  133. {ok, <<"first line good\ntest line\n">>},
  134. file:read_file(FileName)),
  135. file:write_file(FileName, <<"first line bad">>),
  136. F(),
  137. ?assertEqual(
  138. {ok, <<"test line\n">>},
  139. file:read_file(FileName)),
  140. F(),
  141. ?assertEqual(
  142. {ok, <<"test line\ntest line\n">>},
  143. file:read_file(FileName)),
  144. ok.
  145. -endif.