aboutsummaryrefslogtreecommitdiff
path: root/src/mod_http_fileserver_log.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_http_fileserver_log.erl')
-rw-r--r--src/mod_http_fileserver_log.erl179
1 files changed, 179 insertions, 0 deletions
diff --git a/src/mod_http_fileserver_log.erl b/src/mod_http_fileserver_log.erl
new file mode 100644
index 000000000..6ceb37ef4
--- /dev/null
+++ b/src/mod_http_fileserver_log.erl
@@ -0,0 +1,179 @@
+-module(mod_http_fileserver_log).
+
+-behaviour(gen_server).
+
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
+
+-export([start_link/2, start/2, stop/1, add_to_log/4,
+ reopen_log/1]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+
+-include("jlib.hrl").
+
+-include("ejabberd_http.hrl").
+
+-include_lib("kernel/include/file.hrl").
+
+-define(PROCNAME, ejabberd_mod_http_fileserver_log).
+
+-record(state, {host = <<"">>,
+ accesslog :: binary(),
+ accesslogfd :: file:io_device()}).
+
+%% Public API
+
+start(Host, Filename) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ ChildSpec = {Proc,
+ {?MODULE, start_link, [Host, Filename]},
+ transient, % if process crashes abruptly, it gets restarted
+ 1000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, ChildSpec).
+
+stop(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:call(Proc, stop),
+ supervisor:terminate_child(ejabberd_sup, Proc),
+ supervisor:delete_child(ejabberd_sup, Proc).
+
+start_link(Host, Filename) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Filename], []).
+
+-spec add_to_log(binary(), non_neg_integer(), non_neg_integer(),
+ http_request()) -> ok.
+
+add_to_log(Host, FileSize, Code, Request) ->
+ gen_server:cast(gen_mod:get_module_proc(Host,
+ ?PROCNAME),
+ {add_to_log, FileSize, Code, Request}).
+
+-spec reopen_log(binary()) -> ok.
+
+reopen_log(Host) ->
+ gen_server:cast(gen_mod:get_module_proc(Host,
+ ?PROCNAME),
+ reopen_log).
+
+%% Server implementation, a.k.a.: callbacks
+
+init([Host, Filename]) ->
+ try try_open_log(Filename, Host) of
+ AccessLogFD ->
+ ?DEBUG("File opened !", []),
+ {ok,
+ #state{host = Host, accesslog = Filename,
+ accesslogfd = AccessLogFD}}
+ catch
+ Reason -> {stop, Reason}
+ end.
+
+try_open_log(FN, Host) ->
+ FD = try open_log(FN) of
+ FD1 -> FD1
+ catch
+ {cannot_open_accesslog, FN, Reason} ->
+ ?ERROR_MSG("Cannot open access log file: ~p~nReason: ~p",
+ [FN, Reason]),
+ undefined
+ end,
+ ejabberd_hooks:add(reopen_log_hook, Host, ?MODULE,
+ reopen_log, 50),
+ FD.
+
+handle_call(_Request, _From, State) ->
+ {reply, ok, State}.
+
+handle_cast({add_to_log, FileSize, Code, Request},
+ State) ->
+ add_to_log2(State#state.accesslogfd, FileSize, Code,
+ Request),
+ {noreply, State};
+handle_cast(reopen_log, State) ->
+ FD2 = reopen_log(State#state.accesslog,
+ State#state.accesslogfd),
+ {noreply, State#state{accesslogfd = FD2}};
+handle_cast(_Msg, State) -> {noreply, State}.
+
+handle_info(_Info, State) -> {noreply, State}.
+
+terminate(_Reason, State) ->
+ close_log(State#state.accesslogfd),
+ ejabberd_hooks:delete(reopen_log_hook, State#state.host,
+ ?MODULE, reopen_log, 50),
+ ok.
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+%%----------------------------------------------------------------------
+%% Log file
+%%----------------------------------------------------------------------
+
+-spec open_log(binary()) -> file:io_device().
+
+open_log(FN) ->
+ case file:open(FN, [append]) of
+ {ok, FD} -> FD;
+ {error, Reason} ->
+ throw({cannot_open_accesslog, FN, Reason})
+ end.
+
+close_log(FD) -> file:close(FD).
+
+reopen_log(undefined, undefined) -> ok;
+reopen_log(FN, FD) ->
+ ?DEBUG("reopening logs", []),
+ close_log(FD),
+ open_log(FN).
+
+-spec add_to_log2(file:io_device(), non_neg_integer(), non_neg_integer(),
+ http_request()) -> ok.
+
+add_to_log2(undefined, _FileSize, _Code, _Request) ->
+ ok;
+add_to_log2(File, FileSize, Code, Request) ->
+ {{Year, Month, Day}, {Hour, Minute, Second}} =
+ calendar:local_time(),
+ IP = jlib:ip_to_list(Request#request.ip),
+ Path = str:join(Request#request.path, <<"/">>),
+ Query = case str:join(
+ lists:flatmap(fun ({nokey, []}) ->
+ [];
+ ({K, V}) ->
+ [<<K/binary, $=, V/binary>>]
+ end, Request#request.q),
+ <<"&">>) of
+ <<"">> -> <<"">>;
+ String -> <<$?, String/binary>>
+ end,
+ UserAgent = find_header('User-Agent',
+ Request#request.headers, <<"-">>),
+ Referer = find_header('Referer',
+ Request#request.headers, <<"-">>),
+ io:format(File,
+ <<"~s - - [~p/~p/~p:~p:~p:~p] \"~s /~s~s\" "
+ "~p ~p \"~s\" \"~s\"~n">>,
+ [IP, Day, Month, Year, Hour, Minute, Second,
+ Request#request.method, Path, Query, Code, FileSize,
+ escape_quote(Referer), escape_quote(UserAgent)]).
+
+find_header(Header, Headers, Default) ->
+ case lists:keysearch(Header, 1, Headers) of
+ {value, {_, Value}} -> Value;
+ false -> Default
+ end.
+
+
+escape_quote(B) ->
+ escape_quote(B, <<>>).
+
+escape_quote(<<$", Rest/binary>>, Acc) ->
+ escape_quote(Rest, <<Acc/binary, $\\, $">>);
+escape_quote(<<C, Rest/binary>>, Acc) ->
+ escape_quote(Rest, <<Acc/binary, C>>);
+escape_quote(<<>>, Acc) ->
+ Acc.