PageRenderTime 277ms CodeModel.GetById 125ms app.highlight 14ms RepoModel.GetById 72ms 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
  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
 22%% @doc Write newline delimited log files, ensuring that if a truncated
 23%%      entry is found on log open then it is fixed before writing. Uses
 24%%      delayed writes and raw files for performance.
 25-module(mochilogfile2).
 26-author('bob@mochimedia.com').
 27
 28-export([open/1, write/2, close/1, name/1]).
 29
 30%% @spec open(Name) -> Handle
 31%% @doc Open the log file Name, creating or appending as necessary. All data
 32%%      at the end of the file will be truncated until a newline is found, to
 33%%      ensure that all records are complete.
 34open(Name) ->
 35    {ok, FD} = file:open(Name, [raw, read, write, delayed_write, binary]),
 36    fix_log(FD),
 37    {?MODULE, Name, FD}.
 38
 39%% @spec name(Handle) -> string()
 40%% @doc Return the path of the log file.
 41name({?MODULE, Name, _FD}) ->
 42    Name.
 43
 44%% @spec write(Handle, IoData) -> ok
 45%% @doc Write IoData to the log file referenced by Handle.
 46write({?MODULE, _Name, FD}, IoData) ->
 47    ok = file:write(FD, [IoData, $\n]),
 48    ok.
 49
 50%% @spec close(Handle) -> ok
 51%% @doc Close the log file referenced by Handle.
 52close({?MODULE, _Name, FD}) ->
 53    ok = file:sync(FD),
 54    ok = file:close(FD),
 55    ok.
 56
 57fix_log(FD) ->
 58    {ok, Location} = file:position(FD, eof),
 59    Seek = find_last_newline(FD, Location),
 60    {ok, Seek} = file:position(FD, Seek),
 61    ok = file:truncate(FD),
 62    ok.
 63
 64%% Seek backwards to the last valid log entry
 65find_last_newline(_FD, N) when N =< 1 ->
 66    0;
 67find_last_newline(FD, Location) ->
 68    case file:pread(FD, Location - 1, 1) of
 69	{ok, <<$\n>>} ->
 70            Location;
 71	{ok, _} ->
 72	    find_last_newline(FD, Location - 1)
 73    end.
 74
 75%%
 76%% Tests
 77%%
 78-ifdef(TEST).
 79-include_lib("eunit/include/eunit.hrl").
 80name_test() ->
 81    D = mochitemp:mkdtemp(),
 82    FileName = filename:join(D, "open_close_test.log"),
 83    H = open(FileName),
 84    ?assertEqual(
 85       FileName,
 86       name(H)),
 87    close(H),
 88    file:delete(FileName),
 89    file:del_dir(D),
 90    ok.
 91
 92open_close_test() ->
 93    D = mochitemp:mkdtemp(),
 94    FileName = filename:join(D, "open_close_test.log"),
 95    OpenClose = fun () ->
 96                        H = open(FileName),
 97                        ?assertEqual(
 98                           true,
 99                           filelib:is_file(FileName)),
100                        ok = close(H),
101                        ?assertEqual(
102                           {ok, <<>>},
103                           file:read_file(FileName)),
104                        ok
105                end,
106    OpenClose(),
107    OpenClose(),
108    file:delete(FileName),
109    file:del_dir(D),
110    ok.
111
112write_test() ->
113    D = mochitemp:mkdtemp(),
114    FileName = filename:join(D, "write_test.log"),
115    F = fun () ->
116                H = open(FileName),
117                write(H, "test line"),
118                close(H),
119                ok
120        end,
121    F(),
122    ?assertEqual(
123       {ok, <<"test line\n">>},
124       file:read_file(FileName)),
125    F(),
126    ?assertEqual(
127       {ok, <<"test line\ntest line\n">>},
128       file:read_file(FileName)),
129    file:delete(FileName),
130    file:del_dir(D),
131    ok.
132
133fix_log_test() ->
134    D = mochitemp:mkdtemp(),
135    FileName = filename:join(D, "write_test.log"),
136    file:write_file(FileName, <<"first line good\nsecond line bad">>),
137    F = fun () ->
138                H = open(FileName),
139                write(H, "test line"),
140                close(H),
141                ok
142        end,
143    F(),
144    ?assertEqual(
145       {ok, <<"first line good\ntest line\n">>},
146       file:read_file(FileName)),
147    file:write_file(FileName, <<"first line bad">>),
148    F(),
149    ?assertEqual(
150       {ok, <<"test line\n">>},
151       file:read_file(FileName)),
152    F(),
153    ?assertEqual(
154       {ok, <<"test line\ntest line\n">>},
155       file:read_file(FileName)),
156    ok.
157
158-endif.