正文

 

 

  本文記錄了因?yàn)橐粋€(gè)簡(jiǎn)單的日志需求,繼而對(duì)linux環(huán)境下syslog、rsyslog、unix domain socket的學(xué)習(xí)。本文關(guān)注使用層面,并不涉及rsyslog的實(shí)現(xiàn)原理,感興趣的讀者可以參考rsyslog官網(wǎng)。另外,本文實(shí)驗(yàn)的環(huán)境實(shí)在debian8,如果是其他linux發(fā)行版本或者debian的其他版本,可能會(huì)稍微有些差異。

需求:

回到頂部

  工作中有一個(gè)在Linux(debian8)環(huán)境下運(yùn)行的服務(wù)器程序,用python語(yǔ)言實(shí)現(xiàn),代碼中有不同優(yōu)先級(jí)的日志需要記錄,開(kāi)發(fā)的時(shí)候都是使用python的logging模塊輸出到文件,示例代碼如下:

  

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 import logging, os 2  3 logger = None 4 def get_logger(): 5     global logger 6     if not logger: 7         logger = logging.getLogger('ServerLog') 8         logger.setLevel(logging.INFO) 9         filehandler = logging.FileHandler(os.environ['HOME'] + '/Server.log', encoding='utf8')10         filehandler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))11         logger.addHandler(filehandler)12     return logger13 14 def some_func():15     get_logger().info("call some_func")16 17 if __name__ == '__main__':18     some_func()

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 

  運(yùn)行上面這段代碼,就會(huì)在home目錄下面產(chǎn)生一個(gè)server.log文件。

  后來(lái)數(shù)據(jù)分析的部門說(shuō)他們希望能夠?qū)崟r(shí)拿到一部分日志,他們有一臺(tái)專門處理日志的服務(wù)器,那么怎么把日志發(fā)給他們呢?筆者之前并沒(méi)有相關(guān)經(jīng)驗(yàn),數(shù)據(jù)分析部門的同事說(shuō),這種需求他們都是找運(yùn)維人員幫忙。運(yùn)維同事給出的方案很簡(jiǎn)單:產(chǎn)品把日志寫到syslog,然后他們負(fù)責(zé)把帶有某些關(guān)鍵字的日志轉(zhuǎn)發(fā)給數(shù)據(jù)分析部門,在運(yùn)維同事的指導(dǎo)下,把代碼改成了這樣:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 import logging 2 import logging.handlers 3  4 logger = None 5 def get_logger(): 6     global logger 7     if not logger: 8         logger = logging.getLogger('ServerLog') 9         logger.setLevel(logging.INFO)10 11         sys_handler = logging.handlers.SysLogHandler('/dev/log', facility=logging.handlers.SysLogHandler.LOG_LOCAL0)12         syslog_tag = 'ServerLog'13         sys_handler.setFormatter(logging.Formatter(syslog_tag + ":%(asctime)s - %(name)s - %(levelname)s - %(message)s"))14 15         logger.addHandler(sys_handler)16     return logger17 18 def some_func():19     get_logger().info("call some_func")20 21 if __name__ == '__main__':22     some_func()

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 

  上面的代碼修改了日志的輸出形式,直觀的感受就是從文件server.log 到了 /dev/log,但/dev/log對(duì)應(yīng)的是SysLogHandler,并不是FileHandler,所以肯定不是一個(gè)普通的文件。此時(shí),我有兩個(gè)疑問(wèn):第一,這里我并沒(méi)有將日志輸出到home目錄下的Server.log文件,但是程序運(yùn)行的時(shí)候生成了這么一個(gè)文件;第二,怎么講日志發(fā)送到數(shù)據(jù)分析部門的服務(wù)器。

  不懂就問(wèn):

  Q:新的代碼下怎么生成Server.log文件,日志內(nèi)容又是怎么轉(zhuǎn)發(fā)到數(shù)據(jù)分析部門的服務(wù)器?

  A:  這個(gè)是/etc/init.d/rsyslog這個(gè)后臺(tái)程序根據(jù)/etc/rsyslog.conf 這個(gè)配置文件 將日志輸出到不同的文件,包括網(wǎng)絡(luò)文件,即其他服務(wù)器???etc/rsyslog.conf這個(gè)配置就明白了。

  Q:OK,那python代碼將文件輸出到/dev/log跟 rsyslog又是什么關(guān)系呢?

  A:python的sysloghandler會(huì)將日志發(fā)送到rsyslog,他們之間使用unix domain socket通信,具體看logging模塊的源碼就知道了

 

unix domain socket:

回到頂部

  按照上面的對(duì)話的意思,python程序先將日志發(fā)送給rsyslog這個(gè)程序,然后rsyslog再處理收到的日志數(shù)據(jù),所以先看logging代碼:

  SysLogHandler這個(gè)類在logging.handlers.py, 核心代碼如下:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1     def __init__(self, address=('localhost', SYSLOG_UDP_PORT), 2                  facility=LOG_USER, socktype=socket.SOCK_DGRAM): 3         """ 4         Initialize a handler. 5  6         If address is specified as a string, a UNIX socket is used. To log to a 7         local syslogd, "SysLogHandler(address="/dev/log")" can be used. 8         If facility is not specified, LOG_USER is used. 9         """10         logging.Handler.__init__(self)11 12         self.address = address13         self.facility = facility14         self.socktype = socktype15 16         if isinstance(address, basestring):17             self.unixsocket = 118             self._connect_unixsocket(address)19         else:20             self.unixsocket = 021             self.socket = socket.socket(socket.AF_INET, socktype)22             if socktype == socket.SOCK_STREAM:23                 self.socket.connect(address)24         self.formatter = None25 26     def _connect_unixsocket(self, address):27         self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)28         # syslog may require either DGRAM or STREAM sockets29         try:30             self.socket.connect(address)31         except socket.error:32             self.socket.close()33             self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)34             self.socket.connect(address)

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  在__init__.doc里面寫得很清楚,如果address是一個(gè)字符串(默認(rèn)值是一個(gè)tuple),那么會(huì)建立一個(gè)unix socket(unix domain socket)。如果address為“/dev/log”(正如我們之前的python代碼),那么輸出到本機(jī)的syslogd程序。另外,在第27行 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) socket.socket的第一個(gè)參數(shù)family 的值為AF_UNIX,而不是我們經(jīng)常使用的AF_INET(IPV4)或者AF_INET6(IPV6)。那么什么是unix domain socket呢?

  

  unix domain socket是進(jìn)程間通信(IPC:inter-process communication)的一種方式,其他還有管道、命名管道、消息隊(duì)列、共享內(nèi)存、socket之類的。unix domain socket與平常使用的socket(狹義的internet socket)有什么區(qū)別呢,那就是unix domain socket只能在同一臺(tái)主機(jī)上的進(jìn)程之間通信,普通的socket也可以通過(guò)'localhost'來(lái)在同一臺(tái)主機(jī)通信,那么unix domain socket有哪些優(yōu)勢(shì)呢?

  第一:不需要經(jīng)過(guò)網(wǎng)絡(luò)協(xié)議棧

  第二:不需要打包拆包、計(jì)算校驗(yàn)和、維護(hù)序號(hào)和應(yīng)答等

  所以,優(yōu)勢(shì)就是性能好,一個(gè)字,快。

 

  下面用一個(gè)簡(jiǎn)單的服務(wù)器客戶端例子來(lái)看看unix domain socket的使用方法與過(guò)程:

  服務(wù)器:uds_server.py

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 ADDR = '/tmp/uds_tmp' 2  3 import socket, os 4  5 def main(): 6     try: 7         sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 8         if os.path.exists(ADDR): 9             os.unlink(ADDR)10         sock.bind(ADDR)11         sock.listen(5)12         while True:13                 connection, address = sock.accept()14                 print "Data : %s" % connection.recv(1024);15                 connection.send("hello uds client")16                 connection.close()17     finally:18         sock.close()19 20 if __name__ == '__main__':21         main()

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 

  客戶端:uds_client.py

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 ADDR = '/tmp/uds_tmp' 2  3 import socket 4  5 def main(): 6         sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 7         sock.connect(ADDR) 8         sock.send('hello unix domain socket server') 9         print 'client recieve', sock.recv(1024)10         sock.close()11 12 if __name__ == '__main__':13         main()

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 

  首先:運(yùn)行服務(wù)器 python uds_server.py,這個(gè)時(shí)候在/tmp 目錄下產(chǎn)生了文件,用ls查看詳細(xì)信息如下:

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  可以看到,文件類型(第一個(gè)字段)為s,代表socket文件。(PS: 如果進(jìn)程間用命令管道通信,也是利用中間文件,ls顯示的文件類型為p)

  運(yùn)行客戶端 python uds_client.py,在客戶端和服務(wù)器端都有相應(yīng)的輸出,使用方法與普通socket沒(méi)有什么大的差異。

 

日志轉(zhuǎn)發(fā)流程:

回到頂部

  在了解了unix domain socket這個(gè)概念之后,下面就比較簡(jiǎn)單了,首先是/dev/log這個(gè)文件,我們用ls來(lái)查看這個(gè)文件的信息

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  可以看到這個(gè)文件是一個(gè)符號(hào)鏈接文件,真實(shí)的文件是/run/systemd/journal/dev-log, 那么再來(lái)查看這個(gè)文件

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  ok,是一個(gè)socket文件,復(fù)合預(yù)期,按照之前的unix domain socket的例子,rsyslog也應(yīng)該咋這個(gè)文件上監(jiān)聽(tīng),我們來(lái)看看

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  lsof fd可以列出所有使用了這個(gè)文件(linux下文件的概念比較寬泛)的進(jìn)程,事實(shí)上我們看到只有systemd和systemd-j兩個(gè)不明所以的進(jìn)程。那么直接看看rsyslog使用的unix domain socket吧

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

      大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  額,可以看到rsyslogd使用的socket domain socket是/run/systemd/journal/syslog,并不是/run/systemd/journal/dev-log,這兩個(gè)文件在同一個(gè)目錄下,那么再來(lái)看看還有哪些進(jìn)程使用了/run/systemd/journal/syslog。

   大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  so,systemd和rsyslogd都使用了這個(gè)文件,感覺(jué)像是應(yīng)用進(jìn)程(e.g. 上面的python程序)將日志通過(guò)/run/systemd/journal/dev-log(/dev/log背后真正的文件)發(fā)送到systemd, 然后systemd 再將日志通過(guò)/run/systemd/journal/syslog發(fā)送到rsyslogd,是不是這樣呢,google了一下,發(fā)現(xiàn)了這篇文章understand-logging-in-linux,確實(shí)是這么一個(gè)過(guò)程:

  

systemd has a single monolithic log management program, systemd-journald. This runs as a service managed by systemd.

  • It reads /dev/kmsg for kernel log data.

  • It reads /dev/log (a symbolic link to /run/systemd/journal/dev-log) for application log data from the GNU C library's syslog() function.

  • It listens on the AF_LOCAL stream socket at /run/systemd/journal/stdout for log data coming from systemd-managed services.

  • It listens on the AF_LOCAL datagram socket at /run/systemd/journal/socket for log data coming from programs that speak the systemd-specific journal protocol (i.e. sd_journal_sendv() et al.).

  • It mixes these all together.

  • It writes to a set of system-wide and per-user journal files, in /run/log/journal/ or /var/log/journal/.

  • If it can connect (as a client) to an AF_LOCAL datagram socket at /run/systemd/journal/syslogit writes journal data there, if forwarding to syslog is configured.

  

  ok,到現(xiàn)在為止,我們知道了應(yīng)用程序的日志是怎么轉(zhuǎn)發(fā)到rsyslog,那么rsyslog怎么處理接收到的日志,秘密就在/etc/rsyslog.conf, 在打開(kāi)這個(gè)配置文件之前,我們先看看rsyslog官網(wǎng)的簡(jiǎn)單描述:

   RSYSLOG is the rocket-fast system for log processing.

   原來(lái)R是rocket-fast的意思!火箭一般快!官網(wǎng)聲稱每秒可以處理百萬(wàn)級(jí)別的日志。rsyslogd在部分linux環(huán)境是默認(rèn)的syslogd程序(至少在筆者的機(jī)器上),d是daemon的意思,后臺(tái)進(jìn)程。系統(tǒng)啟動(dòng)的時(shí)候就會(huì)啟動(dòng)該進(jìn)程來(lái)處理日志(包括操作系統(tǒng)自身和用戶進(jìn)程的日志)。打開(kāi)修改過(guò)的/etc/rsyslog.conf, 接下來(lái)就是見(jiàn)證奇跡的時(shí)刻

   大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  原來(lái)一舉一動(dòng)都在監(jiān)控之中。這個(gè)文件是系統(tǒng)提供的,直接在這個(gè)文件上做修改顯然不是明智之舉。如上圖紅色部分,可以再rysyslog.d文件夾下增加自己的配置文件,定制日志過(guò)濾規(guī)則。那么看看的rsyslog.d文件夾下新增的tmp.conf

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 $FileOwner USERNAME 2 $FileGroup USERNAME 3 $FileCreateMode 0644 4 $DirCreateMode 0755 5 $Umask 0022 6 $template serverLog,"/home/USERNAME/Server.log" 7 $template LogFormat,"%msg%\n" 8 if $syslogfacility-text == 'local0' and $syslogtag contains 'ServerLog' then -?serverLog;LogFormat 9 10 #if $syslogfacility-text == 'local0' and $syslogtag contains 'ServerLog' then @someip:port11 & stop

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  再來(lái)回顧一下對(duì)應(yīng)的應(yīng)用代碼:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 import logging 2 import logging.handlers 3  4 logger = None 5 def get_logger(): 6     global logger 7     if not logger: 8         logger = logging.getLogger('ServerLog') 9         logger.setLevel(logging.INFO)10 11         sys_handler = logging.handlers.SysLogHandler('/dev/log', facility=logging.handlers.SysLogHandler.LOG_LOCAL0)12         syslog_tag = 'ServerLog'13         sys_handler.setFormatter(logging.Formatter(syslog_tag + ":%(asctime)s - %(name)s - %(levelname)s - %(message)s"))14 15         logger.addHandler(sys_handler)16     return logger17 18 def some_func():19     get_logger().info("call some_func")20 21 if __name__ == '__main__':22     some_func()

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  注意:配置文件需要與應(yīng)用代碼配合,比如代碼中第11行 facility=logging.handlers.SysLogHandler.LOG_LOCAL0 與 配置中 $syslogfacility-text == 'local0' 相對(duì)應(yīng);代碼第12行 syslog_tag = 'ServerLog' 與 配置文件 $syslogtag contains 'ServerLog' 對(duì)應(yīng)。關(guān)于python代碼中syslogtag的設(shè)置,參考了stackoverflow上的這個(gè)問(wèn)答。

  當(dāng)我們修改了配置時(shí)候需要通過(guò)命令 /etc/init.d/rsyslog restart 來(lái)重啟rsyslogd,重啟之后再運(yùn)行之前的python文件,就可以了。

   大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 發(fā)送到遠(yuǎn)端服務(wù)器:

  上面的tmp.conf文件注釋掉了第10行,這一行的作用是將滿足條件的日志發(fā)送到指定的其他機(jī)器上,IP:Port用來(lái)指定接受日志的遠(yuǎn)端rsyslogd程序。默認(rèn)情況下rsyslogd在514端口監(jiān)聽(tīng)。假設(shè)我需要給局域網(wǎng)內(nèi)10.240.10.10發(fā)送syslog,第10行改成這樣就行了:

if $syslogfacility-text == 'local0' and $syslogtag contains 'ServerLog' then @10.240.10.10

  那么10.240.10.10主要開(kāi)啟rsyslogd的遠(yuǎn)程監(jiān)聽(tīng),并指定遠(yuǎn)端日志的輸出規(guī)則,for example:

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  這個(gè)配置,讓rsyslogd使用UDP和TCP協(xié)議同時(shí)在514端口上監(jiān)聽(tīng),并將非本機(jī)的日志輸出到對(duì)應(yīng)遠(yuǎn)端主機(jī)名的文件。注意,以上修改 都需要重啟rsyslogd才能生效。

總結(jié):

回到頂部

  日志從應(yīng)用程序到最終的日志文件(或者遠(yuǎn)程服務(wù)器)的流程如下:

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  

references:

回到頂部

inter-process communication

unix domain socket

understand-logging-in-linux

在 Linux 上配置一個(gè) syslog 服務(wù)器

本文版權(quán)歸作者xybaby(博文地址:http://www.cnblogs.com/xybaby/)所有,歡迎轉(zhuǎn)載和商用,請(qǐng)?jiān)谖恼马?yè)面明顯位置給出原文鏈接并保留此段聲明,否則保留追究法律責(zé)任的權(quán)利,其他事項(xiàng),可留言咨詢。

標(biāo)簽: linux

http://www.cnblogs.com/xybaby/p/6596431.html