Subversion Repositories SE.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
12 7u83 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.
34
open(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.
41
name({?MODULE, Name, _FD}) ->
42
    Name.
43
 
44
%% @spec write(Handle, IoData) -> ok
45
%% @doc Write IoData to the log file referenced by Handle.
46
write({?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.
52
close({?MODULE, _Name, FD}) ->
53
    ok = file:sync(FD),
54
    ok = file:close(FD),
55
    ok.
56
 
57
fix_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
65
find_last_newline(_FD, N) when N =< 1 ->
66
    0;
67
find_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").
80
name_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
 
92
open_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
 
112
write_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
 
133
fix_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.