日志常用指引?

作者

Vinay Sajip <vinay_sajip at red-dove dot com>

日志基礎(chǔ)教程?

日志是對(duì)軟件執(zhí)行時(shí)所發(fā)生事件的一種追蹤方式。軟件開發(fā)人員對(duì)他們的代碼添加日志調(diào)用,借此來指示某事件的發(fā)生。一個(gè)事件通過一些包含變量數(shù)據(jù)的描述信息來描述(比如:每個(gè)事件發(fā)生時(shí)的數(shù)據(jù)都是不同的)。開發(fā)者還會(huì)區(qū)分事件的重要性,重要性也被稱為 等級(jí)嚴(yán)重性。

什么時(shí)候使用日志?

對(duì)于簡單的日志使用來說日志功能提供了一系列便利的函數(shù)。它們是 debug(),info()warning(),error()critical()。想要決定何時(shí)使用日志,請(qǐng)看下表,其中顯示了對(duì)于每個(gè)通用任務(wù)集合來說最好的工具。

你想要執(zhí)行的任務(wù)

此任務(wù)最好的工具

對(duì)于命令行或程序的應(yīng)用,結(jié)果顯示在控制臺(tái)。

print()

在對(duì)程序的普通操作發(fā)生時(shí)提交事件報(bào)告(比如:狀態(tài)監(jiān)控和錯(cuò)誤調(diào)查)

logging.info() 函數(shù)(當(dāng)有診斷目的需要詳細(xì)輸出信息時(shí)使用 logging.debug() 函數(shù))

提出一個(gè)警告信息基于一個(gè)特殊的運(yùn)行時(shí)事件

warnings.warn() 位于代碼庫中,該事件是可以避免的,需要修改客戶端應(yīng)用以消除告警

logging.warning() 不需要修改客戶端應(yīng)用,但是該事件還是需要引起關(guān)注

對(duì)一個(gè)特殊的運(yùn)行時(shí)事件報(bào)告錯(cuò)誤

引發(fā)異常

報(bào)告錯(cuò)誤而不引發(fā)異常(如在長時(shí)間運(yùn)行中的服務(wù)端進(jìn)程的錯(cuò)誤處理)

logging.error(), logging.exception()logging.critical() 分別適用于特定的錯(cuò)誤及應(yīng)用領(lǐng)域

日志功能應(yīng)以所追蹤事件級(jí)別或嚴(yán)重性而定。各級(jí)別適用性如下(以嚴(yán)重性遞增):

級(jí)別

何時(shí)使用

DEBUG

細(xì)節(jié)信息,僅當(dāng)診斷問題時(shí)適用。

INFO

確認(rèn)程序按預(yù)期運(yùn)行。

WARNING

表明有已經(jīng)或即將發(fā)生的意外(例如:磁盤空間不足)。程序仍按預(yù)期進(jìn)行。

ERROR

由于嚴(yán)重的問題,程序的某些功能已經(jīng)不能正常執(zhí)行

CRITICAL

嚴(yán)重的錯(cuò)誤,表明程序已不能繼續(xù)執(zhí)行

默認(rèn)的級(jí)別是 WARNING,意味著只會(huì)追蹤該級(jí)別及以上的事件,除非更改日志配置。

所追蹤事件可以以不同形式處理。最簡單的方式是輸出到控制臺(tái)。另一種常用的方式是寫入磁盤文件。

一個(gè)簡單的例子?

一個(gè)非常簡單的例子:

import logging
logging.warning('Watch out!')  # will print a message to the console
logging.info('I told you so')  # will not print anything

如果你在命令行中輸入這些代碼并運(yùn)行,你將會(huì)看到:

WARNING:root:Watch out!

輸出到命令行。INFO 消息并沒有出現(xiàn),因?yàn)槟J(rèn)級(jí)別是 WARNING 。打印的信息包含事件的級(jí)別以及在日志調(diào)用中的對(duì)于事件的描述,例如 “Watch out!”。暫時(shí)不用擔(dān)心 “root” 部分:之后會(huì)作出解釋。輸出格式可按需要進(jìn)行調(diào)整,格式化選項(xiàng)同樣會(huì)在之后作出解釋。

記錄日志到文件?

一種非常常見的情況是將日志事件記錄到文件,讓我們繼續(xù)往下看。請(qǐng)確認(rèn)啟動(dòng)新的Python 解釋器,不要在上一個(gè)環(huán)境中繼續(xù)操作:

import logging
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
logging.error('And non-ASCII stuff, too, like ?resund and Malm?')

在 3.9 版更改: 增加了 encoding 參數(shù)。在更早的 Python 版本中或沒有指定時(shí),編碼會(huì)用 open() 使用的默認(rèn)值。盡管在上面的例子中沒有展示,但也可以傳入一個(gè)決定如何處理編碼錯(cuò)誤的 errors 參數(shù)??墒褂玫闹岛湍J(rèn)值,請(qǐng)參照 open() 的文檔。

現(xiàn)在,如果我們打開日志文件,我們應(yīng)當(dāng)能看到日志信息:

DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too
ERROR:root:And non-ASCII stuff, too, like ?resund and Malm?

該示例同樣展示了如何設(shè)置日志追蹤級(jí)別的閾值。該示例中,由于我們?cè)O(shè)置的閾值是 DEBUG,所有信息都將被打印。

如果你想從命令行設(shè)置日志級(jí)別,例如:

--log=INFO

并且在一些 loglevel 變量中你可以獲得 --log 命令的參數(shù),你可以使用:

getattr(logging, loglevel.upper())

通過 level 參數(shù)獲得你將傳遞給 basicConfig() 的值。你需要對(duì)用戶輸入數(shù)據(jù)進(jìn)行錯(cuò)誤排查,可如下例:

# assuming loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --log=debug
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, ...)

The call to basicConfig() should come before any calls to debug(), info(), etc. Otherwise, those functions will call basicConfig() for you with the default options. As it's intended as a one-off simple configuration facility, only the first call will actually do anything: subsequent calls are effectively no-ops.

如果多次運(yùn)行上述腳本,則連續(xù)運(yùn)行的消息將追加到文件 example.log 。 如果你希望每次運(yùn)行重新開始,而不是記住先前運(yùn)行的消息,則可以通過將上例中的調(diào)用更改為來指定 filemode 參數(shù):

logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)

輸出將與之前相同,但不再追加進(jìn)日志文件,因此早期運(yùn)行的消息將丟失。

從多個(gè)模塊記錄日志?

如果你的程序包含多個(gè)模塊,這里有一個(gè)如何組織日志記錄的示例:

# myapp.py
import logging
import mylib

def main():
    logging.basicConfig(filename='myapp.log', level=logging.INFO)
    logging.info('Started')
    mylib.do_something()
    logging.info('Finished')

if __name__ == '__main__':
    main()
# mylib.py
import logging

def do_something():
    logging.info('Doing something')

如果你運(yùn)行 myapp.py ,你應(yīng)該在 myapp.log 中看到:

INFO:root:Started
INFO:root:Doing something
INFO:root:Finished

這是你期待看到的。 你可以使用 mylib.py 中的模式將此概括為多個(gè)模塊。 請(qǐng)注意,對(duì)于這種簡單的使用模式,除了查看事件描述之外,你不能通過查看日志文件來了解應(yīng)用程序中消息的 來源 。 如果要跟蹤消息的位置,則需要參考教程級(jí)別以外的文檔 —— 請(qǐng)參閱 進(jìn)階日志教程 。

記錄變量數(shù)據(jù)?

要記錄變量數(shù)據(jù),請(qǐng)使用格式字符串作為事件描述消息,并附加傳入變量數(shù)據(jù)作為參數(shù)。 例如:

import logging
logging.warning('%s before you %s', 'Look', 'leap!')

將顯示:

WARNING:root:Look before you leap!

如你所見,將可變數(shù)據(jù)合并到事件描述消息中使用舊的 %-s形式的字符串格式化。 這是為了向后兼容:logging 包的出現(xiàn)時(shí)間早于較新的格式化選項(xiàng)例如 str.format()string.Template。 這些較新格式化選項(xiàng) 受支持的,但探索它們超出了本教程的范圍:有關(guān)詳細(xì)信息,請(qǐng)參閱 生效于整個(gè)應(yīng)用程序的格式化樣式。

更改顯示消息的格式?

要更改用于顯示消息的格式,你需要指定要使用的格式:

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')

這將輸出:

DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too

請(qǐng)注意,前面示例中出現(xiàn)的 “root” 已消失。 對(duì)于可以出現(xiàn)在格式字符串中的全部內(nèi)容,你可以參考以下文檔 LogRecord 屬性 ,但為了簡單使用,你只需要 levelname (嚴(yán)重性), message (事件描述,包括可變數(shù)據(jù)),也許在事件發(fā)生時(shí)顯示。 這將在下一節(jié)中介紹。

在消息中顯示日期/時(shí)間?

要顯示事件的日期和時(shí)間,你可以在格式字符串中放置 '%(asctime)s'

import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

應(yīng)該打印這樣的東西:

2010-12-12 11:41:42,612 is when this event was logged.

日期/時(shí)間顯示的默認(rèn)格式(如上所示)類似于 ISO8601 或 RFC 3339 。 如果你需要更多地控制日期/時(shí)間的格式,請(qǐng)為 basicConfig 提供 datefmt 參數(shù),如下例所示:

import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')

這會(huì)顯示如下內(nèi)容:

12/12/2010 11:46:36 AM is when this event was logged.

datefmt 參數(shù)的格式與 time.strftime() 支持的格式相同。

后續(xù)步驟?

基本教程到此結(jié)束。 它應(yīng)該足以讓你啟動(dòng)并運(yùn)行日志記錄。 logging 包提供了更多功能,但為了充分利用它,你需要花更多的時(shí)間來閱讀以下部分。 如果你準(zhǔn)備好了,可以拿一些你最喜歡的飲料然后繼續(xù)。

如果你的日志記錄需求很簡單,那么使用上面的示例將日志記錄合并到你自己的腳本中,如果你遇到問題或者不理解某些內(nèi)容,請(qǐng)?jiān)?comp.lang.python Usenet 組上發(fā)布一個(gè)問題(在 https://groups.google.com/forum/#!forum/comp.lang.python ) ,你將在短時(shí)間內(nèi)得到幫助。

還不夠? 你可以繼續(xù)閱讀接下來的幾個(gè)部分,這些部分提供了比上面基本部分更高級(jí)或深入的教程。 之后,你可以看一下 日志操作手冊(cè) 。

進(jìn)階日志教程?

日志庫采用模塊化方法,并提供幾類組件:記錄器、處理器、過濾器和格式器。

  • 記錄器暴露了應(yīng)用程序代碼直接使用的接口。

  • 處理器將日志記錄(由記錄器創(chuàng)建)發(fā)送到適當(dāng)?shù)哪繕?biāo)。

  • 過濾器提供了更細(xì)粒度的功能,用于確定要輸出的日志記錄。

  • 格式器指定最終輸出中日志記錄的樣式。

日志事件信息在 LogRecord 實(shí)例中的記錄器、處理器、過濾器和格式器之間傳遞。

通過調(diào)用 Logger 類(以下稱為 loggers , 記錄器)的實(shí)例來執(zhí)行日志記錄。 每個(gè)實(shí)例都有一個(gè)名稱,它們?cè)诟拍钌弦渣c(diǎn)(句點(diǎn))作為分隔符排列在命名空間的層次結(jié)構(gòu)中。 例如,名為 'scan' 的記錄器是記錄器 'scan.text' ,'scan.html' 和 'scan.pdf' 的父級(jí)。 記錄器名稱可以是你想要的任何名稱,并指示記錄消息源自的應(yīng)用程序區(qū)域。

在命名記錄器時(shí)使用的一個(gè)好習(xí)慣是在每個(gè)使用日志記錄的模塊中使用模塊級(jí)記錄器,命名如下:

logger = logging.getLogger(__name__)

這意味著記錄器名稱跟蹤包或模塊的層次結(jié)構(gòu),并且直觀地從記錄器名稱顯示記錄事件的位置。

記錄器層次結(jié)構(gòu)的根稱為根記錄器。 這是函數(shù) debug() 、 info()warning() 、 error()critical() 使用的記錄器,它們就是調(diào)用了根記錄器的同名方法。 函數(shù)和方法具有相同的簽名。 根記錄器的名稱在輸出中打印為 'root' 。

當(dāng)然,可以將消息記錄到不同的地方。 軟件包中的支持包含,用于將日志消息寫入文件、 HTTP GET/POST 位置、通過 SMTP 發(fā)送電子郵件、通用套接字、隊(duì)列或特定于操作系統(tǒng)的日志記錄機(jī)制(如 syslog 或 Windows NT 事件日志)。 目標(biāo)由 handler 類提供。 如果你有任何內(nèi)置處理器類未滿足的特殊要求,則可以創(chuàng)建自己的日志目標(biāo)類。

默認(rèn)情況下,沒有為任何日志消息設(shè)置目標(biāo)。 你可以使用 basicConfig() 指定目標(biāo)(例如控制臺(tái)或文件),如教程示例中所示。 如果你調(diào)用函數(shù) debug() 、 info()warning() 、 error()critical() ,它們將檢查是否有設(shè)置目標(biāo);如果沒有設(shè)置,將在委托給根記錄器進(jìn)行實(shí)際的消息輸出之前設(shè)置目標(biāo)為控制臺(tái)( sys.stderr )并設(shè)置顯示消息的默認(rèn)格式。

basicConfig() 設(shè)置的消息默認(rèn)格式為:

severity:logger name:message

你可以通過使用 format 參數(shù)將格式字符串傳遞給 basicConfig() 來更改此設(shè)置。有關(guān)如何構(gòu)造格式字符串的所有選項(xiàng),請(qǐng)參閱 格式器對(duì)象 。

記錄流程?

記錄器和處理器中的日志事件信息流程如下圖所示。

../images/logging_flow.png

記錄器?

Logger 對(duì)象有三重任務(wù)。首先,它們向應(yīng)用程序代碼公開了幾種方法,以便應(yīng)用程序可以在運(yùn)行時(shí)記錄消息。其次,記錄器對(duì)象根據(jù)嚴(yán)重性(默認(rèn)過濾工具)或過濾器對(duì)象確定要處理的日志消息。第三,記錄器對(duì)象將相關(guān)的日志消息傳遞給所有感興趣的日志處理器。

記錄器對(duì)象上使用最廣泛的方法分為兩類:配置和消息發(fā)送。

這些是最常見的配置方法:

  • Logger.setLevel() 指定記錄器將處理的最低嚴(yán)重性日志消息,其中 debug 是最低內(nèi)置嚴(yán)重性級(jí)別, critical 是最高內(nèi)置嚴(yán)重性級(jí)別。 例如,如果嚴(yán)重性級(jí)別為 INFO ,則記錄器將僅處理 INFO 、 WARNING 、 ERROR 和 CRITICAL 消息,并將忽略 DEBUG 消息。

  • Logger.addHandler()Logger.removeHandler() 從記錄器對(duì)象中添加和刪除處理器對(duì)象。處理器在以下內(nèi)容中有更詳細(xì)的介紹 處理器 。

  • Logger.addFilter()Logger.removeFilter() 可以添加或移除記錄器對(duì)象中的過濾器。 過濾器對(duì)象 包含更多的過濾器細(xì)節(jié)。

你不需要總是在你創(chuàng)建的每個(gè)記錄器上都調(diào)用這些方法。 請(qǐng)參閱本節(jié)的最后兩段。

配置記錄器對(duì)象后,以下方法將創(chuàng)建日志消息:

  • Logger.debug() 、 Logger.info()Logger.warning() 、 Logger.error()Logger.critical() 都創(chuàng)建日志記錄,包含消息和與其各自方法名稱對(duì)應(yīng)的級(jí)別。該消息實(shí)際上是一個(gè)格式化字符串,它可能包含標(biāo)題字符串替換語法 %s 、 %d 、 %f 等等。其余參數(shù)是與消息中的替換字段對(duì)應(yīng)的對(duì)象列表。關(guān)于 **kwargs ,日志記錄方法只關(guān)注 exc_info 的關(guān)鍵字,并用它來確定是否記錄異常信息。

  • Logger.exception() 創(chuàng)建與 Logger.error() 相似的日志信息。 不同之處是, Logger.exception() 同時(shí)還記錄當(dāng)前的堆棧追蹤。僅從異常處理程序調(diào)用此方法。

  • Logger.log() 將日志級(jí)別作為顯式參數(shù)。對(duì)于記錄消息而言,這比使用上面列出的日志級(jí)別便利方法更加冗長,但這是使用自定義日志級(jí)別的方法。

getLogger() 返回對(duì)具有指定名稱的記錄器實(shí)例的引用(如果已提供),或者如果沒有則返回 root 。名稱是以句點(diǎn)分隔的層次結(jié)構(gòu)。多次調(diào)用 getLogger() 具有相同的名稱將返回對(duì)同一記錄器對(duì)象的引用。在分層列表中較低的記錄器是列表中較高的記錄器的子項(xiàng)。例如,給定一個(gè)名為 foo 的記錄器,名稱為 foo.bar 、 foo.bar.bazfoo.bam 的記錄器都是 foo 子項(xiàng)。

記錄器具有 有效等級(jí) 的概念。如果未在記錄器上顯式設(shè)置級(jí)別,則使用其父記錄器的級(jí)別作為其有效級(jí)別。如果父記錄器沒有明確的級(jí)別設(shè)置,則檢查 父級(jí)。依此類推,搜索所有上級(jí)元素,直到找到明確設(shè)置的級(jí)別。根記錄器始終具有明確的級(jí)別配置(默認(rèn)情況下為 WARNING )。在決定是否處理事件時(shí),記錄器的有效級(jí)別用于確定事件是否傳遞給記錄器相關(guān)的處理器。

子記錄器將消息傳播到與其父級(jí)記錄器關(guān)聯(lián)的處理器。因此,不必為應(yīng)用程序使用的所有記錄器定義和配置處理器。一般為頂級(jí)記錄器配置處理器,再根據(jù)需要?jiǎng)?chuàng)建子記錄器就足夠了。(但是,你可以通過將記錄器的 propagate 屬性設(shè)置為 False 來關(guān)閉傳播。)

處理器?

Handler 對(duì)象負(fù)責(zé)將適當(dāng)?shù)娜罩鞠ⅲɑ谌罩鞠⒌膰?yán)重性)分派給處理器的指定目標(biāo)。 Logger 對(duì)象可以使用 addHandler() 方法向自己添加零個(gè)或多個(gè)處理器對(duì)象。作為示例場景,應(yīng)用程序可能希望將所有日志消息發(fā)送到日志文件,將錯(cuò)誤或更高的所有日志消息發(fā)送到標(biāo)準(zhǔn)輸出,以及將所有關(guān)鍵消息發(fā)送至一個(gè)郵件地址。 此方案需要三個(gè)單獨(dú)的處理器,其中每個(gè)處理器負(fù)責(zé)將特定嚴(yán)重性的消息發(fā)送到特定位置。

標(biāo)準(zhǔn)庫包含很多處理器類型(參見 有用的處理器 );教程主要使用 StreamHandlerFileHandler 。

處理器中很少有方法可供應(yīng)用程序開發(fā)人員使用。使用內(nèi)置處理器對(duì)象(即不創(chuàng)建自定義處理器)的應(yīng)用程序開發(fā)人員能用到的僅有以下配置方法:

  • setLevel() 方法,就像在記錄器對(duì)象中一樣,指定將被分派到適當(dāng)目標(biāo)的最低嚴(yán)重性。為什么有兩個(gè) setLevel() 方法?記錄器中設(shè)置的級(jí)別確定將傳遞給其處理器的消息的嚴(yán)重性。每個(gè)處理器中設(shè)置的級(jí)別確定該處理器將發(fā)送哪些消息。

  • setFormatter() 選擇一個(gè)該處理器使用的 Formatter 對(duì)象。

  • addFilter()removeFilter() 分別在處理器上配置和取消配置過濾器對(duì)象。

應(yīng)用程序代碼不應(yīng)直接實(shí)例化并使用 Handler 的實(shí)例。 相反, Handler 類是一個(gè)基類,它定義了所有處理器應(yīng)該具有的接口,并建立了子類可以使用(或覆蓋)的一些默認(rèn)行為。

格式器?

格式化器對(duì)象配置日志消息的最終順序、結(jié)構(gòu)和內(nèi)容。 與 logging.Handler 類不同,應(yīng)用程序代碼可以實(shí)例化格式器類,但如果應(yīng)用程序需要特殊行為,則可能會(huì)對(duì)格式化器進(jìn)行子類化定制。構(gòu)造函數(shù)有三個(gè)可選參數(shù) —— 消息格式字符串、日期格式字符串和樣式指示符。

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')?

如果沒有消息格式字符串,則默認(rèn)使用原始消息。如果沒有日期格式字符串,則默認(rèn)日期格式為:

%Y-%m-%d %H:%M:%S

最后加上毫秒數(shù)。 style,'{ ' 或 '$' 之一。 如果未指定,則將使用 '%'。

如果 style 是 '%',則消息格式字符串使用 %(<dictionary key>)s 樣式字符串替換;可能的鍵值在 LogRecord 屬性 中。 如果樣式為 '{',則假定消息格式字符串與 str.format() (使用關(guān)鍵字參數(shù))兼容,而如果樣式為 '$' ,則消息格式字符串應(yīng)符合 string.Template.substitute() 。

在 3.2 版更改: 添加 style 形參。

以下消息格式字符串將以人類可讀的格式記錄時(shí)間、消息的嚴(yán)重性以及消息的內(nèi)容,按此順序:

'%(asctime)s - %(levelname)s - %(message)s'

格式器通過用戶可配置的函數(shù)將記錄的創(chuàng)建時(shí)間轉(zhuǎn)換為元組。 默認(rèn)情況下,使用 time.localtime() ;要為特定格式器實(shí)例更改此項(xiàng),請(qǐng)將實(shí)例的 converter 屬性設(shè)置為與 time.localtime()time.gmtime() 具有相同簽名的函數(shù)。 要為所有格式器更改它,例如,如果你希望所有記錄時(shí)間都以 GMT 顯示,請(qǐng)?jiān)诟袷狡黝愔性O(shè)置 converter 屬性(對(duì)于 GMT 顯示,設(shè)置為 time.gmtime )。

配置日志記錄?

開發(fā)者可以通過三種方式配置日志記錄:

  1. 使用調(diào)用上面列出的配置方法的 Python 代碼顯式創(chuàng)建記錄器、處理器和格式器。

  2. 創(chuàng)建日志配置文件并使用 fileConfig() 函數(shù)讀取它。

  3. 創(chuàng)建配置信息字典并將其傳遞給 dictConfig() 函數(shù)。

有關(guān)最后兩個(gè)選項(xiàng)的參考文檔,請(qǐng)參閱 配置函數(shù) 。 以下示例使用 Python 代碼配置一個(gè)非常簡單的記錄器、一個(gè)控制臺(tái)處理器和一個(gè)簡單的格式器:

import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

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

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

從命令行運(yùn)行此模塊將生成以下輸出:

$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

以下 Python 模塊創(chuàng)建的記錄器、處理器和格式器幾乎與上面列出的示例中的相同,唯一的區(qū)別是對(duì)象的名稱:

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.warning('warn message')
logger.error('error message')
logger.critical('critical message')

這是 logging.conf 文件:

[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

其輸出與不基于配置文件的示例幾乎相同:

$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message

你可以看到配置文件方法相較于 Python 代碼方法有一些優(yōu)勢,主要是配置和代碼的分離以及非開發(fā)者輕松修改日志記錄屬性的能力。

警告

fileConfig() 函數(shù)接受一個(gè)默認(rèn)參數(shù) disable_existing_loggers ,出于向后兼容的原因,默認(rèn)為 True 。這可能與您的期望不同,因?yàn)槌窃谂渲弥忻鞔_命名它們(或其父級(jí)),否則它將導(dǎo)致在 fileConfig() 調(diào)用之前存在的任何非 root 記錄器被禁用。有關(guān)更多信息,請(qǐng)參閱參考文檔,如果需要,請(qǐng)將此參數(shù)指定為 False 。

傳遞給 dictConfig() 的字典也可以用鍵 disable_existing_loggers 指定一個(gè)布爾值,如果沒有在字典中明確指定,也默認(rèn)被解釋為 True 。這會(huì)導(dǎo)致上面描述的記錄器禁用行為,這可能與你的期望不同——在這種情況下,請(qǐng)明確地為其提供 False 值。

請(qǐng)注意,配置文件中引用的類名稱需要相對(duì)于日志記錄模塊,或者可以使用常規(guī)導(dǎo)入機(jī)制解析的絕對(duì)值。因此,你可以使用 WatchedFileHandler (相對(duì)于日志記錄模塊)或 mypackage.mymodule.MyHandler (對(duì)于在 mypackage 包中定義的類和模塊 mymodule ,其中 mypackage 在 Python 導(dǎo)入路徑上可用)。

在 Python 3.2 中,引入了一種新的配置日志記錄的方法,使用字典來保存配置信息。 這提供了上述基于配置文件方法的功能的超集,并且是新應(yīng)用程序和部署的推薦配置方法。 因?yàn)?Python 字典用于保存配置信息,并且由于你可以使用不同的方式填充該字典,因此你有更多的配置選項(xiàng)。 例如,你可以使用 JSON 格式的配置文件,或者如果你有權(quán)訪問 YAML 處理功能,則可以使用 YAML 格式的文件來填充配置字典。當(dāng)然,你可以在 Python 代碼中構(gòu)造字典,通過套接字以 pickle 形式接收它,或者使用對(duì)你的應(yīng)用程序合理的任何方法。

以下是與上述相同配置的示例,采用 YAML 格式,用于新的基于字典的方法:

version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple
    stream: ext://sys.stdout
loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: no
root:
  level: DEBUG
  handlers: [console]

有關(guān)使用字典進(jìn)行日志記錄的更多信息,請(qǐng)參閱 配置函數(shù)。

如果沒有提供配置會(huì)發(fā)生什么?

如果未提供日志記錄配置,則可能出現(xiàn)需要輸出日志記錄事件但無法找到輸出事件的處理器的情況。 在這些情況下,logging 包的行為取決于 Python 版本。

對(duì)于 3.2 之前的 Python 版本,行為如下:

  • 如果 logging.raiseExceptionsFalse (生產(chǎn)模式),則會(huì)以靜默方式丟棄該事件。

  • 如果 logging.raiseExceptionsTrue (開發(fā)模式),則會(huì)打印一條消息 'No handlers could be found for logger X.Y.Z'。

在 Python 3.2 及更高版本中,行為如下:

  • 事件使用 “最后的處理器” 輸出,存儲(chǔ)在 logging.lastResort 中。 這個(gè)內(nèi)部處理器與任何記錄器都沒有關(guān)聯(lián),它的作用類似于 StreamHandler ,它將事件描述消息寫入 sys.stderr 的當(dāng)前值(因此服從任何可能的重定向影響)。 沒有對(duì)消息進(jìn)行格式化——只打印裸事件描述消息。處理器的級(jí)別設(shè)置為 WARNING,因此將輸出此級(jí)別和更高級(jí)別的所有事件。

要獲得 3.2 之前的行為,可以設(shè)置 logging.lastResortNone。

配置庫的日志記錄?

在開發(fā)使用日志記錄的庫時(shí),你應(yīng)該注意記錄庫如何使用日志記錄——例如,使用的記錄器的名稱。還需要考慮其日志記錄配置。如果應(yīng)用程序不使用日志記錄,并且?guī)齑a進(jìn)行日志記錄調(diào)用,那么(如上一節(jié)所述)嚴(yán)重性為 WARNING 及更高級(jí)別的事件將打印到 sys.stderr 。這被認(rèn)為是最好的默認(rèn)行為。

如果由于某種原因,你 希望在沒有任何日志記錄配置的情況下打印這些消息,則可以將無操作處理器附加到庫的頂級(jí)記錄器。這樣可以避免打印消息,因?yàn)閷⑹冀K為庫的事件找到處理器:它不會(huì)產(chǎn)生任何輸出。如果庫用戶配置應(yīng)用程序使用的日志記錄,可能是配置將添加一些處理器,如果級(jí)別已適當(dāng)配置,則在庫代碼中進(jìn)行的日志記錄調(diào)用將正常地將輸出發(fā)送給這些處理器。

日志包中包含一個(gè)不做任何事情的處理器: NullHandler (自 Python 3.1 起)??梢詫⒋颂幚砥鞯膶?shí)例添加到庫使用的日志記錄命名空間的頂級(jí)記錄器中( 如果 你希望在沒有日志記錄配置的情況下阻止庫的記錄事件輸出到 sys.stderr )。如果庫 foo 的所有日志記錄都是使用名稱匹配 'foo.x' , 'foo.x.y' 等的記錄器完成的,那么代碼:

import logging
logging.getLogger('foo').addHandler(logging.NullHandler())

應(yīng)該有預(yù)計(jì)的效果。如果一個(gè)組織生成了許多庫,則指定的記錄器名稱可以是 “orgname.foo” 而不僅僅是 “foo” 。

備注

It is strongly advised that you do not log to the root logger in your library. Instead, use a logger with a unique and easily identifiable name, such as the __name__ for your library's top-level package or module. Logging to the root logger will make it difficult or impossible for the application developer to configure the logging verbosity or handlers of your library as they wish.

備注

強(qiáng)烈建議你 不要將 NullHandler 以外的任何處理器添加到庫的記錄器中 。這是因?yàn)樘幚砥鞯呐渲檬鞘褂媚愕膸斓膽?yīng)用程序開發(fā)人員的權(quán)利。應(yīng)用程序開發(fā)人員了解他們的目標(biāo)受眾以及哪些處理器最適合他們的應(yīng)用程序:如果你在“底層”添加處理器,則可能會(huì)干擾他們執(zhí)行單元測試和提供符合其要求的日志的能力。

日志級(jí)別?

日志記錄級(jí)別的數(shù)值在下表中給出。如果你想要定義自己的級(jí)別,并且需要它們具有相對(duì)于預(yù)定義級(jí)別的特定值,那么這你可能對(duì)以下內(nèi)容感興趣。如果你定義具有相同數(shù)值的級(jí)別,它將覆蓋預(yù)定義的值;預(yù)定義的名稱將失效。

級(jí)別

數(shù)值

CRITICAL

50

ERROR

40

WARNING

30

INFO

20

DEBUG

10

NOTSET

0

級(jí)別也可以與記錄器相關(guān)聯(lián),由開發(fā)人員設(shè)置或通過加載已保存的日志記錄配置。在記錄器上調(diào)用日志記錄方法時(shí),記錄器會(huì)將其自己的級(jí)別與與方法調(diào)用關(guān)聯(lián)的級(jí)別進(jìn)行比較。如果記錄器的級(jí)別高于方法調(diào)用的級(jí)別,則實(shí)際上不會(huì)生成任何記錄消息。這是控制日志記錄輸出詳細(xì)程度的基本機(jī)制。

記錄消息被編碼為 LogRecord 類的實(shí)例。當(dāng)記錄器決定實(shí)際記錄事件時(shí),將用記錄消息創(chuàng)建 LogRecord 實(shí)例。

記錄消息受 handlers 建立的調(diào)度機(jī)制控制,它們是 Handler 類的子類實(shí)例。處理器負(fù)責(zé)確保記錄的消息(以 LogRecord 的形式)最終位于對(duì)該消息的目標(biāo)受眾(例如最終用戶、 支持服務(wù)臺(tái)員工、系統(tǒng)管理員、開發(fā)人員)有用的特定位置(或一組位置)上。處理器傳遞適用于特定目標(biāo)的 LogRecord 實(shí)例。 每個(gè)記錄器可以有零個(gè)、一個(gè)或多個(gè)與之關(guān)聯(lián)的處理器(通過 LoggeraddHandler() 方法)。除了與記錄器直接關(guān)聯(lián)的所有處理器之外,還調(diào)用與記錄器的 所有祖先關(guān)聯(lián)的處理器來分派消息(除非記錄器的 *propagate 標(biāo)志設(shè)置為 false 值,這將停止傳遞到上級(jí)處理器)。

就像記錄器一樣,處理器可以具有與它們相關(guān)聯(lián)的級(jí)別。處理器的級(jí)別作為過濾器,其方式與記錄器級(jí)別相同。如果處理器決定調(diào)度一個(gè)事件,則使用 emit() 方法將消息發(fā)送到其目標(biāo)。大多數(shù)用戶定義的 Handler 子類都需要重載 emit() 。

自定義級(jí)別?

定義你自己的級(jí)別是可能的,但不一定是必要的,因?yàn)楝F(xiàn)有級(jí)別是根據(jù)實(shí)踐經(jīng)驗(yàn)選擇的。但是,如果你確信需要自定義級(jí)別,那么在執(zhí)行此操作時(shí)應(yīng)特別小心,如果你正在開發(fā)庫,則 定義自定義級(jí)別可能是一個(gè)非常糟糕的主意 。 這是因?yàn)槿绻鄠€(gè)庫作者都定義了他們自己的自定義級(jí)別,那么使用開發(fā)人員很難控制和解釋這些多個(gè)庫的日志記錄輸出,因?yàn)榻o定的數(shù)值對(duì)于不同的庫可能意味著不同的東西。

有用的處理器?

作為 Handler 基類的補(bǔ)充,提供了很多有用的子類:

  1. StreamHandler 實(shí)例發(fā)送消息到流(類似文件對(duì)象)。

  2. FileHandler 實(shí)例將消息發(fā)送到硬盤文件。

  3. BaseRotatingHandler 是輪換日志文件的處理器的基類。它并不應(yīng)該直接實(shí)例化。而應(yīng)該使用 RotatingFileHandlerTimedRotatingFileHandler 代替它。

  4. RotatingFileHandler 實(shí)例將消息發(fā)送到硬盤文件,支持最大日志文件大小和日志文件輪換。

  5. TimedRotatingFileHandler 實(shí)例將消息發(fā)送到硬盤文件,以特定的時(shí)間間隔輪換日志文件。

  6. SocketHandler 實(shí)例將消息發(fā)送到 TCP/IP 套接字。從 3.4 開始,也支持 Unix 域套接字。

  7. DatagramHandler 實(shí)例將消息發(fā)送到 UDP 套接字。從 3.4 開始,也支持 Unix 域套接字。

  8. SMTPHandler 實(shí)例將消息發(fā)送到指定的電子郵件地址。

  9. SysLogHandler 實(shí)例將消息發(fā)送到 Unix syslog 守護(hù)程序,可能在遠(yuǎn)程計(jì)算機(jī)上。

  10. NTEventLogHandler 實(shí)例將消息發(fā)送到 Windows NT/2000/XP 事件日志。

  11. MemoryHandler 實(shí)例將消息發(fā)送到內(nèi)存中的緩沖區(qū),只要滿足特定條件,緩沖區(qū)就會(huì)刷新。

  12. HTTPHandler 實(shí)例使用 GETPOST 方法將消息發(fā)送到 HTTP 服務(wù)器。

  13. WatchedFileHandler 實(shí)例會(huì)監(jiān)視他們要寫入日志的文件。如果文件發(fā)生更改,則會(huì)關(guān)閉該文件并使用文件名重新打開。此處理器僅在類 Unix 系統(tǒng)上有用; Windows 不支持依賴的基礎(chǔ)機(jī)制。

  14. QueueHandler 實(shí)例將消息發(fā)送到隊(duì)列,例如在 queuemultiprocessing 模塊中實(shí)現(xiàn)的隊(duì)列。

  15. NullHandler 實(shí)例對(duì)錯(cuò)誤消息不執(zhí)行任何操作。它們由想要使用日志記錄的庫開發(fā)人員使用,但是想要避免如果庫用戶沒有配置日志記錄,則顯示 'No handlers could be found for logger XXX' 消息的情況。更多有關(guān)信息,請(qǐng)參閱 配置庫的日志記錄 。

3.1 新版功能: NullHandler 類。

3.2 新版功能: QueueHandler 類。

The NullHandler 、 StreamHandlerFileHandler 類在核心日志包中定義。其他處理器定義在 logging.handlers 中。(還有另一個(gè)子模塊 logging.config ,用于配置功能)

記錄的消息通過 Formatter 類的實(shí)例進(jìn)行格式化后呈現(xiàn)。 它們使用能與 % 運(yùn)算符一起使用的格式字符串和字典進(jìn)行初始化。

要批量格式化多個(gè)消息,可以使用 BufferingFormatter 的實(shí)例。除了格式字符串(應(yīng)用于批處理中的每個(gè)消息)之外,還提供了標(biāo)題和尾部格式字符串。

當(dāng)基于記錄器級(jí)別和處理器級(jí)別的過濾不夠時(shí),可以將 Filter 的實(shí)例添加到 LoggerHandler 實(shí)例(通過它們的 addFilter() 方法)。在決定進(jìn)一步處理消息之前,記錄器和處理器都會(huì)查詢其所有過濾器以獲得許可。如果任何過濾器返回 false 值,則不會(huì)進(jìn)一步處理該消息。

基本 Filter 的功能允許按特定的記錄器名稱進(jìn)行過濾。如果使用此功能,則允許通過過濾器發(fā)送到指定記錄器及其子項(xiàng)的消息,并丟棄其他所有消息。

記錄日志時(shí)引發(fā)的異常?

logging 包設(shè)計(jì)為忽略記錄日志生產(chǎn)時(shí)發(fā)生的異常。這樣,處理日志記錄事件時(shí)發(fā)生的錯(cuò)誤(例如日志記錄錯(cuò)誤配置、網(wǎng)絡(luò)或其他類似錯(cuò)誤)不會(huì)導(dǎo)致使用日志記錄的應(yīng)用程序過早終止。

SystemExitKeyboardInterrupt 異常永遠(yuǎn)不會(huì)被忽略。 在 Handler 子類的 emit() 方法中發(fā)生的其他異常被傳遞給它的 handleError() 方法。

Handler 中默認(rèn)實(shí)現(xiàn)的 handleError() 檢查是否設(shè)置了模塊級(jí)變量 raiseExceptions 。如果有設(shè)置,則會(huì)將回溯打印到 sys.stderr 。如果未設(shè)置,則忽略異常。

備注

raiseExceptions 默認(rèn)值是 True。 這是因?yàn)樵陂_發(fā)期間,你通常希望收到任何發(fā)生異常的通知。建議你將 raiseExceptions 設(shè)置為 False 以供生產(chǎn)環(huán)境使用。

使用任意對(duì)象作為消息?

在前面的部分和示例中,都假設(shè)記錄事件時(shí)傳遞的消息是字符串。 但是,這不是唯一的可能性。你可以將任意對(duì)象作為消息傳遞,并且當(dāng)日志記錄系統(tǒng)需要將其轉(zhuǎn)換為字符串表示時(shí),將調(diào)用其 __ str__() 方法。實(shí)際上,如果你愿意,你可以完全避免計(jì)算字符串表示。例如, SocketHandler 用 pickle 處理事件后,通過網(wǎng)絡(luò)發(fā)送。

優(yōu)化?

消息參數(shù)的格式化將被推遲,直到無法避免。但是,計(jì)算傳遞給日志記錄方法的參數(shù)也可能很消耗資源,如果記錄器只是丟棄你的事件,你可能希望避免這樣做。要決定做什么,可以調(diào)用 isEnabledFor() 方法,該方法接受一個(gè) level 參數(shù),如果記錄器為該級(jí)別的調(diào)用創(chuàng)建了該事件,則返回 true 。 你可以寫這樣的代碼:

if logger.isEnabledFor(logging.DEBUG):
    logger.debug('Message with %s, %s', expensive_func1(),
                                        expensive_func2())

因此,如果記錄器的閾值設(shè)置在“DEBUG”以上,則永遠(yuǎn)不會(huì)調(diào)用 expensive_func1()expensive_func2() 。

備注

在某些情況下, isEnabledFor() 本身可能比你想要的更消耗資源(例如,對(duì)于深度嵌套的記錄器,其中僅在記錄器層次結(jié)構(gòu)中設(shè)置了顯式級(jí)別)。在這種情況下(或者如果你想避免在緊密循環(huán)中調(diào)用方法),你可以在本地或?qū)嵗兞恐袑⒄{(diào)用的結(jié)果緩存到 isEnabledFor() ,并使用它而不是每次調(diào)用方法。在日志記錄配置在應(yīng)用程序運(yùn)行時(shí)動(dòng)態(tài)更改(這不常見)時(shí),只需要重新計(jì)算這樣的緩存值即可。

對(duì)于需要對(duì)收集的日志信息進(jìn)行更精確控制的特定應(yīng)用程序,還可以進(jìn)行其他優(yōu)化。以下列出了在日志記錄過程中您可以避免的非必須處理操作:

你不想收集的內(nèi)容

如何避免收集它

有關(guān)調(diào)用來源的信息

logging._srcfile 設(shè)置為 None 。這避免了調(diào)用 sys._getframe() ,如果 PyPy 支持 Python 3.x ,這可能有助于加速 PyPy (無法加速使用 sys._getframe() 的代碼)等環(huán)境中的代碼。

線程信息

logging.logThreads 設(shè)為 False

當(dāng)前進(jìn)程 ID (os.getpid())

logging.logProcesses 設(shè)為 False。

當(dāng)使用 multiprocessing 來管理多個(gè)進(jìn)程時(shí)的當(dāng)前進(jìn)程名稱。

logging.logMultiprocessing 設(shè)為 False。

Current asyncio.Task name when using asyncio.

Set logging.logAsyncioTasks to False.

另請(qǐng)注意,核心日志記錄模塊僅包含基本處理器。如果你不導(dǎo)入 logging.handlerslogging.config ,它們將不會(huì)占用任何內(nèi)存。

參見

模塊 logging

日志記錄模塊的 API 參考。

logging.config 模塊

日志記錄模塊的配置 API 。

logging.handlers 模塊

日志記錄模塊附帶的有用處理器。

日志操作手冊(cè)