【博文推薦 】 python Howto之logging模塊
本博文出自51CTO博客無名博主,有任何問題請進(jìn)入博主頁面互動(dòng)討論! 博文地址:http://xdzw608.blog.51cto.com/4812210/1608718 |
本文來源于對py2.7.9 docs中howto-logging部分加之源代碼的理解。官方文檔鏈接如下,我用的是下載的pdf版本,應(yīng)該是一致的:https://docs.python.org/2/howto/logging.html
我們不按照文檔上由淺入深的講解順序,因?yàn)榫瓦@么點(diǎn)東西不至于有“入”這個(gè)動(dòng)作。
使用logging模塊記錄日志涉及四個(gè)主要類,使用官方文檔中的概括最為合適:
logger提供了應(yīng)用程序可以直接使用的接口;
handler將(logger創(chuàng)建的)日志記錄發(fā)送到合適的目的輸出;
filter提供了細(xì)度設(shè)備來決定輸出哪條日志記錄;
formatter決定日志記錄的最終輸出格式。
寫log的一般順序?yàn)椋?/strong>
一、創(chuàng)建logger:
我們不要通過logging.Logger來直接實(shí)例化得到logger,而是需要通過logging.getLogger("name")來生成logger對象。
不 是說我們不能實(shí)現(xiàn)Logger的實(shí)例化,而是我們期待的是同一個(gè)name得到的是同一個(gè)logger,這樣多模塊之間可以共同使用同一個(gè) logger,getLogger正是這樣的解決方案,它內(nèi)部使用loggerDict字典來維護(hù),可以保證相同的名字作為key會得到同一個(gè) logger對象。我們可以通過實(shí)例來驗(yàn)證一下:
- #test_logger1.py
- #coding:utf-8
- import logging
- print logging.getLogger("mydear")
- import test_logger2
- test_logger2.run() #調(diào)用文件2中的函數(shù),保證兩個(gè)模塊共同處于生存期
- #test_logger2.py
- #coding:utf-8
- import logging
- def run():
- print logging.getLogger("mydear")
輸出:
<logging.Logger object at 0x00000000020ECF28>
<logging.Logger object at 0x00000000020ECF28>
結(jié)果表明兩個(gè)文件中通過"mydear"調(diào)用getLogger可以保證得到的logger對象是同一個(gè)。而分別進(jìn)行Logger類的實(shí)例化則不能保證。
有了logger之后就可以配置這個(gè)logger,例如設(shè)置日志級別setLevel,綁定控制器addHandler,添加過濾器addFilter等。
配置完成后,就可以調(diào)用logger的方法寫日志了,根據(jù)5個(gè)日志級別對應(yīng)有5個(gè)日志記錄方法,分別為logger.debug,logger.info,logger.warning,logger.error,logger.critical。
二、配置Logger對象的日志級別:
logger.setLevel(logging.DEBUG) #DEBUG以上的日志級別會被此logger處理
三、創(chuàng)建handler對象
handler 負(fù)責(zé)將log分發(fā)到某個(gè)目的輸出,存在多種內(nèi)置的Handler將log分發(fā)到不同的目的地,或是控制臺,或是文件,或是某種形式的stream,或是 socket等。一個(gè)logger可以綁定多個(gè)handler,例如,一條日志可以同時(shí)輸出到控制臺和文件中。
以FileHandler和StreamHandler為例:
logfile= logging.FileHandler("./log.txt") #創(chuàng)建一個(gè)handler,用于將日志輸出到文件中
console = logging.StreamHandler() #創(chuàng)建另一個(gè)handler,將日志導(dǎo)向流
handler對象也需要設(shè)置日志級別,由于一個(gè)logger可以包含多個(gè)handler,所以每個(gè)handler設(shè)置日志級別是有必要的。用通俗的話 講,比如,我們需要處理debug以上級別的消息,所以我們將logger的日志級別定為DEBUG;然后我們想把error以上的日志輸出到控制臺,而 DEBUG以上的消息輸出到文件中,這種分流就需要兩個(gè)Handler來控制。
logfile.setLevel(logging.DEBUG)
console.setLevel(logging.ERROR)
除了對handler對象設(shè)置日志級別外,還可以指定formatter,即日志的輸出格式。對handler對象設(shè)置日志格式,說明了可以將一條記錄以不同的格式輸出到控制臺,文件或其他目的地。
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logfile.setFormatter(formatter) #設(shè)置handler的日志輸出格式
formatter創(chuàng)建時(shí)使用的關(guān)鍵字,***會以列表的形式展現(xiàn),這不是重點(diǎn)。
四、綁定handler到logger中
至此handlers和logger已經(jīng)準(zhǔn)備好了,下面我們就將handlers綁定到logger上,一個(gè)logger對象可以綁定多個(gè)handler。
logger.addHandler(logfile) #logger是通過getLogger得到的Logger對象
logger.addHandler(console)
五、使用logger真正寫日志
logger.debug("some debug message.")
logger.info("some info message.")
看上去,中間步驟(創(chuàng)建handler,設(shè)置日志級別,設(shè)置輸出格式等)更像是配置Logger,一旦配置完成則直接調(diào)用寫日志的接口即可,稍后這些日志將按照先前的配置輸出。
嗚呼,好多內(nèi)容啊,來點(diǎn)簡單的吧.
下面的代碼,是最簡單的。導(dǎo)入logging之后就進(jìn)行了寫日志操作:
- #coding:utf-8
- import logging
- logging.debug("debug mes")
- logging.info("info mes")
- logging.warning("warn mes")
控制臺輸出如下:
WARNING:root:warn mes
咦?發(fā)生了什么情況,為什么只輸出了warning?handler、logger、formatter去哪兒了?
-_-!說好的最簡單的呢?為了讓自己講信用,我盡可能把它解釋成“最簡單的”。
#p#
知識點(diǎn)1:logger間存在繼承關(guān)系
logger 通過名字來決定繼承關(guān)系,如果一個(gè)logger的名字是"mydest",另一個(gè)logger的名字是"mydest.dest1" (getLogger("mydest.dest1")),那么就稱后者是前者的子logger,會繼承前者的配置。上面的代碼沒有指定logger,直接調(diào)用logging.debug等方法時(shí),會使用所有l(wèi)ogger的祖先類RootLogger。
從上面的代碼運(yùn)行結(jié)果可以猜測出,該RootLogger設(shè)置的日志級別是logging.WARN,輸出目的地是標(biāo)準(zhǔn)流。從源碼可以更清楚的看出來:
- root = RootLogger(WARNING) #設(shè)置WARNING的級別
至于rootLogger的輸出目的地的配置,我們跟蹤logging.debug的源代碼來看一下:
- def debug(msg, *args, **kwargs):
- """
- Log a message with severity 'DEBUG' on the root logger.
- """
- if len(root.handlers) == 0:
- basicConfig()
- root.debug(msg, *args, **kwargs)
大約可以看到,如果rootLogger沒有配置handler,就會不帶參數(shù)運(yùn)行basicConfig函數(shù)(*請看知識點(diǎn)2),我們看一下basicConfig的源代碼:
- def basicConfig(**kwargs):
- _acquireLock()
- try:
- if len(root.handlers) == 0:
- filename = kwargs.get("filename")
- if filename:
- mode = kwargs.get("filemode", 'a')
- hdlr = FileHandler(filename, mode)
- else:
- stream = kwargs.get("stream")
- hdlr = StreamHandler(stream)
- fs = kwargs.get("format", BASIC_FORMAT)
- dfs = kwargs.get("datefmt", None)
- fmt = Formatter(fs, dfs)
- hdlr.setFormatter(fmt)
- root.addHandler(hdlr)
- level = kwargs.get("level")
- if level is not None:
- root.setLevel(level)
- finally:
- _releaseLock()
因 為參數(shù)為空,所以我們就看出了,該rootLoger使用了不帶參數(shù)的StreamHandler,也可以看到諸如format之類的默認(rèn)配置。之后我們 跟蹤StreamHandler(因?yàn)槲覀兿肟吹饺罩据敵瞿康牡氐呐渲?,而handler就是控制日志流向的,所以我們要跟蹤它)的源代碼:
- class StreamHandler(Handler):
- """
- A handler class which writes logging records, appropriately formatted,
- to a stream. Note that this class does not close the stream, as
- sys.stdout or sys.stderr may be used.
- """
- def __init__(self, stream=None):
- """
- Initialize the handler.
- If stream is not specified, sys.stderr is used.
- """
- Handler.__init__(self)
- if stream is None:
- stream = sys.stderr ####
- self.stream = stream
不帶參數(shù)的StreamHandler將會把日志流定位到sys.stderr流,標(biāo)準(zhǔn)錯(cuò)誤流同樣會輸出到控制臺
知識點(diǎn)2:basicConfig函數(shù)用來配置RootLogger
basicConfig函數(shù)僅用來配置RootLogger,rootLogger是所有Logger的祖先Logger,所以其他一切Logger會繼承該Logger的配置。
從上面的basicConfig源碼看,它可以有六個(gè)關(guān)鍵字參數(shù),分別為:
filename:執(zhí)行使用該文件名為rootLogger創(chuàng)建FileHandler,而不是StreamHandler
filemode:指定文件打開方式,默認(rèn)是"a"
stream:指定一個(gè)流來初始化StreamHandler。此參數(shù)不能和filename共存,如果同時(shí)提供了這兩個(gè)參數(shù),則stream參數(shù)被忽略
format:為rootLogger的handler指定輸出格式
datefmt:指定輸出的日期時(shí)間格式
level:設(shè)置rootLogger的日志級別
使用樣例:
- logging.basicConfig(
- filename = './log.txt',
- filemode = 'a',
- #stream = sys.stdout,
- format = '%(levelname)s:%(message)s',
- datefmt = '%m/%d/%Y %I:%M:%S',
- level = logging.DEBUG
知識點(diǎn)3 通過示例詳細(xì)討論Logger配置的繼承關(guān)系
首先準(zhǔn)備下繼承條件:log2繼承自log1,logger的名稱可以隨意,要注意‘.’表示的繼承關(guān)系。
- #coding:utf-8
- import logging
- log1 = logging.getLogger("mydear")
- log1.setLevel(logging.WARNING)
- log1.addHandler(StreamHandler())
- log2 = logging.getLogger("mydear.app")
- log2.error("display")
- log2.info("not display")
level的繼承
原則:子logger寫日志時(shí),優(yōu)先使用本身設(shè)置了的level;如果沒有設(shè)置,則逐層向上級父logger查詢,直到查詢到為止。最極端的情況是,使用rootLogger的默認(rèn)日志級別logging.WARNING。
從源代碼中看更為清晰, 感謝python的所見即所得:
- def getEffectiveLevel(self):
- """
- Get the effective level for this logger.
- Loop through this logger and its parents in the logger hierarchy,
- looking for a non-zero logging level. Return the first one found.
- """
- logger = self
- while logger:
- if logger.level:
- return logger.level
- logger = logger.parent
- return NOTSET
handler的繼承
原則:先將日志對象傳遞給子logger的所有handler處理,處理完畢后,如果該子logger的propagate屬性沒有設(shè)置為0,則將日志對象向上傳遞給***個(gè)父Logger,該父logger的所有handler處理完畢后,如果它的propagate也沒有設(shè)置為0,則繼續(xù)向上層傳遞,以此類推。最終的狀態(tài),要么遇到一個(gè)Logger,它的propagate屬性設(shè)置為了0;要么一直傳遞直到rootLogger處理完畢。
在上面實(shí)例代碼的基礎(chǔ)上,我們再添加一句代碼,即:
- #coding:utf-8
- import logging
- log1 = logging.getLogger("mydear")
- log1.setLevel(logging.WARNING)
- log1.addHandler(StreamHandler())
- log2 = logging.getLogger("mydear.app")
- log2.error("display")
- log2.info("not display")
- print log2.handlers #打印log2綁定的handler
輸出如下:
display
[]
說好的繼承,但是子logger竟然沒有綁定父類的handler,what's wrong?
看到下面調(diào)用handler的源代碼,就真相大白了??梢岳斫獬桑@不是真正的(類)繼承,只是"行為上的繼承":
- def callHandlers(self, record):
- """
- Pass a record to all relevant handlers.
- Loop through all handlers for this logger and its parents in the
- logger hierarchy. If no handler was found, output a one-off error
- message to sys.stderr. Stop searching up the hierarchy whenever a
- logger with the "propagate" attribute set to zero is found - that
- will be the last logger whose handlers are called.
- """
- c = self
- found = 0
- while c:
- for hdlr in c.handlers: #首先遍歷子logger的所有handler
- found = found + 1
- if record.levelno >= hdlr.level:
- hdlr.handle(record)
- if not c.propagate: #如果logger的propagate屬性設(shè)置為0,停止
- c = None#break out
- else: #否則使用直接父logger
- c = c.parent
- ...
額,最簡單的樣例牽引出來這么多后臺的邏輯,不過我們懂一下也是有好處的。
下面,我們將一些零碎的不是很重要的東西羅列一下,這篇就結(jié)束了。
1.幾種LogLevel是全局變量,以整數(shù)形式表示,也可以但是不推薦自定義日志級別,如果需要將level設(shè)置為用戶配置,則獲取level和檢查level的一般代碼是:
- #假設(shè)loglevel代表用戶設(shè)置的level內(nèi)容
- numeric_level = getattr(logging, loglevel.upper(), None)
- if not isinstance(numeric_level, int):
- raise ValueError('Invalid log level: %s' % loglevel)
- logging.basicConfig(level=numeric_level, ...)
2.format格式,用于創(chuàng)建formatter對象,或者basicConfig中,就不翻譯了
%(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)
%(lineno)d Source line number where the logging call was issued
(if available)
%(funcName)s Function name
%(created)f Time when the LogRecord was created (time.time()
return value)
%(asctime)s Textual time when the LogRecord was created
%(msecs)d Millisecond portion of the creation time
%(relativeCreated)d Time in milliseconds when the LogRecord was created,
relative to the time the logging module was loaded
(typically at application startup time)
%(thread)d Thread ID (if available)
%(threadName)s Thread name (if available)
%(process)d Process ID (if available)
%(message)s The result of record.getMessage(), computed just as
the record is emitted
3.寫日志接口
logging.warn("%s am a hero", "I") #1 %格式以參數(shù)形式提供實(shí)參
logging.warn("%s am a hero" % ("I",)) #2 直接提供字符串,也可以使用format,template
logging.warn("%(name)s am a hero", {'name':"I"}) #關(guān)鍵字參數(shù)
logging.warn("%(name)s am a hero" % {'name':"I"}) #甚至這樣也可以
logging.warn("%(name)s am a hero, %(value)s" % {'name':"I", 'value':'Yes'}) #原來%也能解析關(guān)鍵字參數(shù),不一定非是元組
如果關(guān)鍵字和位置參數(shù)混用呢,%應(yīng)該不會有什么作為了,***也就能這樣:
logging.warn("%(name)s am a hero, %()s" % {'name':"I" ,'': 'Yes'})#也是字典格式化的原理
4.配置logging:
上 面已經(jīng)講了如果配置handler,綁定到logger。如果需要一個(gè)稍微龐大的日志系統(tǒng),可以想象,我們會使用好多的 addHandler,SetFormatter之類的,有夠煩了。幸好,logging模塊提供了兩種額外配置方法,不需要寫眾多代碼,直接從配置結(jié)構(gòu) 中獲悉我們的配置意圖
方式一:使用配置文件
- import logging
- import logging.config
- logging.config.fileConfig('logging.conf')
- # create logger
- logger = logging.getLogger('simpleExample')
- # 'application' code
- logger.debug('debug message')
- logger.info('info message')
- logger.warn('warn message')
- logger.error('error message')
- logger.critical('critical message')
- #配置文件logging.conf的內(nèi)容
- [loggers]
- keys=root,simpleExample
- [handlers]
- keys=consoleHandler
- [formatters]
- keys=simpleFormatter
- [logger_root]
- level=DEBUG
- handlers=consoleHandler
- [logger_simpleExample]
- level=DEBUG
- handlers=consoleHandler
- qualname=simpleExample
- propagate=0
- [handler_consoleHandler]
- class=StreamHandler
- level=DEBUG
- formatter=simpleFormatter
- args=(sys.stdout,)
- [formatter_simpleFormatter]
- format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
- datefmt=
方式二:使用字典
請參閱python2.7.9 Library文檔,鏈接:
5.眾多的handler滿足不同的輸出需要
StreamHandler,F(xiàn)ileHandler,NullHandler,RotatingFileHandler,TimedRotatingFileHandler,SocketHandler,DatagramHandler,SMTPHandler,SysLogHandler,NTEventLogHandler,MemoryHandler,HTTPHandler,WatchedFileHandler,
其中前三種在logging模塊中給出,其他的在logging.handlers模塊中給出。