-
-
Save ngwing/21dedbddf330b7bfe746 to your computer and use it in GitHub Desktop.
Ejabberd module: log message,iq,presence stanza to a xml log file.
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
-module(mod_logxml). | |
-author('badlop@ono.com'). | |
-behaviour(gen_mod). | |
-export([ | |
start/2, | |
init/7, | |
stop/1, | |
send_packet/3, | |
receive_packet/4 | |
]). | |
-include("ejabberd.hrl"). | |
-include("logger.hrl"). | |
-include("jlib.hrl"). | |
-define(PROCNAME, ejabberd_mod_logxml). | |
start(Host, Opts) -> | |
%% 日志存储目录 | |
Logdir = gen_mod:get_opt(logdir, Opts, fun(A) -> A end, "/tmp/jabberlogs/"), | |
?DEBUG("EJABBERD_MOD_LOGXML Logdir: ~p", [Logdir]), | |
%% 日志滚动天数 | |
Rd = gen_mod:get_opt(rotate_days, Opts, fun(A) -> A end, 1), | |
?DEBUG("EJABBERD_MOD_LOGXML Rd: ~p", [Rd]), | |
%% 日志滚动兆字节数,默认日志文件满10MB后创建新文件记录当前日志输出 | |
Rf = case gen_mod:get_opt(rotate_megs, Opts, fun(A) -> A end, 10) of | |
no -> no; | |
Rf1 -> Rf1 * 1024 * 1024 | |
end, | |
?DEBUG("EJABBERD_MOD_LOGXML Rf: ~p", [Rf]), | |
%% 按接收到的XMPP数据包的数量滚动 | |
Rp = case gen_mod:get_opt(rotate_kpackets, Opts, fun(A) -> A end, 10) of | |
no -> no; | |
Rp1 -> Rp1 * 1000 | |
end, | |
?DEBUG("EJABBERD_MOD_LOGXML Rp: ~p", [Rp]), | |
%% 日志滚动选项 | |
RotateO = {Rd, Rf, Rp}, | |
CheckRKP = gen_mod:get_opt(check_rotate_kpackets, Opts, fun(A) -> A end, 1), | |
?DEBUG("EJABBERD_MOD_LOGXML RotateO: ~p", [RotateO]), | |
%% 时区配置选项 | |
Timezone = gen_mod:get_opt(timezone, Opts, fun(A) -> A end, local), | |
?DEBUG("EJABBERD_MOD_LOGXML Timezone: ~p", [Timezone]), | |
%% 数据流方向 | |
Orientation = gen_mod:get_opt(orientation, Opts, fun(A) -> A end, [send, recv]), | |
?DEBUG("EJABBERD_MOD_LOGXML Orientation: ~p", [Orientation]), | |
%% XMPP节 | |
Stanza = gen_mod:get_opt(stanza, Opts, fun(A) -> A end, [iq, message, presence, other]), | |
?DEBUG("EJABBERD_MOD_LOGXML Stanza: ~p", [Stanza]), | |
%% 连接方向 | |
Direction = gen_mod:get_opt(direction, Opts, fun(A) -> A end, [internal, vhosts, external]), | |
?DEBUG("EJABBERD_MOD_LOGXML Direction: ~p", [Direction]), | |
%% 过滤器选项 | |
FilterO = { | |
{orientation, Orientation}, | |
{stanza, Stanza}, | |
{direction, Direction}}, | |
%% 是否显示IP地址 | |
ShowIP = gen_mod:get_opt(show_ip, Opts, fun(A) -> A end, false), | |
?DEBUG("EJABBERD_MOD_LOGXML ShowIP: ~p", [ShowIP]), | |
%% 用户收发XMPP数据包钩子 | |
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, send_packet, 90), | |
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, receive_packet, 90), | |
%% 进程注册 | |
register( | |
%% 获取模块 | |
gen_mod:get_module_proc(Host, ?PROCNAME), | |
spawn(?MODULE, init, [Host, Logdir, RotateO, CheckRKP, Timezone, ShowIP, FilterO]) | |
). | |
stop(Host) -> | |
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, send_packet, 90), | |
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, receive_packet, 90), | |
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), | |
Proc ! stop, | |
{wait, Proc}. | |
init(Host, Logdir, RotateO, CheckRKP, Timezone, ShowIP, FilterO) -> | |
{IoDevice, Filename, Gregorian_day} = open_file(Logdir, Host, Timezone), | |
loop(Host, IoDevice, Filename, Logdir, CheckRKP, RotateO, 0, Gregorian_day, Timezone, ShowIP, FilterO). | |
manage_rotate(Host, IoDevice, Filename, Logdir, RotateO, PacketC, | |
Gregorian_day_log, Timezone) -> | |
{RO_days, RO_size, RO_packets} = RotateO, | |
Rotate1 = case RO_packets of | |
no -> false; | |
PacketC -> true; | |
_ -> false | |
end, | |
Filesize = filelib:file_size(Filename), | |
Rotate2 = if | |
RO_size == no -> false; | |
Filesize >= RO_size -> true; | |
true -> false | |
end, | |
Gregorian_day_today = get_gregorian_day(), | |
Rotate3 = if | |
RO_days == no -> false; | |
(Gregorian_day_today - Gregorian_day_log) >= RO_days -> | |
true; | |
true -> false | |
end, | |
case lists:any(fun(E) -> E end, [Rotate1, Rotate2, Rotate3]) of | |
true -> | |
{IoDevice2, Filename2, Gregorian_day2} = | |
rotate_log(IoDevice, Logdir, Host, Timezone), | |
{IoDevice2, Filename2, Gregorian_day2, 0}; | |
false -> | |
{IoDevice, Filename, Gregorian_day_log, PacketC + 1} | |
end. | |
filter(FilterO, E) -> | |
{{orientation, OrientationO}, {stanza, StanzaO}, {direction, DirectionO}} = FilterO, | |
?DEBUG("EJABBERD_MOD_LOGXML FilterO: ~p", [FilterO]), | |
{Orientation, From, To, Packet} = E, | |
?DEBUG("EJABBERD_MOD_LOGXML E: ~p", [E]), | |
?DEBUG("EJABBERD_MOD_LOGXML Packet before: ~p", [Packet]), | |
?DEBUG("EJABBERD_MOD_LOGXML Stanza_str before: ~p", [Packet#xmlel.name]), | |
Stanza_str = Packet#xmlel.name, | |
% {xmlelement, Stanza_str, _Attrs, _Els} = Packet, | |
Stanza = list_to_atom(binary_to_list(Stanza_str)), | |
case lists:member(Stanza, StanzaO) of | |
true -> ?DEBUG("EJABBERD_MOD_LOGXML Member: ", []); | |
false -> ?DEBUG("EJABBERD_MOD_LOGXML Not: ", []) | |
end, | |
?DEBUG("EJABBERD_MOD_LOGXML Stanza_str after: ~p", [Stanza]), | |
Hosts_all = ejabberd_config:get_myhosts(), | |
?DEBUG("EJABBERD_MOD_LOGXML Hosts_all after: ~p", [Hosts_all]), | |
{Host_local, Host_remote} = case Orientation of | |
send -> {From#jid.lserver, To#jid.lserver}; | |
recv -> {To#jid.lserver, From#jid.lserver} | |
end, | |
?DEBUG("EJABBERD_MOD_LOGXML Host_local : ~p", [Host_local]), | |
?DEBUG("EJABBERD_MOD_LOGXML Host_remote : ~p", [Host_remote]), | |
Direction = case Host_remote of | |
Host_local -> internal; | |
_ -> | |
case lists:member(Host_remote, Hosts_all) of | |
true -> vhosts; | |
false -> external | |
end | |
end, | |
{lists:all(fun(O) -> O end, | |
[lists:member(Orientation, OrientationO), | |
lists:member(Stanza, StanzaO), | |
lists:member(Direction, DirectionO)]), | |
{Orientation, Stanza, Direction}}. | |
loop(Host, IoDevice, Filename, Logdir, CheckRKP, RotateO, PacketC, | |
Gregorian_day, Timezone, ShowIP, FilterO) -> | |
receive | |
{addlog, E} -> | |
{IoDevice3, Filename3, Gregorian_day3, PacketC3} = | |
case filter(FilterO, E) of | |
{true, OSD} -> | |
Div = calc_div(PacketC, CheckRKP), | |
{IoDevice2, Filename2, Gregorian_day2, PacketC2} = | |
case Div == round(Div) of | |
true -> | |
manage_rotate(Host, IoDevice, Filename, | |
Logdir, RotateO, PacketC, | |
Gregorian_day, Timezone); | |
false -> | |
{IoDevice, Filename, Gregorian_day, | |
PacketC + 1} | |
end, | |
add_log(IoDevice2, Timezone, ShowIP, E, OSD), | |
{IoDevice2, Filename2, Gregorian_day2, PacketC2}; | |
_ -> | |
{IoDevice, Filename, Gregorian_day, PacketC} | |
end, | |
loop(Host, IoDevice3, Filename3, Logdir, CheckRKP, RotateO, | |
PacketC3, Gregorian_day3, Timezone, ShowIP, FilterO); | |
stop -> | |
close_file(IoDevice), | |
ok; | |
_ -> | |
loop(Host, IoDevice, Filename, Logdir, CheckRKP, RotateO, PacketC, | |
Gregorian_day, Timezone, ShowIP, FilterO) | |
end. | |
send_packet(FromJID, ToJID, P) -> | |
Host = FromJID#jid.lserver, | |
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), | |
?DEBUG("EJABBERD_MOD_LOGXML send_packet Proc: ~p", [Proc]), | |
?DEBUG("EJABBERD_MOD_LOGXML send_packet FromJID: ~p", [FromJID]), | |
?DEBUG("EJABBERD_MOD_LOGXML send_packet ToJID: ~p", [ToJID]), | |
?DEBUG("EJABBERD_MOD_LOGXML send_packet P: ~p", [P]), | |
% add_log(Io, Timezone, ShowIP,{send, FromJID, ToJID, P}). | |
Proc ! {addlog, {send, FromJID, ToJID, P}}. | |
receive_packet(_JID, From, To, P) -> | |
Host = To#jid.lserver, | |
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), | |
?DEBUG("EJABBERD_MOD_LOGXML receive_packet Proc: ~p", [Proc]), | |
?DEBUG("EJABBERD_MOD_LOGXML receive_packet From: ~p", [From]), | |
?DEBUG("EJABBERD_MOD_LOGXML receive_packet To: ~p", [To]), | |
?DEBUG("EJABBERD_MOD_LOGXML receive_packet P: ~p", [P]), | |
Proc ! {addlog, {recv, From, To, P}}. | |
add_log(Io, Timezone, ShowIP, {Orientation, From, To, Packet}, _OSD) -> | |
%%{Orientation, Stanza, Direction} = OSD, | |
LocalJID = case Orientation of | |
send -> From; | |
recv -> To | |
end, | |
LocalIPS = case ShowIP of | |
true -> | |
{UserIP, _Port} = ejabberd_sm:get_user_ip( | |
LocalJID#jid.user, | |
LocalJID#jid.server, | |
LocalJID#jid.resource), | |
io_lib:format("lip=\"~s\" ", [inet_parse:ntoa(UserIP)]); | |
false -> "" | |
end, | |
TimestampISO = get_now_iso(Timezone), | |
?DEBUG("EJABBERD_MOD_LOGXML Packet: ~p", [Packet]), | |
?DEBUG("EJABBERD_MOD_LOGXML Packet binary_to_list: ~p", [xml:element_to_binary(Packet)]), | |
% Test = xml:element_to_string(Packet), | |
% ?DEBUG("EJABBERD_MOD_LOGXML Test : ~p",[Test]), | |
io:fwrite( | |
Io, | |
"<packet or=\"~p\" ljid=\"~s\" ~sts=\"~s\">~s</packet>~n", | |
[Orientation, jlib:jid_to_string(LocalJID), LocalIPS, TimestampISO, xml:element_to_binary(Packet)] | |
). | |
%% io:fwrite( | |
%% Io, | |
%% "~s", | |
%% [xml:element_to_binary(Packet)] | |
%% ). | |
%% ------------------- | |
%% File | |
%% ------------------- | |
open_file(Logdir, Host, Timezone) -> | |
BintimeStamp = get_now_iso(Timezone), | |
TimeStamp = binary_to_list(BintimeStamp), | |
?DEBUG("EJABBERD_MOD_LOGXML TimeStamp: ~p", [TimeStamp]), | |
%% Year = string:substr(TimeStamp, 1, 4), | |
%% Month = string:substr(TimeStamp, 5, 2), | |
%% Day = string:substr(TimeStamp, 7, 2), | |
%% Hour = string:substr(TimeStamp, 10, 2), | |
%% Min = string:substr(TimeStamp, 13, 2), | |
%% Sec = string:substr(TimeStamp, 16, 2), | |
%% S = "-", | |
%% Logname = lists:flatten([Host, S, Year, S, Month, S, Day, S, Hour, S, Min, S, Sec, ".xml"]), | |
Logname = lists:flatten([Host,".xml"]), | |
Filename = filename:join([Logdir, Logname]), | |
Gregorian_day = get_gregorian_day(), | |
%% Open file, create if it does not exist, create parent dirs if needed | |
case file:read_file_info(Filename) of | |
{ok, _} -> | |
{ok, IoDevice} = file:open(Filename, [append]); | |
{error, enoent} -> | |
make_dir_rec(Logdir), | |
{ok, IoDevice} = file:open(Filename, [append]) | |
%% io:fwrite(IoDevice, "~s~n", ["<?xml version=\"1.0\"?>"]), | |
%% io:fwrite(IoDevice, "~s~n", ["<?xml-stylesheet href=\"xmpp.xsl\" type=\"text/xsl\"?>"]), | |
%% io:fwrite(IoDevice, "~s~n", ["<log>"]) | |
end, | |
{IoDevice, Filename, Gregorian_day}. | |
close_file(IoDevice) -> | |
%% io:fwrite(IoDevice, "~s~n", ["</log>"]), | |
file:close(IoDevice). | |
rotate_log(IoDevice, Logdir, Host, Timezone) -> | |
close_file(IoDevice), | |
open_file(Logdir, Host, Timezone). | |
make_dir_rec(Dir) -> | |
case file:read_file_info(Dir) of | |
{ok, _} -> | |
ok; | |
{error, enoent} -> | |
DirS = filename:split(Dir), | |
DirR = lists:sublist(DirS, length(DirS) - 1), | |
make_dir_rec(filename:join(DirR)), | |
file:make_dir(Dir) | |
end. | |
%% ------------------- | |
%% Utils | |
%% ------------------- | |
get_gregorian_day() -> calendar:date_to_gregorian_days(date()). | |
get_now_iso(Timezone) -> | |
TimeStamp = case Timezone of | |
local -> calendar:now_to_local_time(now()); | |
universal -> calendar:now_to_universal_time(now()) | |
end, | |
jlib:timestamp_to_iso(TimeStamp). | |
calc_div(A, B) when is_integer(A) and is_integer(B) and B =/= 0 -> | |
A / B; | |
calc_div(_A, _B) -> | |
0.5. %% This ensures that no rotation is performed |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment