日誌記錄 HOWTO

作者:

Vinay Sajip <vinay_sajip at red-dove dot com>

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

基本日誌記錄教程

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

何時使用日誌記錄

您可以透過 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。如果您希望每次執行都重新開始,而不記住早期執行的訊息,則可以指定 filemode 引數,方法是將上述示例中的呼叫更改為

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() 支援的格式相同。

下一步

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

如果您的日誌記錄需求很簡單,那麼請使用上面的示例將日誌記錄整合到您自己的指令碼中,如果您遇到問題或不理解某些內容,請在 comp.lang.python Usenet 組(可在 https://groups.google.com/g/comp.lang.python 中找到)上釋出問題,您應該很快就會得到幫助。

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

高階日誌記錄教程

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

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

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

  • 過濾器提供更精細的工具來確定要輸出哪些日誌記錄。

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

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

日誌記錄是透過呼叫 Logger 類(以下稱為記錄器)的例項上的方法來執行的。每個例項都有一個名稱,它們在概念上使用點(句點)作為分隔符排列在名稱空間層次結構中。例如,名為 “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.setLevel() 指定記錄器將處理的最低嚴重性日誌訊息,其中 debug 是最低的內建嚴重性級別,而 critical 是最高的內建嚴重性級別。例如,如果嚴重性級別為 INFO,則記錄器將僅處理 INFO、WARNING、ERROR 和 CRITICAL 訊息,而忽略 DEBUG 訊息。

  • Logger.addHandler()Logger.removeHandler() 從記錄器物件新增和刪除處理程式物件。處理程式在 處理程式 中進行了更詳細的介紹。

  • Logger.addFilter()Logger.removeFilter() 從記錄器物件新增和刪除過濾器物件。過濾器在 過濾器物件 中進行了更詳細的介紹。

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

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

  • 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 類是一個基類,它定義了所有處理程式都應具有的介面,並建立了一些子類可以使用的(或覆蓋的)預設行為。

格式化器

Formatter 物件配置日誌訊息的最終順序、結構和內容。與基礎 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

請注意,配置檔案中引用的類名需要是相對於 logging 模組的相對值,或者是可以使用普通匯入機制解析的絕對值。因此,您可以使用 WatchedFileHandler(相對於 logging 模組)或 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 版本中更改: 對於 3.2 之前的 Python 版本,行為如下:

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

  • 如果 raiseExceptionsTrue(開發模式),則會列印一次訊息“找不到記錄器 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 例項。每個記錄器可以有零個、一個或多個與之關聯的處理器(透過 addHandler() 方法,該方法屬於 Logger)。除了直接與記錄器關聯的任何處理器之外,所有與記錄器的所有祖先關聯的處理器都會被呼叫來分派訊息(除非記錄器的 propagate 標誌設定為假值,此時傳遞給祖先處理器的過程會停止)。

與記錄器一樣,處理器也可以有與之關聯的級別。處理器的級別充當過濾器,與記錄器的級別作用相同。如果處理器決定實際分派一個事件,則會使用 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 系統日誌守護程式,可能在遠端機器上。

  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() 方法)。在決定進一步處理訊息之前,記錄器和處理器都會查詢它們的所有過濾器以獲取許可。如果任何過濾器返回 false 值,則該訊息將不會被進一步處理。

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

記錄過程中引發的異常

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

SystemExitKeyboardInterrupt 異常永遠不會被吞下。在 emit() 方法中發生的其他異常,將傳遞給 Handler 子類的 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 這樣的環境中加速您的程式碼(無法加速使用 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

日誌模組的 API 參考。

模組 logging.config

日誌模組的配置 API。

模組 logging.handlers

日誌模組中包含的有用處理器。

日誌記錄手冊