日誌記錄 HOWTO

作者:

Vinay Sajip <vinay_sajip at red-dove dot com>

此頁面包含教程資訊。有關參考資訊和日誌記錄手冊的連結,請參閱其他資源

基本日誌記錄教程

日誌記錄是一種跟蹤軟體執行時發生的事件的方法。軟體開發者將日誌記錄呼叫新增到其程式碼中,以指示某些事件已發生。事件由描述性訊息描述,該訊息可以可選地包含變數資料(即,對於事件的每次發生可能不同的資料)。事件還具有開發者賦予事件的重要性;重要性也可以稱為*級別*或*嚴重性*。

何時使用日誌記錄

您可以透過使用logger = getLogger(__name__)建立記錄器,然後呼叫記錄器的debug()info()warning()error()critical()方法來訪問日誌記錄功能。要確定何時使用日誌記錄以及何時使用哪個記錄器方法,請參閱下表。它針對一組常見任務,說明了完成該任務的最佳工具。

您要執行的任務

完成任務的最佳工具

為命令列指令碼或程式的普通用法顯示控制檯輸出

print()

報告程式正常執行期間發生的事件(例如,用於狀態監控或故障調查)

記錄器的info()(或debug()方法,用於診斷目的的非常詳細的輸出)

發出有關特定執行時事件的警告

庫程式碼中的warnings.warn(),如果問題可避免且客戶端應用程式應修改以消除警告

記錄器的warning()方法,如果客戶端應用程式無法對情況做任何事情,但事件仍應被記錄

報告有關特定執行時事件的錯誤

引發異常

報告抑制錯誤而不引發異常(例如,長時間執行的伺服器程序中的錯誤處理器)

記錄器的error()exception()critical()方法,根據具體錯誤和應用領域選擇

記錄器方法的名稱與其用於跟蹤的事件的級別或嚴重性相對應。下面描述了標準級別及其適用性(按嚴重性遞增順序)

級別

何時使用

DEBUG

詳細資訊,通常僅在診斷問題時感興趣。

INFO

確認一切按預期執行。

WARNING

表示發生了意外情況,或預示著近期可能出現問題(例如,“磁碟空間不足”)。軟體仍按預期執行。

ERROR

由於更嚴重的問題,軟體無法執行某些功能。

CRITICAL

嚴重錯誤,表示程式本身可能無法繼續執行。

預設級別是WARNING,這意味著只有此嚴重級別及更高級別的事件才會被跟蹤,除非日誌記錄包被配置為執行其他操作。

被跟蹤的事件可以透過不同的方式處理。處理被跟蹤事件的最簡單方法是將其列印到控制檯。另一種常見方法是將其寫入磁碟檔案。

一個簡單的例子

一個非常簡單的例子是

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

如果您將這些行輸入到指令碼中並執行它,您將看到

WARNING:root:Watch out!

列印到控制檯。INFO訊息沒有出現,因為預設級別是WARNING。列印的訊息包括級別指示和日誌呼叫中提供的事件描述,即“小心!”。如果需要,實際輸出可以非常靈活地格式化;格式化選項也將在後面解釋。

請注意,在此示例中,我們直接在logging模組上使用函式,例如logging.debug,而不是建立記錄器並呼叫其上的函式。這些函式在根記錄器上操作,但它們可能很有用,因為如果尚未呼叫basicConfig(),它們將為您呼叫它,就像此示例中一樣。在較大的程式中,您通常希望明確控制日誌記錄配置——因此,出於這個原因以及其他原因,最好建立記錄器並呼叫其方法。

日誌記錄到檔案

一種非常常見的情況是將日誌記錄事件記錄到檔案中,所以接下來我們來看看這個。請務必在新啟動的 Python 直譯器中嘗試以下操作,而不要從上面描述的會話繼續

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

3.9 版新增: 添加了 *encoding* 引數。在早期 Python 版本中,或者如果未指定,使用的編碼是 open() 使用的預設值。雖然在上面的示例中未顯示,但現在也可以傳遞 *errors* 引數,它確定如何處理編碼錯誤。有關可用值和預設值,請參閱 open() 的文件。

現在如果我們開啟檔案並檢視我們有什麼,我們應該會找到日誌訊息

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

此示例還展示瞭如何設定日誌記錄級別作為跟蹤的閾值。在此示例中,因為我們將閾值設定為DEBUG,所以所有訊息都已列印。

如果您想從命令列選項設定日誌級別,例如

--log=INFO

並且您在某個變數*loglevel*中儲存了傳遞給--log引數的值,則可以使用

getattr(logging, loglevel.upper())

獲取您將透過*level*引數傳遞給basicConfig()的值。您可能需要對任何使用者輸入值進行錯誤檢查,可能如下例所示

# 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, ...)

呼叫basicConfig()應該在呼叫記錄器的方法(如debug()info()等)*之前*進行。否則,該日誌事件可能無法以所需方式處理。

如果您多次執行上述指令碼,後續執行的訊息將附加到檔案example.log中。如果您希望每次執行都重新開始,而不記住之前執行的訊息,可以透過將上述示例中的呼叫更改為

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

輸出將與以前相同,但日誌檔案不再追加,因此早期執行的訊息丟失。

記錄變數資料

要記錄變數資料,請使用事件描述訊息的格式字串,並將變數資料作為引數附加。例如

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

將顯示

WARNING:root:Look before you leap!

如您所見,將變數資料合併到事件描述訊息中使用了舊的 % 樣式字串格式。這是為了向後相容:日誌記錄包早於新的格式化選項,例如 str.format()string.Template。這些新的格式化選項*是*支援的,但探索它們超出了本教程的範圍:有關更多資訊,請參閱在整個應用程式中使用特定格式樣式

更改顯示訊息的格式

要更改用於顯示訊息的格式,您需要指定要使用的格式

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

請注意,在早期示例中出現的“root”已經消失。有關可在格式字串中出現的所有內容的完整列表,您可以參考LogRecord 屬性的文件,但對於簡單用法,您只需要*levelname*(嚴重性)、*message*(事件描述,包括變數資料),可能還需要顯示事件發生的時間。這將在下一節中描述。

在訊息中顯示日期/時間

要顯示事件的日期和時間,您可以在格式字串中放置“%(asctime)s”

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

這將打印出類似這樣的內容

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

日期/時間顯示的預設格式(如上所示)類似於 ISO8601 或 RFC 3339。如果您需要對日期/時間格式進行更多控制,請向 basicConfig 提供 *datefmt* 引數,如下例所示

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.')

這將顯示類似這樣的內容

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

*datefmt* 引數的格式與 time.strftime() 支援的格式相同。

後續步驟

基本教程到此結束。它應該足以讓您開始使用日誌記錄。日誌記錄包提供了更多功能,但要充分利用它,您需要花更多時間閱讀以下部分。如果您已準備好,請拿起您最喜歡的飲料,繼續閱讀。

如果您的日誌記錄需求很簡單,那麼可以使用上述示例將日誌記錄整合到您自己的指令碼中,如果您遇到問題或不理解任何內容,請在 Python 討論論壇的幫助類別中釋出問題,您應該很快會收到幫助。

還在嗎?您可以繼續閱讀接下來的幾節,它們提供了比上面基本教程略高階/深入的教程。之後,您可以檢視日誌記錄食譜

高階日誌記錄教程

日誌記錄庫採用模組化方法,提供以下幾類元件:記錄器、處理器、過濾器和格式化器。

  • 記錄器公開了應用程式程式碼直接使用的介面。

  • 處理器將日誌記錄(由記錄器建立)傳送到適當的目標。

  • 過濾器提供更細粒度的功能,用於確定要輸出哪些日誌記錄。

  • 格式化器指定最終輸出中日誌記錄的佈局。

日誌事件資訊在記錄器、處理器、過濾器和格式化器之間以 LogRecord 例項的形式傳遞。

日誌記錄是透過呼叫 Logger 類的例項(以下稱為 *loggers*)上的方法來執行的。每個例項都有一個名稱,它們在概念上使用點(句點)作為分隔符排列在名稱空間層次結構中。例如,名為“scan”的記錄器是記錄器“scan.text”、“scan.html”和“scan.pdf”的父級。記錄器名稱可以是您想要的任何名稱,並指示應用程式中日誌訊息的來源區域。

命名記錄器時的一個好約定是,在每個使用日誌記錄的模組中,使用模組級記錄器,命名如下

logger = logging.getLogger(__name__)

這意味著記錄器名稱跟蹤包/模組層次結構,並且僅從記錄器名稱就可以直觀地知道事件是在何處記錄的。

記錄器層次結構的根稱為根記錄器。這就是函式debug()info()warning()error()critical()使用的記錄器,它們只是呼叫根記錄器中同名的方法。這些函式和方法具有相同的簽名。根記錄器的名稱在日誌輸出中列印為“root”。

當然,可以將訊息記錄到不同的目的地。該軟體包包含將日誌訊息寫入檔案、HTTP GET/POST 位置、透過 SMTP 傳送電子郵件、通用套接字、佇列或特定於作業系統的日誌記錄機制(如 syslog 或 Windows NT 事件日誌)的支援。目的地由*處理程式*類提供服務。如果您有任何內建處理程式類無法滿足的特殊要求,您可以建立自己的日誌目的地類。

預設情況下,沒有為任何日誌訊息設定目標。您可以透過使用 basicConfig() 指定目標(例如控制檯或檔案),如教程示例所示。如果您呼叫函式 debug()info()warning()error()critical(),它們將檢查是否未設定目標;如果未設定,它們將設定控制檯 (sys.stderr) 作為目標,併為顯示的訊息設定預設格式,然後委託給根記錄器執行實際的訊息輸出。

basicConfig() 為訊息設定的預設格式是

severity:logger name:message

您可以透過使用 *format* 關鍵字引數將格式字串傳遞給 basicConfig() 來更改此設定。有關如何構造格式字串的所有選項,請參閱格式化器物件

日誌記錄流程

以下圖表說明了日誌事件資訊在記錄器和處理程式中的流向。

Logger flow Create LogRecord Logging call in user code, e.g. logger.info(...) Stop Does a filter attached to logger reject the record? Pass record to handlers of current logger Is propagate true for current logger? Is there a parent logger? Set current logger to parent At least one handler in hierarchy? Use lastResort handler Handler enabled for level of record? Does a filter attached to handler reject the record? Stop Emit (includes formatting) Handler flow Logger enabled for level of call? No Yes Yes No No Yes Yes No No Yes No Yes No Yes Record passed to handler

記錄器

Logger 物件有三重作用。首先,它們嚮應用程式程式碼公開多種方法,以便應用程式可以在執行時記錄訊息。其次,記錄器物件根據嚴重性(預設過濾功能)或過濾器物件確定要處理哪些日誌訊息。第三,記錄器物件將相關的日誌訊息傳遞給所有感興趣的日誌處理程式。

記錄器物件上最廣泛使用的方法分為兩類:配置和訊息傳送。

這些是最常見的配置方法

您不需要在建立的每個記錄器上都呼叫這些方法。請參閱本節的最後兩段。

配置了記錄器物件後,以下方法建立日誌訊息

  • Logger.debug()Logger.info()Logger.warning()Logger.error()Logger.critical() 都建立帶有訊息和與它們各自方法名對應的級別的日誌記錄。訊息實際上是一個格式字串,可能包含標準的字串替換語法,如 %s%d%f 等。其餘引數是與訊息中的替換欄位對應的物件列表。關於 **kwargs,日誌記錄方法只關心關鍵字 exc_info,並用它來確定是否記錄異常資訊。

  • Logger.exception() 建立的日誌訊息類似於 Logger.error()。區別在於 Logger.exception() 會同時轉儲堆疊跟蹤。僅在異常處理程式中呼叫此方法。

  • Logger.log() 將日誌級別作為顯式引數。對於記錄訊息來說,這比使用上面列出的日誌級別便捷方法稍微冗長,但這是在自定義日誌級別進行日誌記錄的方式。

getLogger() 返回指定名稱的記錄器例項的引用(如果提供),否則返回 root。名稱是句點分隔的層次結構。多次呼叫相同名稱的 getLogger() 將返回對相同記錄器物件的引用。層次結構列表中位置較低的記錄器是列表中位置較高的記錄器的子級。例如,給定一個名為 foo 的記錄器,名為 foo.barfoo.bar.bazfoo.bam 的記錄器都是 foo 的後代。

記錄器具有*有效級別*的概念。如果未在記錄器上明確設定級別,則使用其父級的級別作為其有效級別。如果父級沒有明確設定級別,則檢查*其*父級,依此類推——搜尋所有祖先,直到找到明確設定的級別。根記錄器始終具有明確設定的級別(預設為WARNING)。當決定是否處理事件時,記錄器的有效級別用於確定事件是否傳遞給記錄器的處理程式。

子記錄器將訊息傳播給與其祖先記錄器關聯的處理程式。因此,不需要為應用程式使用的所有記錄器定義和配置處理程式。只需為頂級記錄器配置處理程式,並根據需要建立子記錄器即可。(但是,可以透過將記錄器的*propagate*屬性設定為False來關閉傳播。)

處理程式

Handler 物件負責根據日誌訊息的嚴重性,將相應的日誌訊息分派到處理程式指定的目標。Logger 物件可以使用 addHandler() 方法向自身新增零個或多個處理程式物件。例如,應用程式可能希望將所有日誌訊息傳送到日誌檔案,將所有錯誤或更高級別的日誌訊息傳送到 stdout,並將所有關鍵訊息傳送到電子郵件地址。此方案需要三個單獨的處理程式,每個處理程式負責將特定嚴重性的訊息傳送到特定位置。

標準庫包含多種處理程式型別(請參閱有用的處理程式);教程示例中主要使用StreamHandlerFileHandler

處理程式中很少有應用程式開發人員需要關注的方法。對於使用內建處理程式物件(即不建立自定義處理程式)的應用程式開發人員而言,唯一相關的處理程式方法是以下配置方法

  • 與記錄器物件一樣,setLevel() 方法指定將分派到相應目標的最低嚴重性。為什麼有兩個 setLevel() 方法?在記錄器中設定的級別決定了它將哪些嚴重性訊息傳遞給其處理程式。在每個處理程式中設定的級別決定了該處理程式將傳送哪些訊息。

  • setFormatter() 為此處理程式選擇一個 Formatter 物件使用。

  • addFilter()removeFilter() 分別配置和取消配置處理程式上的過濾器物件。

應用程式程式碼不應直接例項化和使用 Handler 的例項。相反,Handler 類是一個基類,它定義了所有處理程式應該具有的介面,並建立了一些子類可以使用的(或覆蓋的)預設行為。

格式化器

格式化器物件配置日誌訊息的最終順序、結構和內容。與基礎 logging.Handler 類不同,應用程式程式碼可以例項化格式化器類,儘管如果您的應用程式需要特殊行為,您很可能會子類化格式化器。建構函式接受三個可選引數——訊息格式字串、日期格式字串和樣式指示符。

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

如果沒有訊息格式字串,預設使用原始訊息。如果沒有日期格式字串,預設日期格式是

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

在末尾附加毫秒。style'%''{''$' 之一。如果未指定其中之一,則將使用 '%'

如果 style'%',則訊息格式字串使用 %(<dictionary key>)s 樣式字串替換;可能的鍵在LogRecord 屬性中進行了說明。如果樣式是 '{',則假定訊息格式字串與 str.format() 相容(使用關鍵字引數),而如果樣式是 '$',則訊息格式字串應符合 string.Template.substitute() 的預期。

3.2 版新增: 添加了 style 引數。

以下訊息格式字串將按順序記錄可讀格式的時間、訊息的嚴重性以及訊息的內容

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

格式化器使用使用者可配置的函式將記錄的建立時間轉換為元組。預設情況下,使用 time.localtime();要為特定格式化器例項更改此設定,請將例項的 converter 屬性設定為與 time.localtime()time.gmtime() 具有相同簽名的函式。要為所有格式化器更改此設定,例如如果您希望所有日誌記錄時間都顯示為 GMT,請在 Formatter 類中設定 converter 屬性(對於 GMT 顯示,設定為 time.gmtime)。

配置日誌記錄

程式設計師可以透過三種方式配置日誌記錄

  1. 使用 Python 程式碼顯式建立記錄器、處理程式和格式化程式,呼叫上面列出的配置方法。

  2. 建立日誌記錄配置檔案並使用fileConfig()函式讀取它。

  3. 建立一個配置資訊字典並將其傳遞給dictConfig()函式。

有關後兩個選項的參考文件,請參閱配置函式。以下示例使用 Python 程式碼配置了一個非常簡單的記錄器、一個控制檯處理程式和一個簡單的格式化程式

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')

從命令列執行此模組會產生以下輸出

$ 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 模組建立了一個記錄器、處理程式和格式化程式,幾乎與上面列出的示例相同,唯一的區別是物件的名稱

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 程式碼方法有一些優勢,主要是配置與程式碼分離,並且非編碼人員可以輕鬆修改日誌記錄屬性。

警告

fileConfig() 函式接受一個預設引數 disable_existing_loggers,為了向後相容,其預設值為 True。這可能不是您想要的,因為它會導致在呼叫 fileConfig() 之前存在的任何非根記錄器被停用,除非它們(或其祖先)在配置中明確命名。請參閱參考文件以獲取更多資訊,如果您希望,請將此引數指定為 False

傳遞給 dictConfig() 的字典也可以指定一個鍵為 disable_existing_loggers 的布林值,如果未在字典中明確指定,該值也預設為 True。這會導致上述記錄器停用行為,這可能不是您想要的——在這種情況下,請明確提供該鍵並將其值設定為 False

請注意,配置檔案中引用的類名需要相對於日誌記錄模組,或者是可以使用正常匯入機制解析的絕對值。因此,您可以使用 WatchedFileHandler(相對於日誌記錄模組)或 mypackage.mymodule.MyHandler(對於在包 mypackage 和模組 mymodule 中定義的類,其中 mypackage 在 Python 匯入路徑上可用)。

在 Python 3.2 中,引入了一種新的日誌記錄配置方式,使用字典來儲存配置資訊。這提供了上述基於配置檔案的方法的超集功能,並且是新應用程式和部署的推薦配置方法。因為 Python 字典用於儲存配置資訊,並且您可以使用不同的方式填充該字典,所以您有更多的配置選項。例如,您可以使用 JSON 格式的配置檔案,或者,如果您可以訪問 YAML 處理功能,則可以使用 YAML 格式的檔案來填充配置字典。或者,當然,您可以在 Python 程式碼中構造字典,透過套接字以 pickle 形式接收它,或者使用適合您應用程式的任何方法。

這是上述相同配置的示例,採用 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]

有關使用字典進行日誌記錄的更多資訊,請參閱配置函式

未提供配置時會發生什麼

如果未提供日誌配置,則可能會出現需要輸出日誌事件,但找不到任何處理程式來輸出事件的情況。

該事件使用儲存在 lastResort 中的“最後手段處理程式”輸出。此內部處理程式不與任何記錄器關聯,其作用類似於 StreamHandler,將事件描述訊息寫入 sys.stderr 的當前值(因此尊重可能生效的任何重定向)。訊息不進行格式化——僅列印裸露的事件描述訊息。處理程式的級別設定為 WARNING,因此所有此級別及更高級別的事件都將輸出。

3.2 版新增: 對於 Python 3.2 之前的版本,行為如下

  • 如果 raiseExceptionsFalse(生產模式),則事件會靜默丟棄。

  • 如果 raiseExceptionsTrue(開發模式),則會列印一次訊息“No handlers could be found for logger X.Y.Z”。

要獲得 3.2 之前的行為,可以將 lastResort 設定為 None

為庫配置日誌記錄

在開發使用日誌記錄的庫時,您應該注意記錄庫如何使用日誌記錄——例如,所使用的記錄器名稱。還需要考慮其日誌記錄配置。如果使用的應用程式不使用日誌記錄,並且庫程式碼進行日誌記錄呼叫,那麼(如上一節所述)嚴重性為 WARNING 及更高級別的事件將列印到 sys.stderr。這被認為是最佳預設行為。

如果由於某種原因,您*不*希望在沒有任何日誌配置的情況下列印這些訊息,您可以將一個無所事事的處理程式附加到您的庫的頂級記錄器。這樣可以避免列印訊息,因為總能找到庫事件的處理程式:它只是不產生任何輸出。如果庫使用者配置應用程式使用的日誌記錄,那麼該配置可能會新增一些處理程式,並且如果級別配置得當,那麼庫程式碼中進行的日誌記錄呼叫將像往常一樣向這些處理程式傳送輸出。

日誌記錄包中包含一個無所事事的處理程式:NullHandler(自 Python 3.1 起)。可以將此處理程式的例項新增到庫使用的日誌記錄名稱空間的頂級記錄器中(*如果*您想在沒有日誌記錄配置的情況下阻止庫的日誌事件輸出到 sys.stderr)。如果庫 *foo* 的所有日誌記錄都使用名稱與“foo.x”、“foo.x.y”等匹配的記錄器完成,那麼程式碼

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

應該產生預期的效果。如果一個組織生產多個庫,那麼指定的記錄器名稱可以是“orgname.foo”,而不僅僅是“foo”。

備註

強烈建議您*不要在您的庫中記錄到根記錄器*。相反,請使用一個具有唯一且易於識別名稱的記錄器,例如您庫的頂級包或模組的__name__。記錄到根記錄器將使應用程式開發人員難以或無法根據自己的意願配置您庫的日誌記錄詳細程度或處理程式。

備註

強烈建議您*不要向庫的記錄器新增除*NullHandler*之外的任何處理程式*。這是因為處理程式的配置是使用您庫的應用程式開發人員的特權。應用程式開發人員瞭解他們的目標受眾以及哪種處理程式最適合他們的應用程式:如果您“暗中”新增處理程式,您很可能會干擾他們進行單元測試和交付符合其要求的日誌的能力。

日誌記錄級別

日誌級別的數值在下表中給出。如果您想定義自己的級別,並且需要它們相對於預定義級別具有特定的值,那麼這些級別主要才會有意義。如果您定義一個具有相同數值的級別,它會覆蓋預定義的值;預定義的名稱將丟失。

級別

數值

CRITICAL

50

ERROR

40

WARNING

30

INFO

20

DEBUG

10

NOTSET

0

級別也可以與記錄器關聯,由開發人員設定或透過載入儲存的日誌記錄配置設定。當在記錄器上呼叫日誌記錄方法時,記錄器將其自身級別與與方法呼叫關聯的級別進行比較。如果記錄器的級別高於方法呼叫的級別,則實際上不會生成任何日誌記錄訊息。這是控制日誌輸出詳細程度的基本機制。

日誌訊息編碼為 LogRecord 類的例項。當記錄器決定實際記錄一個事件時,會從日誌訊息中建立一個 LogRecord 例項。

日誌訊息透過使用*處理程式*進行分發,處理程式是 Handler 類的子類的例項。處理程式負責確保日誌訊息(以 LogRecord 的形式)最終到達對該訊息的目標受眾(例如終端使用者、支援人員、系統管理員、開發人員)有用的特定位置(或一組位置)。處理程式被傳遞用於特定目標的 LogRecord 例項。每個記錄器都可以關聯零個、一個或多個處理程式(透過 LoggeraddHandler() 方法)。除了直接與記錄器關聯的任何處理程式之外,*與記錄器所有祖先關聯的所有處理程式*都會被呼叫以分發訊息(除非記錄器的 *propagate* 標誌設定為 false 值,此時傳遞給祖先處理程式的操作將停止)。

就像記錄器一樣,處理程式也可以具有與之關聯的級別。處理程式的級別以與記錄器級別相同的方式充當過濾器。如果處理程式決定實際分派事件,則使用 emit() 方法將訊息傳送到其目的地。大多數使用者定義的 Handler 子類都需要覆蓋此 emit()

自定義級別

定義自己的級別是可能的,但應該不是必需的,因為現有級別是根據實踐經驗選擇的。但是,如果您確信需要自定義級別,則在這樣做時應格外小心,並且如果您正在開發庫,則定義自定義級別可能*是一個非常糟糕的主意*。這是因為如果多個庫作者都定義了自己的自定義級別,那麼一起使用的這些多個庫的日誌輸出可能會讓使用它的開發人員難以控制和/或解釋,因為給定的數值對於不同的庫可能意味著不同的東西。

有用的處理器

除了基本的 Handler 類之外,還提供了許多有用的子類

  1. StreamHandler 例項將訊息傳送到流(類似檔案的物件)。

  2. FileHandler 例項將訊息傳送到磁碟檔案。

  3. BaseRotatingHandler 是在某個點輪換日誌檔案的處理程式的基類。它不應直接例項化。而是使用 RotatingFileHandlerTimedRotatingFileHandler

  4. RotatingFileHandler 例項將訊息傳送到磁碟檔案,支援最大日誌檔案大小和日誌檔案輪換。

  5. TimedRotatingFileHandler 例項將訊息傳送到磁碟檔案,並以特定時間間隔輪換日誌檔案。

  6. SocketHandler 例項將訊息傳送到 TCP/IP 套接字。自 3.4 版起,也支援 Unix 域套接字。

  7. DatagramHandler 例項將訊息傳送到 UDP 套接字。自 3.4 版起,也支援 Unix 域套接字。

  8. SMTPHandler 例項將訊息傳送到指定的電子郵件地址。

  9. SysLogHandler 例項將訊息傳送到 Unix syslog 守護程序,可能在遠端機器上。

  10. NTEventLogHandler 例項將訊息傳送到 Windows NT/2000/XP 事件日誌。

  11. MemoryHandler 例項將訊息傳送到記憶體緩衝區,當滿足特定條件時會將其重新整理。

  12. HTTPHandler 例項使用 GETPOST 語義將訊息傳送到 HTTP 伺服器。

  13. WatchedFileHandler 例項監視它們正在記錄的檔案。如果檔案更改,它將關閉並使用檔名重新開啟。此處理程式僅在類 Unix 系統上有用;Windows 不支援所使用的底層機制。

  14. QueueHandler 例項將訊息傳送到佇列,例如在 queuemultiprocessing 模組中實現的佇列。

  15. NullHandler 例項不處理錯誤訊息。它們由想要使用日誌記錄但希望避免“未找到記錄器 *XXX* 的處理程式”訊息的庫開發人員使用,該訊息可能會在庫使用者未配置日誌記錄時顯示。有關更多資訊,請參閱為庫配置日誌記錄

3.1 版新增: NullHandler 類。

3.2 版新增: QueueHandler 類。

NullHandlerStreamHandlerFileHandler 類在核心日誌記錄包中定義。其他處理程式在子模組 logging.handlers 中定義。(還有一個子模組 logging.config,用於配置功能。)

日誌訊息透過 Formatter 類的例項進行格式化以供顯示。它們使用適合與 % 運算子和字典一起使用的格式字串進行初始化。

為了批次格式化多條訊息,可以使用 BufferingFormatter 的例項。除了格式字串(應用於批處理中的每條訊息)之外,還提供了頭部和尾部格式字串。

當基於日誌器級別和/或處理器級別的過濾不足時,可以將 Filter 例項新增到 LoggerHandler 例項(透過它們的 addFilter() 方法)。在決定是否進一步處理訊息之前,日誌器和處理器都會諮詢它們所有的過濾器以獲得許可。如果任何過濾器返回一個假值,則訊息將不會被進一步處理。

基本的 Filter 功能允許按特定的日誌器名稱進行過濾。如果使用此功能,傳送到指定日誌器及其子日誌器的訊息將透過過濾器,而所有其他訊息將被丟棄。

日誌記錄期間引發的異常

日誌記錄包旨在吞噬在生產環境中日誌記錄時發生的異常。這是為了防止在處理日誌記錄事件時發生的錯誤(例如日誌記錄配置錯誤、網路錯誤或其他類似錯誤)導致使用日誌記錄的應用程式過早終止。

SystemExitKeyboardInterrupt 異常永遠不會被吞噬。在 Handler 子類的 emit() 方法期間發生的其他異常將傳遞給其 handleError() 方法。

HandlerhandleError() 的預設實現會檢查模組級變數 raiseExceptions 是否已設定。如果已設定,則會將堆疊跟蹤列印到 sys.stderr。如果未設定,則異常將被吞噬。

備註

raiseExceptions 的預設值為 True。這是因為在開發過程中,您通常希望收到任何發生的異常通知。建議您在生產環境中使用時將 raiseExceptions 設定為 False

使用任意物件作為訊息

在前面的章節和示例中,假設記錄事件時傳遞的訊息是字串。然而,這並非唯一可能。您可以傳遞任意物件作為訊息,當日志系統需要將其轉換為字串表示時,將呼叫其 __str__() 方法。實際上,如果您願意,可以完全避免計算字串表示——例如,SocketHandler 透過將其序列化並透過網路傳送來發出事件。

最佳化

訊息引數的格式化會延遲到無法避免為止。然而,計算傳遞給日誌記錄方法的引數也可能很耗時,如果日誌器只是丟棄您的事件,您可能希望避免這樣做。要決定如何操作,您可以呼叫 isEnabledFor() 方法,該方法接受一個級別引數,並且如果日誌器會為該級別的呼叫建立事件,則返回 True。您可以編寫如下程式碼:

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

這樣,如果日誌器的閾值設定在 DEBUG 以上,則永遠不會呼叫 expensive_func1expensive_func2

備註

在某些情況下,isEnabledFor() 本身可能比您期望的更耗時(例如,對於巢狀很深的日誌器,其中顯式級別只在日誌器層次結構的高層設定)。在這種情況下(或者如果您想在緊密迴圈中避免呼叫方法),您可以將呼叫 isEnabledFor() 的結果快取到區域性或例項變數中,並使用該變數代替每次呼叫方法。這樣的快取值只有在應用程式執行時日誌配置動態更改時才需要重新計算(這種情況並不常見)。

對於需要更精確控制收集哪些日誌資訊的特定應用程式,還可以進行其他最佳化。以下是您可以採取的一些措施,以避免在日誌記錄過程中處理您不需要的資訊:

您不想收集什麼

如何避免收集

有關呼叫來源的資訊。

logging._srcfile 設定為 None。這可以避免呼叫 sys._getframe(),這可能有助於在 PyPy 等環境中加快程式碼速度(PyPy 無法加速使用 sys._getframe() 的程式碼)。

執行緒資訊。

logging.logThreads 設定為 False

當前程序 ID (os.getpid())

logging.logProcesses 設定為 False

使用 multiprocessing 管理多個程序時的當前程序名稱。

logging.logMultiprocessing 設定為 False

使用 asyncio 時的當前 asyncio.Task 名稱。

logging.logAsyncioTasks 設定為 False

另外請注意,核心日誌模組只包含基本處理器。如果您不匯入 logging.handlerslogging.config,它們將不會佔用任何記憶體。

其他資源

參見

模組 logging

logging 模組的 API 參考。

模組 logging.config

日誌模組的配置 API。

模組 logging.handlers

日誌模組附帶的有用的處理器。

日誌記錄指南