diff --git a/src/couchdb/couch_log.erl b/src/couchdb/couch_log.erl index 2676689..03e2fa7 100644 --- a/src/couchdb/couch_log.erl +++ b/src/couchdb/couch_log.erl @@ -27,10 +27,12 @@ -define(LEVEL_INFO, 2). -define(LEVEL_DEBUG, 1). +-define(MAX_BUFFER_LEN, 64 * 1024). + -record(state, { - fd, level, - sasl + sasl, + writer }). debug(Format, Args) -> @@ -81,17 +83,21 @@ init([]) -> Sasl = couch_config:get("log", "include_sasl", "true") =:= "true", case ets:info(?MODULE) of - undefined -> ets:new(?MODULE, [named_table]); + undefined -> ets:new(?MODULE, [named_table, {read_concurrency, true}]); _ -> ok end, ets:insert(?MODULE, {level, Level}), - case file:open(Filename, [append]) of - {ok, Fd} -> - {ok, #state{fd = Fd, level = Level, sasl = Sasl}}; - {error, eacces} -> + process_flag(trap_exit, true), + Parent = self(), + Writer = spawn_link(fun() -> writer(Parent, Filename) end), + + receive + {Writer, ok} -> + {ok, #state{writer = Writer, level = Level, sasl = Sasl}}; + {Writer, {error, eacces}} -> {stop, {file_permission_error, Filename}}; - Error -> + {Writer, Error} -> {stop, Error} end. @@ -143,18 +149,21 @@ handle_call({set_level_integer, NewLevel}, State) -> ets:insert(?MODULE, {level, NewLevel}), {ok, ok, State#state{level = NewLevel}}. +handle_info({'EXIT', Pid, Reason}, #state{writer = Pid}) -> + io:format("[error] log writer died: ~p~n", [Reason]), + exit({log_writer_died, Reason}); handle_info(_Info, State) -> {ok, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}. -terminate(_Arg, #state{fd = Fd}) -> - file:close(Fd). +terminate(_Arg, #state{writer = Writer}) -> + Writer ! stop, + receive {'EXIT', Writer, _} -> ok end. -log(#state{fd = Fd}, ConsoleMsg, FileMsg) -> - ok = io:put_chars(ConsoleMsg), - ok = io:put_chars(Fd, FileMsg). +log(#state{writer = Writer}, ConsoleMsg, FileMsg) -> + Writer ! {log, FileMsg, ConsoleMsg}. get_log_messages(Pid, Level, Format, Args) -> ConsoleMsg = unicode:characters_to_binary(io_lib:format( @@ -175,3 +184,41 @@ read(Bytes, Offset) -> {ok, Chunk} = file:pread(Fd, Start, LogFileSize), ok = file:close(Fd), Chunk. + + +writer(Parent, Filename) -> + case file:open(Filename, [append, raw, binary]) of + {ok, Fd} -> + Parent ! {self(), ok}, + writer_loop(Fd); + Error -> + Parent ! {self(), Error} + end. + +writer_loop(Fd) -> + receive + {log, FileMsg, ConsoleMsg} -> + ok = io:put_chars(ConsoleMsg), + ok = file:write(Fd, FileMsg), + writer_loop(Fd); + % writer_loop_batch(Fd, [FileMsg], [ConsoleMsg], byte_size(FileMsg)); + stop -> + ok = file:close(Fd) + end. + +writer_loop_batch(Fd, AccFile, AccConsole, Len) when Len >= ?MAX_BUFFER_LEN -> + ok = io:put_chars(lists:reverse(AccConsole)), + ok = file:write(Fd, lists:reverse(AccFile)), + writer_loop(Fd); + +writer_loop_batch(Fd, AccFile, AccConsole, Len) -> + receive + {log, FileMsg, ConsoleMsg} -> + writer_loop_batch( + Fd, [FileMsg | AccFile], [ConsoleMsg | AccConsole], + Len + byte_size(FileMsg)) + after 0 -> + ok = io:put_chars(lists:reverse(AccConsole)), + ok = file:write(Fd, lists:reverse(AccFile)), + writer_loop(Fd) + end.