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