鍍金池/ 教程/ Python/ python Howto 之 logging 模塊
通過 memcached 實現(xiàn)領(lǐng)號排隊功能及 python 隊列實例
利用 pypy 提高 python 腳本的執(zhí)行速度及測試性能
Python FAQ3-python 中 的原始(raw)字符串
Mongodb 千萬級數(shù)據(jù)在 python 下的綜合壓力測試及應用探討
Parallel Python 實現(xiàn)程序的并行多 cpu 多核利用【pp 模塊】
python simplejson 模塊淺談
服務端 socket 開發(fā)之多線程和 gevent 框架并發(fā)測試[python 語言]
python Howto 之 logging 模塊
python 之 MySQLdb 庫的使用
關(guān)于 python 調(diào)用 zabbix api 接口的自動化實例 [結(jié)合 saltstack]
python 之利用 PIL 庫實現(xiàn)頁面的圖片驗證碼及縮略圖
Python 通過 amqp 消息隊列協(xié)議中的 Qpid 實現(xiàn)數(shù)據(jù)通信
python 中用 string.maketrans 和 translate 巧妙替換字符串
python linecache 模塊讀取文件用法詳解
Python 批量更新 nginx 配置文件
python 計算文件的行數(shù)和讀取某一行內(nèi)容的實現(xiàn)方法
python+Django 實現(xiàn) Nagios 自動化添加監(jiān)控項目
多套方案來提高 python web 框架的并發(fā)處理能力
python 寫報警程序中的聲音實現(xiàn) winsound
python 調(diào)用 zabbix 的 api 接口添加主機、查詢組、主機、模板
對 Python-memcache 分布式散列和調(diào)用的實現(xiàn)
使用 python 構(gòu)建基于 hadoop 的 mapreduce 日志分析平臺
一個腳本講述 python 語言的基礎(chǔ)規(guī)范,適合初學者
Python 編寫的 socket 服務器和客戶端
如何將 Mac OS X10.9 下的 Python2.7 升級到最新的 Python3.3
python 監(jiān)控文件或目錄變化
報警監(jiān)控平臺擴展功能 url 回調(diào)的設計及應用 [python 語言]
Python 處理 cassandra 升級后的回滾腳本
python 實現(xiàn) select 和 epoll 模型 socket 網(wǎng)絡編程
關(guān)于 B+tree (附 python 模擬代碼)
通過 python 和 websocket 構(gòu)建實時通信系統(tǒng)[擴展 saltstack 監(jiān)控]

python Howto 之 logging 模塊

本文來源于對 py2.7.9 docs 中 howto-logging 部分加之源代碼的理解。官方文檔鏈接如下,我用的是下載的 pdf 版本,應該是一致的:https://docs.python.org/2/howto/logging.html

我們不按照文檔上由淺入深的講解順序,因為就這么點東西不至于有“入”這個動作。

使用 logging 模塊記錄日志涉及四個主要類,使用官方文檔中的概括最為合適:

logger提供了應用程序可以直接使用的接口;
handler將(logger創(chuàng)建的)日志記錄發(fā)送到合適的目的輸出;
filter提供了細度設備來決定輸出哪條日志記錄;
formatter決定日志記錄的最終輸出格式。

寫 log 的一般順序為:

一、創(chuàng)建logger:

我們不要通過 logging.Logger 來直接實例化得到 logger,而是需要通過 logging.getLogger("name")來生成 logger 對象。

不是說我們不能實現(xiàn) Logger 的實例化,而是我們期待的是同一個 name 得到的是同一個 logger,這樣多模塊之間可以共同使用同一個 logger,getLogger 正是這樣的解決方案,它內(nèi)部使用 loggerDict 字典來維護,可以保證相同的名字作為 key 會得到同一個 logger 對象。我們可以通過實例來驗證一下:

#test_logger1.py
#coding:utf-8

import logging
print logging.getLogger("mydear")    
import test_logger2
test_logger2.run()           #調(diào)用文件 2 中的函數(shù),保證兩個模塊共同處于生存期

#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é)果表明兩個文件中通過"mydear"調(diào)用 getLogger 可以保證得到的 logger 對象是同一個。而分別進行 Logger 類的實例化則不能保證。

有了 logger 之后就可以配置這個 logger,例如設置日志級別 setLevel,綁定控制器 addHandler,添加過濾器 addFilter 等。

配置完成后,就可以調(diào)用 logger 的方法寫日志了,根據(jù) 5 個日志級別對應有 5 個日志記錄方法,分別為logger.debug,logger.info,logger.warning,logger.error,logger.critical。

二、配置 Logger 對象的日志級別:

logger.setLevel(logging.DEBUG) #DEBUG 以上的日志級別會被此 logger 處理

三、創(chuàng)建 handler 對象

handler 負責將 log 分發(fā)到某個目的輸出,存在多種內(nèi)置的 Handler 將 log 分發(fā)到不同的目的地,或是控制臺,或是文件,或是某種形式的 stream,或是 socket 等。一個 logger 可以綁定多個 handler,例如,一條日志可以同時輸出到控制臺和文件中。

以 FileHandler 和 StreamHandler 為例:

logfile= logging.FileHandler("./log.txt") #創(chuàng)建一個handler,用于將日志輸出到文件中
console = logging.StreamHandler() #創(chuàng)建另一個handler,將日志導向流

handler 對象也需要設置日志級別,由于一個 logger 可以包含多個 handler,所以每個 handler 設置日志級別是有必要的。用通俗的話講,比如,我們需要處理 debug 以上級別的消息,所以我們將 logger 的日志級別定為 DEBUG;然后我們想把 error 以上的日志輸出到控制臺,而 DEBUG 以上的消息輸出到文件中,這種分流就需要兩個 Handler 來控制。

logfile.setLevel(logging.DEBUG)
console.setLevel(logging.ERROR)

除了對 handler 對象設置日志級別外,還可以指定 formatter,即日志的輸出格式。對 handler 對象設置日志格式,說明了可以將一條記錄以不同的格式輸出到控制臺,文件或其他目的地。

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

logfile.setFormatter(formatter) #設置 handler 的日志輸出格式

formatter 創(chuàng)建時使用的關(guān)鍵字,最后會以列表的形式展現(xiàn),這不是重點。

四、綁定 handler 到 logger 中

至此 handlers 和 logger 已經(jīng)準備好了,下面我們就將 handlers 綁定到 logger 上,一個 logger 對象可以綁定多個 handler。

    logger.addHandler(logfile)  #logger 是通過 getLogger 得到的 Logger 對象
    logger.addHandler(console)

五、使用 logger 真正寫日志

    logger.debug("some debug message.")
    logger.info("some info message.")

看上去,中間步驟(創(chuàng)建 handler,設置日志級別,設置輸出格式等)更像是配置 Logger,一旦配置完成則直接調(diào)用寫日志的接口即可,稍后這些日志將按照先前的配置輸出。

嗚呼,好多內(nèi)容啊,來點簡單的吧.

下面的代碼,是最簡單的。導入 logging 之后就進行了寫日志操作:

#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 去哪兒了?

-_-!說好的最簡單的呢?為了讓自己講信用,我盡可能把它解釋成“最簡單的”。

知識點 1:logger 間存在繼承關(guān)系

logger 通過名字來決定繼承關(guān)系,如果一個 logger 的名字是 "mydest",另一個 logger 的名字是"mydest.dest1"(getLogger("mydest.dest1")),那么就稱后者是前者的子 logger,會繼承前者的配置。上面的代碼沒有指定 logger,直接調(diào)用 logging.debug 等方法時,會使用所有 logger 的祖先類 RootLogger。

從上面的代碼運行結(jié)果可以猜測出,該 RootLogger 設置的日志級別是 logging.WARN,輸出目的地是標準流。從源碼可以更清楚的看出來:

root = RootLogger(WARNING)  #設置 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ù)運行 basicConfig 函數(shù)(*請看知識點 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 之類的默認配置。之后我們跟蹤 StreamHandler(因為我們想看到日志輸出目的地的配置,而 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流,標準錯誤流同樣會輸出到控制臺

知識點 2:basicConfig 函數(shù)用來配置 RootLogger

basicConfig 函數(shù)僅用來配置 RootLogger,rootLogger 是所有 Logger 的祖先 Logger,所以其他一切 Logger 會繼承該 Logger 的配置。

從上面的 basicConfig 源碼看,它可以有六個關(guān)鍵字參數(shù),分別為:

filename:執(zhí)行使用該文件名為 rootLogger 創(chuàng)建 FileHandler,而不是 StreamHandler
filemode:指定文件打開方式,默認是"a"
stream:指定一個流來初始化 StreamHandler。此參數(shù)不能和 filename 共存,如果同時提供了這兩個參數(shù),則 stream 參數(shù)被忽略
format:為 rootLogger 的 handler 指定輸出格式
datefmt:指定輸出的日期時間格式
level:設置 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
                    )

知識點 3 通過示例詳細討論 Logger 配置的繼承關(guā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 寫日志時,優(yōu)先使用本身設置了的 level;如果沒有設置,則逐層向上級父 logger 查詢,直到查詢到為止。最極端的情況是,使用 rootLogger 的默認日志級別 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 屬性沒有設置為 0,則將日志對象向上傳遞給第一個父 Logger,該父 logger 的所有 handler 處理完畢后,如果它的 propagate 也沒有設置為 0,則繼續(xù)向上層傳遞,以此類推。最終的狀態(tài),要么遇到一個 Logger,它的 propagate 屬性設置為了 0;要么一直傳遞直到 rootLogger 處理完畢。

在上面實例代碼的基礎(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 屬性設置為 0,停止
                c = None    #break out 
            else:                           #否則使用直接父 logger
                c = c.parent
        ...

額,最簡單的樣例牽引出來這么多后臺的邏輯,不過我們懂一下也是有好處的。

下面,我們將一些零碎的不是很重要的東西羅列一下,這篇就結(jié)束了。

  1. 幾種 LogLevel 是全局變量,以整數(shù)形式表示,也可以但是不推薦自定義日志級別,如果需要將 level 設置為用戶配置,則獲取 level 和檢查 level 的一般代碼是:
#假設 loglevel 代表用戶設置的 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, ...)
  1. 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
  1. 寫日志接口
    logging.warn("%s am a hero", "I")   #1 %格式以參數(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ù)混用呢,%應該不會有什么作為了,最強也就能這樣:
    logging.warn("%(name)s am a hero, %()s" % {'name':"I" ,'': 'Yes'})#也是字典格式化的原理
  1. 配置 logging:

上面已經(jīng)講了如果配置 handler,綁定到 logger。如果需要一個稍微龐大的日志系統(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 文檔,鏈接:

https://docs.python.org/2/library/logging.config.html?highlight=dictconfig#configuration-dictionary-schema

  1. 眾多的 handler 滿足不同的輸出需要

StreamHandler,F(xiàn)ileHandler,NullHandler,RotatingFileHandler,TimedRotatingFileHandler,SocketHandler,DatagramHandler,SMTPHandler,SysLogHandler,NTEventLogHandler,MemoryHandler,HTTPHandler,WatchedFileHandler,

其中前三種在 logging 模塊中給出,其他的在 logging.handlers 模塊中給出。