工做中有一個在Linux(debian8)環境下運行的服務器程序,用python語言實現,代碼中有不一樣優先級的日誌須要記錄,開發的時候都是使用python的logging模塊輸出到文件,示例代碼以下:linux
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()
運行上面這段代碼,就會在home目錄下面產生一個server.log文件。git
後來數據分析的部門說他們但願可以實時拿到一部分日誌,他們有一臺專門處理日誌的服務器,那麼怎麼把日誌發給他們呢?筆者以前並無相關經驗,數據分析部門的同事說,這種需求他們都是找運維人員幫忙。運維同事給出的方案很簡單:產品把日誌寫到syslog,而後他們負責把帶有某些關鍵字的日誌轉發給數據分析部門,在運維同事的指導下,把代碼改爲了這樣:服務器
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()
上面的代碼修改了日誌的輸出形式,直觀的感覺就是從文件server.log 到了 /dev/log,但/dev/log對應的是SysLogHandler,並非FileHandler,因此確定不是一個普通的文件。此時,我有兩個疑問:第一,這裏我並無將日誌輸出到home目錄下的Server.log文件,可是程序運行的時候生成了這麼一個文件;第二,怎麼講日誌發送到數據分析部門的服務器。網絡
不懂就問:app
Q:新的代碼下怎麼生成Server.log文件,日誌內容又是怎麼轉發到數據分析部門的服務器?運維
A: 這個是/etc/init.d/rsyslog這個後臺程序根據/etc/rsyslog.conf 這個配置文件 將日誌輸出到不一樣的文件,包括網絡文件,即其餘服務器。看/etc/rsyslog.conf這個配置就明白了。dom
Q:OK,那python代碼將文件輸出到/dev/log跟 rsyslog又是什麼關係呢?socket
A:python的sysloghandler會將日誌發送到rsyslog,他們之間使用unix domain socket通訊,具體看logging模塊的源碼就知道了ide
按照上面的對話的意思,python程序先將日誌發送給rsyslog這個程序,而後rsyslog再處理收到的日誌數據,因此先看logging代碼:
SysLogHandler這個類在logging.handlers.py, 核心代碼以下:
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)
在__init__.doc裏面寫得很清楚,若是address是一個字符串(默認值是一個tuple),那麼會創建一個unix socket(unix domain socket)。若是address爲「/dev/log」(正如咱們以前的python代碼),那麼輸出到本機的syslogd程序。另外,在第27行 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) socket.socket的第一個參數family 的值爲AF_UNIX,而不是咱們常用的AF_INET(IPV4)或者AF_INET6(IPV6)。那麼什麼是unix domain socket呢?
unix domain socket是進程間通訊(IPC:inter-process communication)的一種方式,其餘還有管道、命名管道、消息隊列、共享內存、socket之類的。unix domain socket與日常使用的socket(狹義的internet socket)有什麼區別呢,那就是unix domain socket只能在同一臺主機上的進程之間通訊,普通的socket也能夠經過'localhost'來在同一臺主機通訊,那麼unix domain socket有哪些優點呢?
第一:不須要通過網絡協議棧
第二:不須要打包拆包、計算校驗和、維護序號和應答等
因此,優點就是性能好,一個字,快。
下面用一個簡單的服務器客戶端例子來看看unix domain socket的使用方法與過程:
服務器:uds_server.py
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()
客戶端:uds_client.py
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()
首先:運行服務器 python uds_server.py,這個時候在/tmp 目錄下產生了文件,用ls查看詳細信息以下:
能夠看到,文件類型(第一個字段)爲s,表明socket文件。(PS: 若是進程間用命令管道通訊,也是利用中間文件,ls顯示的文件類型爲p)
運行客戶端 python uds_client.py,在客戶端和服務器端都有相應的輸出,使用方法與普通socket沒有什麼大的差別。
在瞭解了unix domain socket這個概念以後,下面就比較簡單了,首先是/dev/log這個文件,咱們用ls來查看這個文件的信息
能夠看到這個文件是一個符號連接文件,真實的文件是/run/systemd/journal/dev-log, 那麼再來查看這個文件
ok,是一個socket文件,複合預期,按照以前的unix domain socket的例子,rsyslog也應該咋這個文件上監聽,咱們來看看
lsof fd能夠列出全部使用了這個文件(linux下文件的概念比較寬泛)的進程,事實上咱們看到只有systemd和systemd-j兩個不明因此的進程。那麼直接看看rsyslog使用的unix domain socket吧
額,能夠看到rsyslogd使用的socket domain socket是/run/systemd/journal/syslog,並非/run/systemd/journal/dev-log,這兩個文件在同一個目錄下,那麼再來看看還有哪些進程使用了/run/systemd/journal/syslog。
so,systemd和rsyslogd都使用了這個文件,感受像是應用進程(e.g. 上面的python程序)將日誌經過/run/systemd/journal/dev-log(/dev/log背後真正的文件)發送到systemd, 而後systemd 再將日誌經過/run/systemd/journal/syslog發送到rsyslogd,是否是這樣呢,google了一下,發現了這篇文章understand-logging-in-linux,確實是這麼一個過程:
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,到如今爲止,咱們知道了應用程序的日誌是怎麼轉發到rsyslog,那麼rsyslog怎麼處理接收到的日誌,祕密就在/etc/rsyslog.conf, 在打開這個配置文件以前,咱們先看看rsyslog官網的簡單描述:
RSYSLOG is the rocket-fast system for log processing.
原來R是rocket-fast的意思!火箭通常快!官網聲稱每秒能夠處理百萬級別的日誌。rsyslogd在部分linux環境是默認的syslogd程序(至少在筆者的機器上),d是daemon的意思,後臺進程。系統啓動的時候就會啓動該進程來處理日誌(包括操做系統自身和用戶進程的日誌)。打開修改過的/etc/rsyslog.conf, 接下來就是見證奇蹟的時刻
原來一舉一動都在監控之中。這個文件是系統提供的,直接在這個文件上作修改顯然不是明智之舉。如上圖紅色部分,能夠再rysyslog.d文件夾下增長本身的配置文件,定製日誌過濾規則。那麼看看的rsyslog.d文件夾下新增的tmp.conf
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
再來回顧一下對應的應用代碼:
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()
注意:配置文件須要與應用代碼配合,好比代碼中第11行 facility=logging.handlers.SysLogHandler.LOG_LOCAL0 與 配置中 $syslogfacility-text == 'local0' 相對應;代碼第12行 syslog_tag = 'ServerLog' 與 配置文件 $syslogtag contains 'ServerLog' 對應。關於python代碼中syslogtag的設置,參考了stackoverflow上的這個問答。
當咱們修改了配置時候須要經過命令 /etc/init.d/rsyslog restart 來重啓rsyslogd,重啓以後再運行以前的python文件,就能夠了。
上面的tmp.conf文件註釋掉了第10行,這一行的做用是將知足條件的日誌發送到指定的其餘機器上,IP:Port用來指定接受日誌的遠端rsyslogd程序。默認狀況下rsyslogd在514端口監聽。假設我須要給局域網內10.240.10.10發送syslog,第10行改爲這樣就好了:
if $syslogfacility-text == 'local0' and $syslogtag contains 'ServerLog' then @10.240.10.10
那麼10.240.10.10主要開啓rsyslogd的遠程監聽,並指定遠端日誌的輸出規則,for example:
這個配置,讓rsyslogd使用UDP和TCP協議同時在514端口上監聽,並將非本機的日誌輸出到對應遠端主機名的文件。注意,以上修改 都須要重啓rsyslogd才能生效。
日誌從應用程序到最終的日誌文件(或者遠程服務器)的流程以下: