Skip to content

Instantly share code, notes, and snippets.

@mardambey
Created August 23, 2012 03:44
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mardambey/3432032 to your computer and use it in GitHub Desktop.
Save mardambey/3432032 to your computer and use it in GitHub Desktop.
mod_log_remote allows for logging Ejabberd messages / packets to a remote Erlang node.
%%
%% mod_log_remote is a simple Ejabberd module and gen_server that
%% allow for remote logging. It uses the filter_packet hook to
%% intercept <message> stanzas addressed to logger@vhost. These
%% messages are beamed off to the configured Erland node / pid in
%% the form:
%%
%% {Type, LogTime, Payload}
%%
%% where Type and Payload are specified by the caller and LogTime is
%% the time the module beamed off the packet.
%% mod_log_remote also pings the remote Erlang node at a specified
%% interval to alert the other side that it is still alive in case
%% no messages come through for a short period of time.
%%
%% For other modules to log through this they need to call the:
%%
%% log(Type, Payload)
%%
%% function that will ship the log message out as specified above.
%%
-module(mod_log_remote).
-author('hisham.mardambey@gmail.com').
%% gen_server API
-behaviour(gen_server).
-export([start_link/3]).
-export([init/1, handle_call/3,
handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%% Ejabberd gen_mod API
-behaviour(gen_mod).
-export([start/2, stop/1,
filter_packet/1]).
%% API
-export([log/2]).
-include("ejabberd.hrl").
-include("jlib.hrl").
%% If these are set to something other than the
%% default (through the configuration) then every
%% allowed message stanza will be send to the given
%% Pid / Node pair.
%%
-define(DEFAULT_LOG_PID, nopid).
-define(DEFAULT_LOG_NODE, nonode).
-define(DEFAULT_PING_INTERVAL, 10000).
%% Logging configuration
%%
-record(log_config, {pid, node, pingInterval}).
%% Ejabberd hook API
%% Starts the module and reads in the configuration.
%% Starts the supervisor as well.
%%
start(Host, Opts) ->
?INFO_MSG("Loading module 'mod_log_remote'", []),
LogPid = gen_mod:get_opt(log_pid, Opts, ?DEFAULT_LOG_PID),
LogNode = gen_mod:get_opt(log_node, Opts, ?DEFAULT_LOG_NODE),
PingInt = gen_mod:get_opt(ping_interval, Opts, ?DEFAULT_PING_INTERVAL),
Proc = gen_mod:get_module_proc(Host, ?MODULE),
ChildSpec = {Proc,
{?MODULE, start_link, [Host, Opts, #log_config{pid = LogPid, node = LogNode, pingInterval = PingInt}]},
permanent,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
%% Shuts down the module.
%%
stop(Host) ->
?INFO_MSG("Unloading module 'mod_log_remote'", []),
Proc = gen_mod:get_module_proc(Host, ?MODULE),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc),
ok.
%% If the packet is a message sent to "logger" we'll intercept it,
%% beam it off to the logger, and drop it.
%%
filter_packet({_From, To, {xmlelement, "message", _Attrs, _Els} = _Packet} = Msg) ->
case To#jid.luser of
"logger" ->
log(event, msg_to_event_payload(Msg)),
drop;
_ -> Msg
end;
filter_packet(drop) -> drop;
filter_packet(Input) -> Input.
%% gen_server API
start_link(_Host, _Opts, Config) ->
?DEBUG("start_link: ~p", [Config]),
gen_server:start_link({local, ?MODULE}, ?MODULE, [Config], []).
init([Config]) ->
inets:start(),
%% priority matters as this module drops messages it processes
ejabberd_hooks:add(filter_packet, global, ?MODULE, filter_packet, 120),
erlang:send_after(Config#log_config.pingInterval, self(), ping),
{ok, Config}.
handle_call({beam, Type, Payload}, _From, Config) ->
?DEBUG("handle_call {beam}: Type=~p Payload=~p", [Type, Payload]),
beam(Type, Payload, Config),
{reply, ok, Config};
handle_call(_Request, _From, Config) ->
{reply, unknown, Config}.
handle_cast({beam, Type, Payload}, Config) ->
?DEBUG("handle_cast {beam}: Type=~p Payload=~p", [Type, Payload]),
beam(Type, Payload, Config),
{noreply, Config};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(ping, Config) ->
{Mega, Secs, _} = now(),
PingTime = Mega*1000000 + Secs,
log(ping, {PingTime}),
erlang:send_after(Config#log_config.pingInterval, self(), ping),
{noreply, Config};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ejabberd_hooks:delete(filter_packet, global, ?MODULE, filter_packet, 120),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% API
%% Send a log message consisting of a Type and Payload
%%
log(Type, Payload) ->
gen_server:cast(?MODULE, {beam, Type, Payload}).
%% Internal API
%% Send off Type and Payload to the remote logger
%% in the following format:
%%
%% {Type, LogTime, Payload}
%%
%% where LogTime is the current time on the server.
%%
beam(Type, Payload, Config) ->
if Config#log_config.pid /= nopid andalso Config#log_config.node /= nonode ->
LogPid = Config#log_config.pid,
LogNode = Config#log_config.node,
?DEBUG("mod_log_remote: logging to ~p ~p", [LogPid, LogNode]),
{Mega, Secs, _} = now(),
LogTime = Mega*1000000 + Secs,
{LogPid, LogNode} ! {Type, LogTime, Payload}
end.
%% Given a <message> stanza this call converts it into a tuple with
%% the following format:
%%
%% {Jid, Msg}
%%
msg_to_event_payload({From, _To, {xmlelement, "message", _Attrs, _Els} = _Packet} = Msg) ->
Jid = jlib:jid_to_string(From),
{Jid, Msg}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment