Python Howto 之 logging 模組

發表於2015-09-18

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

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

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

logger提供了應用程式可以直接使用的介面;

handler將(logger建立的)日誌記錄傳送到合適的目的輸出;

filter提供了細度裝置來決定輸出哪條日誌記錄;

formatter決定日誌記錄的最終輸出格式。

寫log的一般順序為:

一、建立logger:

我們不要通過logging.Logger來直接例項化得到logger,而是需要通過logging.getLogger(“name”)來生成logger物件。

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

輸出:

<logging.Logger object at 0x00000000020ECF28>
<logging.Logger object at 0x00000000020ECF28>

結果表明兩個檔案中通過”mydear”呼叫getLogger可以保證得到的logger物件是同一個。而分別進行Logger類的例項化則不能保證。

有了logger之後就可以配置這個logger,例如設定日誌級別setLevel,繫結控制器addHandler,新增過濾器addFilter等。

配置完成後,就可以呼叫logger的方法寫日誌了,根據5個日誌級別對應有5個日誌記錄方法,分別為logger.debug,logger.info,logger.warning,logger.error,logger.critical。

二、配置Logger物件的日誌級別:

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

三、建立handler物件

handler負責將log分發到某個目的輸出,存在多種內建的Handler將log分發到不同的目的地,或是控制檯,或是檔案,或是某種形式的stream,或是socket等。一個logger可以繫結多個handler,例如,一條日誌可以同時輸出到控制檯和檔案中。

以FileHandler和StreamHandler為例:

logfile= logging.FileHandler(“./log.txt”)  #建立一個handler,用於將日誌輸出到檔案中

console = logging.StreamHandler()         #建立另一個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建立時使用的關鍵字,最後會以列表的形式展現,這不是重點。

四、繫結handler到logger中

至此handlers和logger已經準備好了,下面我們就將handlers繫結到logger上,一個logger物件可以繫結多個handler。

logger.addHandler(logfile)  #logger是通過getLogger得到的Logger物件

logger.addHandler(console)

五、使用logger真正寫日誌

logger.debug(“some debug message.”)

logger.info(“some info message.”)

看上去,中間步驟(建立handler,設定日誌級別,設定輸出格式等)更像是配置Logger,一旦配置完成則直接呼叫寫日誌的介面即可,稍後這些日誌將按照先前的配置輸出。

嗚呼,好多內容啊,來點簡單的吧.

下面的程式碼,是最簡單的。匯入logging之後就進行了寫日誌操作:

控制檯輸出如下:

WARNING:root:warn mes

咦?發生了什麼情況,為什麼只輸出了warning?handler、logger、formatter去哪兒了?

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

知識點1:logger間存在繼承關係

logger通過名字來決定繼承關係,如果一個logger的名字是”mydest”,另一個logger的名字是”mydest.dest1″(getLogger(“mydest.dest1”)),那麼就稱後者是前者的子logger,會繼承前者的配置。上面的程式碼沒有指定logger,直接呼叫logging.debug等方法時,會使用所有logger的祖先類RootLogger

從上面的程式碼執行結果可以猜測出,該RootLogger設定的日誌級別是logging.WARN,輸出目的地是標準流。從原始碼可以更清楚的看出來:

至於rootLogger的輸出目的地的配置,我們跟蹤logging.debug的原始碼來看一下:

大約可以看到,如果rootLogger沒有配置handler,就會不帶引數執行basicConfig函式(*請看知識點2),我們看一下basicConfig的原始碼:

因為引數為空,所以我們就看出了,該rootLoger使用了不帶引數的StreamHandler,也可以看到諸如format之類的預設配置。之後我們跟蹤StreamHandler(因為我們想看到日誌輸出目的地的配置,而handler就是控制日誌流向的,所以我們要跟蹤它)的原始碼:

不帶引數的StreamHandler將會把日誌流定位到sys.stderr流,標準錯誤流同樣會輸出到控制檯

知識點2:basicConfig函式用來配置RootLogger

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

從上面的basicConfig原始碼看,它可以有六個關鍵字引數,分別為:

filename:執行使用該檔名為rootLogger建立FileHandler,而不是StreamHandler

filemode:指定檔案開啟方式,預設是”a”

stream:指定一個流來初始化StreamHandler。此引數不能和filename共存,如果同時提供了這兩個引數,則stream引數被忽略

format:為rootLogger的handler指定輸出格式

datefmt:指定輸出的日期時間格式

level:設定rootLogger的日誌級別

使用樣例:

知識點3 通過示例詳細討論Logger配置的繼承關係

首先準備下繼承條件:log2繼承自log1,logger的名稱可以隨意,要注意‘.’表示的繼承關係。

level的繼承

原則:子logger寫日誌時,優先使用本身設定了的level;如果沒有設定,則逐層向上級父logger查詢,直到查詢到為止。最極端的情況是,使用rootLogger的預設日誌級別logging.WARNING。

從原始碼中看更為清晰, 感謝python的所見即所得:

handler的繼承

原則:先將日誌物件傳遞給子logger的所有handler處理,處理完畢後,如果該子logger的propagate屬性沒有設定為0,則將日誌物件向上傳遞給第一個父Logger,該父logger的所有handler處理完畢後,如果它的propagate也沒有設定為0,則繼續向上層傳遞,以此類推。最終的狀態,要麼遇到一個Logger,它的propagate屬性設定為了0;要麼一直傳遞直到rootLogger處理完畢。

在上面例項程式碼的基礎上,我們再新增一句程式碼,即:

輸出如下:

display
[]

說好的繼承,但是子logger竟然沒有繫結父類的handler,what’s wrong?

看到下面呼叫handler的原始碼,就真相大白了。可以理解成,這不是真正的(類)繼承,只是”行為上的繼承”:

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

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

1.幾種LogLevel是全域性變數,以整數形式表示,也可以但是不推薦自定義日誌級別,如果需要將level設定為使用者配置,則獲取level和檢查level的一般程式碼是:

2.format格式,用於建立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 %格式以引數形式提供實參

logging.warn(“%s am a hero” % (“I”,)) #2 直接提供字串,也可以使用format,template

logging.warn(“%(name)s am a hero”, {‘name’:”I”})  #關鍵字引數

logging.warn(“%(name)s am a hero” % {‘name’:”I”}) #甚至這樣也可以

logging.warn(“%(name)s am a hero, %(value)s” % {‘name’:”I”, ‘value’:’Yes’}) #原來%也能解析關鍵字引數,不一定非是元組

如果關鍵字和位置引數混用呢,%應該不會有什麼作為了,最強也就能這樣:

logging.warn(“%(name)s am a hero, %()s” % {‘name’:”I” ,”: ‘Yes’})#也是字典格式化的原理

4.配置logging:

上面已經講了如果配置handler,繫結到logger。如果需要一個稍微龐大的日誌系統,可以想象,我們會使用好多的addHandler,SetFormatter之類的,有夠煩了。幸好,logging模組提供了兩種額外配置方法,不需要寫眾多程式碼,直接從配置結構中獲悉我們的配置意圖

方式一:使用配置檔案

方式二:使用字典

請參閱python2.7.9 Library文件,連結:

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

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

StreamHandler,FileHandler,NullHandler,RotatingFileHandler,TimedRotatingFileHandler,SocketHandler,DatagramHandler,SMTPHandler,SysLogHandler,NTEventLogHandler,MemoryHandler,HTTPHandler,WatchedFileHandler,

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

相關文章