Python的logging模塊提供了通用的日誌系統,能夠方便第三方模塊或者是應用使用。這個模塊提供不一樣的日誌級別,並能夠採用不一樣的方式記錄日誌,好比文件,HTTP GET/POST,SMTP,Socket等,甚至能夠本身實現具體的日誌記錄方式。python
logging模塊與log4j的機制是同樣的,只是具體的實現細節不一樣。正則表達式
模塊提供logger,handler,filter,formatter:app
1)logger:提供日誌接口,供應用代碼使用。logger最長用的操做有兩類:配置和發送日誌消息。能夠經過logging.getLogger(name)獲取logger對象,若是不指定name則返回root對象,屢次使用相同的name調用getLogger方法返回同一個logger對象。socket
2)handler:將日誌記錄(log record)發送到合適的目的地(destination),好比文件,socket等。一個logger對象能夠經過addHandler方法添加0到多個handler,每一個handler又能夠定義不一樣日誌級別,以實現日誌分級過濾顯示。函數
3)filter:提供一種優雅的方式決定一個日誌記錄是否發送到handler。this
4)formatter:指定日誌記錄輸出的具體格式。formatter的構造方法須要兩個參數:消息的格式字符串和日期字符串,這兩個參數都是可選的。spa
與log4j相似,logger,handler和日誌消息的調用能夠有具體的日誌級別(Level),只有在日誌消息的級別大於logger和handler的級別。.net
RotatingFileHandler:debug
#!/usr/bin/python # -*- coding:utf-8 -*- # # ithomer.net import logging import logging.handlers LOG_FILE = 'tst.log' handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes = 1024*1024, backupCount=5) fmt = '%(asctime)s - %(filename)s:%(lineno)s - %(name)s - %(levelname)s - %(message)s' formatter = logging.Formatter(fmt) handler.setFormatter(formatter) logger = logging.getLogger('tst') logger.addHandler(handler) logger.setLevel(logging.DEBUG) logger.info('info msg') logger.debug('debug msg')
運行結果(tst.log日誌文件內容):
2013-11-28 21:19:25,442 - logtest3.py:21 - tst - INFO - info msg
2013-11-28 21:19:25,442 - logtest3.py:22 - tst - DEBUG - debug msg日誌
TimedRotatingFileHandler:
#!/usr/bin/env python # -*- coding: utf-8 -*- import logging import logging.handlers def main(): logger = logging.getLogger() logger.setLevel(logging.DEBUG) handler = logging.handlers.TimedRotatingFileHandler("test.log", 'D') # fmt = logging.Formatter("%(asctime)s - %(pathname)s - %(filename)s - %(funcName)s - %(lineno)s - %(levelname)s - %(message)s", "%Y-%m-%d %H:%M:%S") fmt = logging.Formatter("%(asctime)s - %(pathname)s - %(filename)s - %(funcName)s - %(lineno)s - %(levelname)s - %(message)s") handler.setFormatter(fmt) logger.addHandler(handler) logger.debug("debug msg") logger.info("info msg") logger.warn("warn msg") logger.warning("warning msg") logger.error("error msg") logger.fatal("fatal msg") logger.critical("critical msg") if __name__ == "__main__": main()
運行結果:
2013-12-31 17:39:07,093 - /home/homer/workspace/myPython/com/mylogging.py - mylogging.py - main - 17 - DEBUG - debug msg 2013-12-31 17:39:07,093 - /home/homer/workspace/myPython/com/mylogging.py - mylogging.py - main - 18 - INFO - info msg 2013-12-31 17:39:07,093 - /home/homer/workspace/myPython/com/mylogging.py - mylogging.py - main - 19 - WARNING - warn msg 2013-12-31 17:39:07,093 - /home/homer/workspace/myPython/com/mylogging.py - mylogging.py - main - 20 - WARNING - warning msg 2013-12-31 17:39:07,093 - /home/homer/workspace/myPython/com/mylogging.py - mylogging.py - main - 21 - ERROR - error msg 2013-12-31 17:39:07,093 - /home/homer/workspace/myPython/com/mylogging.py - mylogging.py - main - 22 - CRITICAL - fatal msg 2013-12-31 17:39:07,093 - /home/homer/workspace/myPython/com/mylogging.py - mylogging.py - main - 23 - CRITICAL - critical msg
關於formatter的配置,採用的是%(<dict key>)s的形式,就是字典的關鍵字替換。提供的關鍵字包括:
Format | Description |
---|---|
%(name)s | Name of the logger (logging channel). |
%(levelno)s | Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). |
%(levelname)s | Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). |
%(pathname)s | Full pathname of the source file where the logging call was issued (if available). |
%(filename)s | Filename portion of pathname. |
%(module)s | Module (name portion of filename). |
%(funcName)s | Name of function containing the logging call. |
%(lineno)d | Source line number where the logging call was issued (if available). |
%(created)f | Time when the LogRecord was created (as returned by time.time()). |
%(relativeCreated)d | Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded. |
%(asctime)s | Human-readable time when the LogRecord was created. By default this is of the form 「2003-07-08 16:49:45,896」 (the numbers after the comma are millisecond portion of the time). |
%(msecs)d | Millisecond portion of the time when the LogRecord was created. |
%(thread)d | Thread ID (if available). |
%(threadName)s | Thread name (if available). |
%(process)d | Process ID (if available). |
%(message)s | The logged message, computed as msg % args. |
logging的配置能夠採用python代碼或是配置文件。python代碼的方式就是在應用的主模塊中,構建handler,handler,formatter等對象。而配置文件的方式是將這些對象的依賴關係分離出來放在文件中。好比前面的例子就相似於python代碼的配置方式。這裏看一下采用配置文件的方式。
#!/usr/bin/python # -*- coding:utf-8 -*- # # ithomer.net import logging import logging.config LOG_FILE = 'tst.log' logging.config.fileConfig('logging.conf') # instance of logging logger = logging.getLogger('tst') logger.debug('debug msg') logger.info('info msg') logger.warn('warn msg') logger.error('error msg') logger.critical('critical msg')
loggin.conf採用了模式匹配的方式進行配置,正則表達式是r'^[(.*)]$',從而匹配出全部的組件。對於同一個組件具備多個實例的狀況使用逗號‘,’進行分隔。對於一個實例的配置採用componentName_instanceName配置塊。使用這種方式仍是蠻簡單的。
[loggers] keys=root,tst [handlers] keys=consoleHandler [formatters] keys=tstFormatter [logger_root] level=DEBUG handlers=consoleHandler [logger_tst] level=DEBUG handlers=consoleHandler qualname=tst propagate=0 [handler_consoleHandler] class=StreamHandler level=DEBUG formatter=tstFormatter args=(sys.stdout,) [formatter_tstFormatter] format=%(asctime)s - %(filename)s:%(lineno)s - %(name)s - %(levelname)s - %(message)s datefmt=
在指定handler的配置時,class是具體的handler類的類名,能夠是相對logging模塊或是全路徑類名,好比須要RotatingFileHandler,則class的值能夠爲:RotatingFileHandler或者logging.handlers.RotatingFileHandler。args就是要傳給這個類的構造方法的參數,就是一個元組,按照構造方法聲明的參數的順序。
運行結果,控制檯輸出:
2013-11-28 23:02:32,574 - logtest4.py:16 - tst - DEBUG - debug msg
2013-11-28 23:02:32,574 - logtest4.py:17 - tst - INFO - info msg
2013-11-28 23:02:32,574 - logtest4.py:18 - tst - WARNING - warn msg
2013-11-28 23:02:32,574 - logtest4.py:19 - tst - ERROR - error msg
2013-11-28 23:02:32,574 - logtest4.py:20 - tst - CRITICAL - critical msg
這裏還要明確一點,logger對象是有繼承關係的,好比名爲a.b和a.c的logger都是名爲a的子logger,而且全部的logger對象都繼承於root。若是子對象沒有添加handler等一些配置,會從父對象那繼承。這樣就能夠經過這種繼承關係來複用配置。
logging模塊保證在同一個python解釋器內,屢次調用logging.getLogger('log_name')都會返回同一個logger實例,即便是在多個模塊的狀況下。因此典型的多模塊場景下使用logging的方式是在main模塊中配置logging,這個配置會做用於多個的子模塊,而後在其餘模塊中直接經過getLogger獲取Logger對象便可。
這裏使用上面配置文件:
[loggers] keys=root,main [handlers] keys=consoleHandler,fileHandler [formatters] keys=fmt [logger_root] level=DEBUG handlers=consoleHandler [logger_main] level=DEBUG handlers=fileHandler qualname=main #propagate=0 [handler_consoleHandler] class=StreamHandler level=DEBUG formatter=fmt args=(sys.stdout,) [handler_fileHandler] class=logging.handlers.RotatingFileHandler level=DEBUG formatter=fmt args=('tst.log','a',20000,5,) [formatter_fmt] format=%(asctime)s - %(filename)s:%(lineno)s - %(name)s - %(levelname)s - %(message)s datefmt=
主模塊main.py:
#!/usr/bin/python # -*- coding:utf-8 -*- # # main.py # # ithomer.net import logging import logging.config logging.config.fileConfig('logging.conf') # root logger_root = logging.getLogger('root') logger_root.debug('debug root') # main logger_main = logging.getLogger('main') logger_main.info('info main') logger_main.info('start import module \'mod\'...') import mod logger_main.debug('test mod.testLogger()') mod.testLogger() logger_root.info('the end!')
子模塊mod.py:
#!/usr/bin/python # -*- coding:utf-8 -*- # # mod.py # # ithomer.net import logging logger = logging.getLogger('main.mod') logger.info('info of main.mod') def testLogger(): logger.debug('this is main.mod testLogging()') logger.info('start import module \'submod\'...') import submod submod.testLogger()
子子模塊submod.py:
#!/usr/bin/python # -*- coding:utf-8 -*- # # submod.py # # ithomer.net import logging logger = logging.getLogger('main.mod.submod') logger.info('info of main.mod.submod') def testLogger(): logger.debug('this is main.mod.submod testLogging()')
而後運行python main.py,控制檯輸出:
2013-11-28 16:58:15,517 - main.py:16 - root - DEBUG - debug root 2013-11-28 16:58:15,517 - main.py:20 - main - INFO - info main 2013-11-28 16:58:15,517 - main.py:22 - main - INFO - start import module 'mod'... 2013-11-28 16:58:15,518 - mod.py:11 - main.mod - INFO - info of main.mod 2013-11-28 16:58:15,518 - main.py:24 - main - DEBUG - test mod.testLogger() 2013-11-28 16:58:15,518 - mod.py:14 - main.mod - DEBUG - this is main.mod testLogging() 2013-11-28 16:58:15,519 - mod.py:15 - main.mod - INFO - start import module 'submod'... 2013-11-28 16:58:15,519 - submod.py:11 - main.mod.submod - INFO - info of main.mod.submod 2013-11-28 16:58:15,519 - submod.py:14 - main.mod.submod - DEBUG - this is main.mod.submod testLogging() 2013-11-28 16:58:15,519 - main.py:27 - root - INFO - the end!
能夠看出,和預想的同樣,而後在看一下tst.log,logger配置中的輸出的目的地:
2013-11-28 16:58:15,517 - main.py:20 - main - INFO - info main 2013-11-28 16:58:15,517 - main.py:22 - main - INFO - start import module 'mod'... 2013-11-28 16:58:15,518 - mod.py:11 - main.mod - INFO - info of main.mod 2013-11-28 16:58:15,518 - main.py:24 - main - DEBUG - test mod.testLogger() 2013-11-28 16:58:15,518 - mod.py:14 - main.mod - DEBUG - this is main.mod testLogging() 2013-11-28 16:58:15,519 - mod.py:15 - main.mod - INFO - start import module 'submod'... 2013-11-28 16:58:15,519 - submod.py:11 - main.mod.submod - INFO - info of main.mod.submod 2013-11-28 16:58:15,519 - submod.py:14 - main.mod.submod - DEBUG - this is main.mod.submod testLogging()
tst.log中沒有root logger輸出的信息,由於logging.conf中配置了只有main logger及其子logger使用RotatingFileHandler,而root logger是輸出到標準輸出。
一、從一個使用場景開始
開發一個日誌系統, 既要把日誌輸出到控制檯, 還要寫入日誌文件
#!/usr/bin/python # ithomer in 2013 import logging # create instance of logging logger = logging.getLogger('mylogger') logger.setLevel(logging.DEBUG) # file handler fh = logging.FileHandler('test.log') fh.setLevel(logging.DEBUG) # console handler ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) # formatter fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(fmt) ch.setFormatter(fmt) # add formatter to handler logger.addHandler(fh) logger.addHandler(ch) # print a log logger.info('ithomer')
運行後, 在控制檯和日誌文件(test.log)都有一條日誌:
2013-11-27 23:45:20,329 - mylogger - INFO - ithomer
二、logging模塊的API
結合上面的例子,咱們說下幾個最常使用的API
logging.getLogger([name])
返回一個logger實例,若是沒有指定name,返回root logger。只要name相同,返回的logger實例都是同一個並且只有一個,即name和logger實例是一一對應的。這意味着,無需把logger實例在各個模塊中傳遞。只要知道name,就能獲得同一個logger實例
Logger.setLevel(lvl)
設置logger的level, level有如下幾個級別:
>>> import logging
>>> print logging.NOTSET
0
>>> print logging.DEBUG
10
>>> print logging.INFO
20
>>> print logging.WARNING
30
>>> print logging.ERROR
40
>>> print logging.CRITICAL
50
NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICAL
若是把looger的級別設置爲INFO, 那麼小於INFO級別的日誌都不輸出, 大於等於INFO級別的日誌都輸出
logger.debug("foobar") # 不輸出
logger.info("foobar") # 輸出
logger.warning("foobar") # 輸出
logger.error("foobar") # 輸出
logger.critical("foobar") # 輸出
Logger.addHandler(hdlr)
logger能夠僱傭handler來幫它處理日誌, handler主要有如下幾種:
StreamHandler # 輸出到控制檯
FileHandler # 輸出到文件
handler還能夠設置本身的level以及輸出格式。
logging.basicConfig([**kwargs])
這個函數用來配置root logger, 爲root logger建立一個StreamHandler,設置默認的格式。
這些函數: logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical() 若是調用的時候發現root logger沒有任何 handler, 會自動調 basicConfig添加一個handler, 若是root logger已有handler, 這個函數不作任何事情,使用basicConfig來配置root logger的輸出格式和level:
import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
三、root logger與logger 父子關係
前面屢次提到root logger, 實際上logger實例之間還有父子關係, root logger就是處於最頂層的logger, 它是全部logger的祖先。以下圖:
root logger是默認的logger,若是不建立logger實例, 直接調用logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical()這些函數,那麼使用的logger就是 root logger, 它能夠自動建立,也是單實例的。
如何獲得root logger
經過logging.getLogger()或者logging.getLogger("")獲得root logger實例。
默認的level
root logger默認的level是logging.WARNING
如何表示父子關係
logger的name的命名方式能夠表示logger之間的父子關係. 好比:
parent_logger = logging.getLogger('foo')
child_logger = logging.getLogger('foo.bar')
什麼是effective level
logger有一個概念,effective level。 若是一個logger沒有顯示地設置level,那麼它就用父親的level。若是父親也沒有顯示地設置level, 就用父親的父親的level,以此推....
最後到達root logger,必定設置過level。默認爲logging.WARNING,child loggers獲得消息後,既把消息分發給它的handler處理,也會傳遞給全部祖先logger處理,來看一個例子:
#/usr/bin/python # ithomer in 2013 import logging # root logger root = logging.getLogger() # root ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) root.addHandler(ch) # logging as parent p = logging.getLogger('foo') p.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(message)s') ch.setFormatter(formatter) p.addHandler(ch) # logging as child c = logging.getLogger('foo.bar') c.debug('foo')
運行結果:
2013-11-27 22:40:56,699 - foo
2013-11-27 22:40:56,699 - DEBUG - foo
可見, 孩子logger沒有任何handler,因此對消息不作處理,可是它把消息轉發給了它的父親以及root logger,最後輸出兩條日誌,分別是p和root 輸出的logging。