用python socket发送rsyslog日志

来源:互联网 发布:全球网络加速器 编辑:程序博客网 时间:2024/06/05 00:46

最近做了一个项目,其中要求把日志发送给syslog服务器,客户端是用python写的,开始要求发送用UDP协议就行,简单;可后来要求改用TCP,问题就出来了,rsyslog收到日志,就是不写入指定的日志文件中,所以有必要把这其中的历程写下来。

UDP rsyslog

(1) 首先编辑/etc/rsyslog.conf,把udp注释去掉

# provides udp syslog reception$modload imudp$InputUDPServerRun 514##### template ####$template LongFileFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %syslogpriority% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"$template ScTemplate,"/var/log/tem.log"if $msg contains "*|" then{        *.*     ?ScTemplate;LongFileFormat        & stop}

(2)客户端用python socket正常发送就行

#!/usr/bin/env python#-*-coding:utf-8 -*-import sockets = u"5*|987654321*|admin*|check*|1*|加班*|datacenterid=1*|"setSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)try:    setSock.sendto(s,(HOST, 514))except socket.error, e:    print esetSock.close()

TCP syslog

这个才是重中之重,开始我只是按照上面的配置方法把相应的udp改成tcp,我以为就完事了,谁想….客户端用socket的tcp把数据成功发送出去了,服务器端的514也改成tcp协议了,selinux,iptables等都放开了,用tcpdump监听514端口,也收到数据了,但是,就是不写到日志文件里。

#创建/etc/rsyslog.d/tcp.conf文件,内容如下# provides tcp syslog reception$modload imtcp$InputTCPServerRun 514##### template ####$template LongFileFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %syslogpriority% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"$template ScTemplate,"/var/log/tem.log"if $msg contains "*|" then{        *.*     ?ScTemplate;LongFileFormat        & stop}

后来我用echo “5*|987654321*|admin*|check*|” | nc host port命令模拟发送syslog日志时,syslog竟然写入到日志文件里了。这时初步判定,问题在发送方,即python socket tcp。
(1)我用模块logging发送syslog日志时,偶尔也能成功,所以我就决定看一下这个logging发送tcp的原理,找到如下,浓缩版:

#!/usr/bin/env python#-*-coding:utf-8 -*-import socketimport cPickleimport structs = u"5*|987654321*|admin*|check*|1*|加班*|datacenterid=1*|"setSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)setSock.connect((HOST, 514))cs = cPickle.dumps(s)#slen = struct.pack(">L", 2)try:    setSock.send(slen + cs)except socket.error, e:    passsetSock.close()

从上面的代码中我们可以定位到logging模块用了两个关键的处理cPickle.dumps和struct.pack,后来发现struct.pack没啥特别大的用。因为我发送的是字符串,如果是字典的话用处就大了。
(2)但是这个发送时,syslog接收的日志开头会莫名的多出一个大写V。后来知道如果被传入字符串是unicode时会是V,utf-8时是S。为了去掉这个多余难看的V,我又看了cPickle模块的源码,原理浓缩如下:

#!/usr/bin/env python#-*-coding:utf-8 -*-try:    from cStringIO import StringIOexcept ImportError:    from StringIO import StringIOimport sockets = u"5*|987654321*|admin*|check*|1*|加班*|datacenterid=1*|"def dumps(obj):    fd = StringIO()    obj = obj.replace("\\", "\\u005c")    obj = obj.replace("\n", "\\u000a")    fd.write('V' + obj.encode('raw-unicode-escape') + '\n')    fd.write('p' + repr(0) + '\n')    fd.write('.')    return fd.getvalue()ss = dumps(a)setSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)setSock.connect((HOST,514))try:    setSock.send(ss)except (AttributeError,socket.error), e:    passsetSock.close()

从上面源码可以发现,它发送的是一个ASCII流。但是为什么要添加那么多的V,P还有换行符呢?
(3)从上面我发现了一个重点raw-unicode-escape,感觉这个东东肯定是重点,然后就把传入的字符串都转换成这个格式试试,结果失望。参考了这个,在我不断尝试中,终于发现了问题的关键,浓缩代码如下:

#!/usr/bin/env python#-*-coding:utf-8 -*-import sockets = u"5*|987654321*|admin*|check*|1*|加班*|datacenterid=1*|"setSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)setSock.connect((HOST,514))ss = (s + '\n').encode('raw-unicode-escape')try:    setSock.send(ss)except (AttributeError,socket.error), e:    passsetSock.close()

我认为火眼晶晶的小伙伴们都发现了重点,就是字符串后面必须加个\n,还要用raw-unicode-escape编码,具体为啥,我也不知道。