Merge pull request #60 from weiss/otp-message-log

Run mod_message_log as a supervised gen_server process
This commit is contained in:
badlop 2015-01-23 17:22:23 +01:00
commit fb643dfb10
1 changed files with 122 additions and 47 deletions

View File

@ -9,12 +9,22 @@
-author('holger@zedat.fu-berlin.de'). -author('holger@zedat.fu-berlin.de').
-behaviour(gen_mod). -behaviour(gen_mod).
-behaviour(gen_server).
-export([start/2, %% gen_mod/supervisor callbacks.
stop/1, -export([start_link/1,
init/1, start/2,
perform_upgrade/1]). stop/1]).
%% gen_server callbacks.
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
%% ejabberd_hooks callbacks.
-export([log_packet_send/3, -export([log_packet_send/3,
log_packet_receive/4, log_packet_receive/4,
log_packet_offline/3, log_packet_offline/3,
@ -27,7 +37,22 @@
-define(DEFAULT_FILENAME, <<"message.log">>). -define(DEFAULT_FILENAME, <<"message.log">>).
-define(FILE_MODES, [append, raw]). -define(FILE_MODES, [append, raw]).
-record(config, {filename = ?DEFAULT_FILENAME, iodevice}). -record(state, {filename = ?DEFAULT_FILENAME :: binary(),
iodevice :: io:device()}).
-type direction() :: incoming | outgoing | offline.
-type state() :: #state{}.
%% -------------------------------------------------------------------
%% gen_mod/supervisor callbacks.
%% -------------------------------------------------------------------
-spec start_link(gen_mod:opts()) -> {ok, pid()} | ignore | {error, _}.
start_link(Opts) ->
gen_server:start_link({local, ?PROCNAME}, ?MODULE, Opts, []).
-spec start(binary(), gen_mod:opts()) -> {ok, _} | {ok, _, _} | {error, _}.
start(Host, Opts) -> start(Host, Opts) ->
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
@ -36,17 +61,17 @@ start(Host, Opts) ->
log_packet_receive, 42), log_packet_receive, 42),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
log_packet_offline, 42), log_packet_offline, 42),
case whereis(?PROCNAME) of Spec = {
undefined -> ?PROCNAME,
ejabberd_hooks:add(reopen_log_hook, ?MODULE, reopen_log, 42), {?MODULE, start_link, [Opts]},
Filename = gen_mod:get_opt(filename, Opts, fun(V) -> V end, permanent,
?DEFAULT_FILENAME), 3000,
register(?PROCNAME, spawn(?MODULE, init, worker,
[#config{filename = Filename}])), [?MODULE]
ok; },
_ -> supervisor:start_child(ejabberd_sup, Spec).
ok
end. -spec stop(binary()) -> ok.
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
@ -55,42 +80,102 @@ stop(Host) ->
log_packet_receive, 42), log_packet_receive, 42),
ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE,
log_packet_offline, 42), log_packet_offline, 42),
case whereis(?PROCNAME) of case supervisor:terminate_child(ejabberd_sup, ?PROCNAME) of
undefined -> ok ->
ok; ok = supervisor:delete_child(ejabberd_sup, ?PROCNAME);
_ -> {error, not_found} ->
ejabberd_hooks:delete(reopen_log_hook, ?MODULE, reopen_log, 42), ok % We just run one process per node.
gen_mod:get_module_proc(Host, ?PROCNAME) ! stop,
ok
end. end.
init(Config) -> %% -------------------------------------------------------------------
{ok, IoDevice} = file:open(Config#config.filename, ?FILE_MODES), %% gen_server callbacks.
loop(Config#config{iodevice = IoDevice}). %% -------------------------------------------------------------------
-spec init(gen_mod:opts()) -> {ok, state()}.
init(Opts) ->
process_flag(trap_exit, true),
ejabberd_hooks:add(reopen_log_hook, ?MODULE, reopen_log, 42),
Filename = gen_mod:get_opt(filename, Opts, fun(V) -> V end,
?DEFAULT_FILENAME),
{ok, IoDevice} = file:open(Filename, ?FILE_MODES),
{ok, #state{filename = Filename, iodevice = IoDevice}}.
-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
handle_call(_Request, _From, State) ->
{noreply, State}.
-spec handle_cast(_, state()) -> {noreply, state()}.
handle_cast({message, Direction, From, To, Type}, #state{iodevice = IoDevice} =
State) ->
write_log(IoDevice, Direction, From, To, Type),
{noreply, State};
handle_cast(reopen_log, #state{filename = Filename, iodevice = IoDevice} =
State) ->
ok = file:close(IoDevice),
{ok, NewIoDevice} = file:open(Filename, ?FILE_MODES),
{noreply, State#state{iodevice = NewIoDevice}};
handle_cast(_Request, State) ->
{noreply, State}.
-spec handle_info(timeout | _, state()) -> {noreply, state()}.
handle_info(_Info, State) ->
{noreply, State}.
-spec terminate(normal | shutdown | {shutdown, _} | _, _) -> any().
terminate(_Reason, State) ->
ejabberd_hooks:delete(reopen_log_hook, ?MODULE, reopen_log, 42),
ok = file:close(State#state.iodevice).
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% -------------------------------------------------------------------
%% ejabberd_hooks callbacks.
%% -------------------------------------------------------------------
-spec log_packet_send(jid(), jid(), xmlel()) -> any().
log_packet_send(From, To, Packet) -> log_packet_send(From, To, Packet) ->
log_packet(outgoing, From, To, Packet). log_packet(outgoing, From, To, Packet).
-spec log_packet_receive(jid(), jid(), jid(), xmlel()) -> any().
log_packet_receive(JID, From, _To, Packet) -> log_packet_receive(JID, From, _To, Packet) ->
log_packet(incoming, From, JID, Packet). log_packet(incoming, From, JID, Packet).
-spec log_packet_offline(jid(), jid(), xmlel()) -> any().
log_packet_offline(From, To, Packet) -> log_packet_offline(From, To, Packet) ->
log_packet(offline, From, To, Packet). log_packet(offline, From, To, Packet).
reopen_log() -> -spec reopen_log() -> any().
?PROCNAME ! reopen.
reopen_log() ->
gen_server:cast(?PROCNAME, reopen_log).
%% -------------------------------------------------------------------
%% Internal functions. %% Internal functions.
%% -------------------------------------------------------------------
-spec log_packet(direction(), jid(), jid(), xmlel()) -> any().
log_packet(Direction, From, To, #xmlel{name = <<"message">>} = Packet) -> log_packet(Direction, From, To, #xmlel{name = <<"message">>} = Packet) ->
case xml:get_subtag(Packet, <<"body">>) of case xml:get_subtag(Packet, <<"body">>) of
#xmlel{children = Body} when length(Body) > 0 -> #xmlel{children = Body} when length(Body) > 0 ->
Type = get_message_type(Packet), Type = get_message_type(Packet),
?PROCNAME ! {message, Direction, From, To, Type}; gen_server:cast(?PROCNAME, {message, Direction, From, To, Type});
_ -> _ ->
case is_carbon(Packet) of case is_carbon(Packet) of
{true, OrigDirection} -> {true, OrigDirection} ->
?PROCNAME ! {message, OrigDirection, From, To, carbon}; gen_server:cast(?PROCNAME, {message, OrigDirection, From, To,
carbon});
false -> false ->
ok ok
end end
@ -98,6 +183,8 @@ log_packet(Direction, From, To, #xmlel{name = <<"message">>} = Packet) ->
log_packet(_Direction, _From, _To, _Packet) -> log_packet(_Direction, _From, _To, _Packet) ->
ok. ok.
-spec get_message_type(xmlel()) -> binary().
get_message_type(#xmlel{attrs = Attrs}) -> get_message_type(#xmlel{attrs = Attrs}) ->
case xml:get_attr_s(<<"type">>, Attrs) of case xml:get_attr_s(<<"type">>, Attrs) of
<<"">> -> <<"">> ->
@ -106,6 +193,8 @@ get_message_type(#xmlel{attrs = Attrs}) ->
Type Type
end. end.
-spec is_carbon(xmlel()) -> {true, direction()} | false.
is_carbon(Packet) -> is_carbon(Packet) ->
{Direction, SubTag} = case {xml:get_subtag(Packet, <<"sent">>), {Direction, SubTag} = case {xml:get_subtag(Packet, <<"sent">>),
xml:get_subtag(Packet, <<"received">>)} of xml:get_subtag(Packet, <<"received">>)} of
@ -128,20 +217,7 @@ is_carbon(Packet) ->
false false
end. end.
loop(Config) -> -spec write_log(io:device(), direction(), jid(), jid(), binary()) -> ok.
receive
{message, Direction, From, To, Type} ->
write_log(Config#config.iodevice, Direction, From, To, Type),
loop(Config);
reopen ->
file:close(Config#config.iodevice),
{ok, IoDevice} = file:open(Config#config.filename, ?FILE_MODES),
loop(Config#config{iodevice = IoDevice});
upgrade ->
mod_message_log:perform_upgrade(Config);
stop ->
exit(normal)
end.
write_log(IoDevice, Direction, From, To, Type) -> write_log(IoDevice, Direction, From, To, Type) ->
Date = format_date(calendar:local_time()), Date = format_date(calendar:local_time()),
@ -151,9 +227,8 @@ write_log(IoDevice, Direction, From, To, Type) ->
jlib:jid_to_string(To)]), jlib:jid_to_string(To)]),
ok = file:write(IoDevice, [Record]). ok = file:write(IoDevice, [Record]).
-spec format_date(calendar:datetime()) -> io_lib:chars().
format_date({{Year, Month, Day}, {Hour, Minute, Second}}) -> format_date({{Year, Month, Day}, {Hour, Minute, Second}}) ->
Format = "~B-~2..0B-~2..0B ~2..0B:~2..0B:~2..0B", Format = "~B-~2..0B-~2..0B ~2..0B:~2..0B:~2..0B",
io_lib:format(Format, [Year, Month, Day, Hour, Minute, Second]). io_lib:format(Format, [Year, Month, Day, Hour, Minute, Second]).
perform_upgrade(Config) ->
loop(Config).