%% @author Bob Ippolito %% @copyright 2010 Mochi Media, Inc. %% %% Permission is hereby granted, free of charge, to any person obtaining a %% copy of this software and associated documentation files (the "Software"), %% to deal in the Software without restriction, including without limitation %% the rights to use, copy, modify, merge, publish, distribute, sublicense, %% and/or sell copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in %% all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL %% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER %% DEALINGS IN THE SOFTWARE. %% @doc Write newline delimited log files, ensuring that if a truncated %% entry is found on log open then it is fixed before writing. Uses %% delayed writes and raw files for performance. -module(mochilogfile2). -author('bob@mochimedia.com'). -export([open/1, write/2, close/1, name/1]). %% @spec open(Name) -> Handle %% @doc Open the log file Name, creating or appending as necessary. All data %% at the end of the file will be truncated until a newline is found, to %% ensure that all records are complete. open(Name) -> {ok, FD} = file:open(Name, [raw, read, write, delayed_write, binary]), fix_log(FD), {?MODULE, Name, FD}. %% @spec name(Handle) -> string() %% @doc Return the path of the log file. name({?MODULE, Name, _FD}) -> Name. %% @spec write(Handle, IoData) -> ok %% @doc Write IoData to the log file referenced by Handle. write({?MODULE, _Name, FD}, IoData) -> ok = file:write(FD, [IoData, $\n]), ok. %% @spec close(Handle) -> ok %% @doc Close the log file referenced by Handle. close({?MODULE, _Name, FD}) -> ok = file:sync(FD), ok = file:close(FD), ok. fix_log(FD) -> {ok, Location} = file:position(FD, eof), Seek = find_last_newline(FD, Location), {ok, Seek} = file:position(FD, Seek), ok = file:truncate(FD), ok. %% Seek backwards to the last valid log entry find_last_newline(_FD, N) when N =< 1 -> 0; find_last_newline(FD, Location) -> case file:pread(FD, Location - 1, 1) of {ok, <<$\n>>} -> Location; {ok, _} -> find_last_newline(FD, Location - 1) end. %% %% Tests %% -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). name_test() -> D = mochitemp:mkdtemp(), FileName = filename:join(D, "open_close_test.log"), H = open(FileName), ?assertEqual( FileName, name(H)), close(H), file:delete(FileName), file:del_dir(D), ok. open_close_test() -> D = mochitemp:mkdtemp(), FileName = filename:join(D, "open_close_test.log"), OpenClose = fun () -> H = open(FileName), ?assertEqual( true, filelib:is_file(FileName)), ok = close(H), ?assertEqual( {ok, <<>>}, file:read_file(FileName)), ok end, OpenClose(), OpenClose(), file:delete(FileName), file:del_dir(D), ok. write_test() -> D = mochitemp:mkdtemp(), FileName = filename:join(D, "write_test.log"), F = fun () -> H = open(FileName), write(H, "test line"), close(H), ok end, F(), ?assertEqual( {ok, <<"test line\n">>}, file:read_file(FileName)), F(), ?assertEqual( {ok, <<"test line\ntest line\n">>}, file:read_file(FileName)), file:delete(FileName), file:del_dir(D), ok. fix_log_test() -> D = mochitemp:mkdtemp(), FileName = filename:join(D, "write_test.log"), file:write_file(FileName, <<"first line good\nsecond line bad">>), F = fun () -> H = open(FileName), write(H, "test line"), close(H), ok end, F(), ?assertEqual( {ok, <<"first line good\ntest line\n">>}, file:read_file(FileName)), file:write_file(FileName, <<"first line bad">>), F(), ?assertEqual( {ok, <<"test line\n">>}, file:read_file(FileName)), F(), ?assertEqual( {ok, <<"test line\ntest line\n">>}, file:read_file(FileName)), ok. -endif.