Created
August 23, 2012 03:44
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% | |
%% 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