Python Logging 指南

wcode發表於2018-07-11

文章翻譯自官方文件:Logging HOWTO

基礎日誌教程

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

何時使用日誌

Logging 為簡單的日誌記錄使用提供了一組便利功能。它們是 debug(), info(), warning(), error()critical()。要確定何時使用日誌記錄,請參閱下表,其中列出了針對一組常見任務中的每個任務的最佳工具。

您要執行的任務 這項任務的最佳工具
顯示控制檯輸出,以便正常使用命令列指令碼或程式 print()
報告在程式正常執行期間發生的事件(例如,用於狀態監測或故障調查) logging.info()(或者 logging.debug() 用於非常詳細的輸出以用於診斷目的)
發出有關特定執行時事件的警告 warnings.warn(): 在程式碼庫中,如果問題是可以避免的,則應修改客戶端應用程式以消除警告

logging.warning(): 如果客戶端應用程式無法處理該情況,但仍應注意該事件
報告有關特定執行時事件的錯誤 丟擲異常
報告在不引發異常的情況下抑制錯誤(例如,長時間執行的伺服器程式中的錯誤處理程式) logging.error(), logging.exception()logging.critical() 適用於特定錯誤和應用程式域

日誌函式以它們用於跟蹤的事件的級別或嚴重性命名。標準級別及其適用性描述如下(按嚴重程度遞增):

級別 什麼時候使用
DEBUG 詳細資訊,通常僅在診斷問題時才有意義。
INFO 確認事情按預期工作。
WARNING 表明發生了意外情況,或表明在不久的將來出現了一些問題(例如 “磁碟空間不足”)。但是該軟體仍在按預期工作。
ERROR 由於更嚴重的問題,該軟體無法執行某些功能。
CRITICAL 嚴重錯誤,表明程式本身可能無法繼續執行。

預設級別為 WARNING ,這意味著將僅跟蹤此級別及更高階別的事件,除非日誌包已配置為執行其他操作。

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

一個簡單的例子

一個非常簡單的例子:

import logging
logging.warning('Watch out!')  # 將列印訊息到控制檯
logging.info('I told you so')  # 不會列印任何東西
複製程式碼

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

WARNING:root:Watch out!
複製程式碼

列印在控制檯上。 INFO 訊息不會出現,因為預設級別為 WARNING 。列印的訊息包括記錄呼叫中提供的事件的級別和描述的指示,即 “Watch out!”。暫時不要擔心 'root' 部分:它將在後面解釋。如果需要,可以非常靈活地格式化實際輸出;格式化選項也將在稍後解釋。

記錄到檔案

一種非常常見的情況是在檔案中記錄日誌事件,所以讓我們看看下一步。請務必在新啟動的 Python 直譯器中嘗試以下操作,並且不要只繼續上述會話:

import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
複製程式碼

現在,如果我們開啟檔案並檢視我們的內容,我們應該找到日誌訊息:

DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too
複製程式碼

此示例還說明了如何設定作為跟蹤閾值的日誌記錄級別。在這種情況下,因為我們將閾值設定為 DEBUG ,所以列印了所有訊息。

如果要從命令列選項設定日誌記錄級別,例如:

--log=INFO
複製程式碼

並且你有一個變數 loglevel--log 傳遞的引數的值,你可以使用:

getattr(logging, loglevel.upper())
複製程式碼

通過 loglevel 引數獲取您將傳遞給 basicConfig() 的值。您可能還希望檢查使用者的輸入值,如下例所示:

# 假設 loglevel 是從命令列引數中獲取的字串值。 轉換為大寫以允許使用者
# 指定 --log=DEBUG 或 --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)
複製程式碼

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

多個模組中的日誌記錄

如果您的程式包含多個模組,這裡有一個如何組織日誌記錄的示例:

# 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')
複製程式碼

如果你執行 myapp.py,你應該在 myapp.log 中看到這個:

INFO:root:Started
INFO:root:Doing something
INFO:root:Finished
複製程式碼

希望你們能看到。您可以使用 mylib.py 中的模式將此概括為多個模組。請注意,對於這種簡單的使用模式,除了檢視事件描述之外,僅僅通過檢視日誌檔案,您不會知道您的訊息來自應用程式中的何處。如果要跟蹤訊息的位置,則需要參考教程級別之外的文件 -- 請參閱高階日誌教程

記錄變數資料

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

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

下一步

基本教程到此結束。它應該足以讓您啟動並執行 logging。日誌包提供了更多功能,但為了充分利用它,您需要花費更多的時間來閱讀以下部分。如果你準備好了,可以拿一些你最喜歡的飲料繼續。

如果您的日誌記錄需求很簡單,那麼使用上面的示例將日誌記錄合併到您自己的指令碼中基本就可以了。

還在?您可以繼續閱讀接下來的幾個部分,這些部分提供了比上面基本部分更高階/深入的教程。之後,您可以檢視 Logging Cookbook

高階日誌教程

日誌庫採用模組化方法,並提供幾類元件:記錄器 (loggers),處理器 (handlers),過濾器 (filters) 和格式化器 (formatters)。

  • 記錄器公開應用程式程式碼直接使用的介面。
  • 處理器將日誌(由記錄器建立)傳送到適當的目標。
  • 過濾器提供了更精細的設施,用於確定要輸出的日誌記錄。
  • 格式化器指定最終輸出中的日誌記錄的佈局。

日誌事件資訊在 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() 來更改此設定。有關如何構造格式字串的所有選項,請參閱格式化物件

記錄流程

記錄器和處理器中的日誌事件資訊流程如下圖所示。

Python Logging 指南

記錄器

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

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

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

  • Logger.setLevel() 指定記錄器將處理的日誌級別,其中 debug 是最低內建日誌級別,critical 是最高內建日誌級別。例如,如果日誌級別為 INFO,則記錄器將僅處理 INFO,WARNING,ERROR 和 CRITICAL 訊息,並將忽略 DEBUG 訊息。
  • Logger.addHandler()Logger.removeHandler() 從記錄器物件中新增和刪除處理器物件。
  • Logger.addFilter()Logger.removeFilter() 從記錄器物件中新增和刪除過濾器物件。

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

配置 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 來關閉傳播。)

處理器

處理器物件負責將適當的日誌訊息(基於日誌訊息的嚴重性)分派給處理器的指定目標。Logger 物件可以使用 addHandler() 方法向自身新增零個或多個處理器物件。作為示例場景,應用程式可能希望將所有日誌訊息傳送到日誌檔案,將錯誤或更高的所有日誌訊息傳送到標準輸出,以及將至關重要的所有訊息傳送到電子郵箱。此方案需要三個單獨的處理器,其中每個處理器負責將特定嚴重性的訊息傳送到特定位置。

標準庫包含很多處理器型別(請參閱常用處理器);這些教程在其示例中主要使用 StreamHandlerFileHandler

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

  • 與記錄器物件一樣,setLevel() 方法指定將分派到適當目標的最低嚴重性。為什麼有兩個 setLevel() 方法?記錄器中設定的級別確定將傳遞給其處理器的訊息的嚴重性。而每個處理器中設定的級別確定處理器將傳送哪些訊息。
  • setFormatter() 選擇要使用的此處理器的 Formatter 物件。
  • addFilter()removeFilter() 分別在處理器上配置和取消配置過濾器物件。

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

格式化器

Formatter 物件配置日誌訊息的最終順序,結構和內容。與基本 logging.Handler 類不同,應用程式程式碼可以例項化 formatter 類,但如果應用程式需要特殊行為,則可能會對 formatter 進行子類化。建構函式有三個可選引數 - 訊息格式字串,日期格式字串和樣式指示符。

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

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

%Y-%m-%d %H:%M:%S
複製程式碼

最後加上毫秒數。樣式是 %'{''$' 之一。如果未指定其中一個,則使用 '%'

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

Python 3.2 中新增了 style 引數。

以下訊息格式字串將按以下順序以人類可讀的格式記錄時間,訊息的嚴重性和訊息的內容:

'%(asctime)s - %(levelname)s - %(message)s'
複製程式碼

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

配置日誌記錄

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

  1. 呼叫上面列出的配置方法顯式建立記錄器,處理器和格式化器。
  2. 建立日誌配置檔案並使用 fileConfig() 函式讀取它。
  3. 建立配置資訊字典並將其傳遞給 dictConfig() 函式。

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

import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.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.warn('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.warn('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
datefmt=
複製程式碼

輸出幾乎與基於非配置檔案的示例相同:

$ 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 程式碼中構建字典,通過套接字以序列化形式接收它,或者使用對您的應用程式有意義的任何方法。

以下是與上述相同配置的示例,採用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]
複製程式碼

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

如果沒有提供配置會發生什麼

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

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

  • 如果 logging.raiseExceptionsFalse(生產模式),則會以靜默方式刪除該事件。
  • 如果 logging.raiseExceptionsTrue(開發模式),則會列印一條訊息 “無法找到記錄器 X.Y.Z 的處理器”。

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

  • 該事件使用 “最後的處理器” 輸出,儲存在 logging.lastResort 中。此內部處理器不與任何記錄器關聯,並且像 StreamHandler 一樣將事件描述訊息寫入 sys.stderr 的當前值(因此尊重可能有效的任何重定向)。沒有對訊息進行格式化 - 只列印裸事件描述訊息。處理程式的級別設定為 WARNING,因此將輸出此級別和更高階別的所有事件。

要獲取 3.2 之前的行為,logging.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”。

?> 注意:強烈建議您不要將 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 例項使用 GET 或 POST 語義將訊息傳送到 HTTP 伺服器。
  13. WatchedFileHandler 例項監視他們要記錄的檔案。如果檔案發生更改,則會關閉該檔案並使用檔名重新開啟。此處理程式僅在類 Unix 系統上有用; Windows 不支援使用的基礎機制。
  14. QueueHandler 例項將訊息傳送到佇列,例如佇列或多處理模組中實現的佇列。
  15. NullHandler 例項不會對錯誤訊息執行任何操作。

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

記錄的訊息被格式化以便通過 Formatter 類的例項進行呈現。它們使用適合與 % 運算子和字典一起使用的格式字串進行初始化。

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

當基於記錄器級別和(或)處理器級別的過濾不夠時,可以將過濾器的例項新增到 LoggerHandler 例項(通過他們的 addFilter() 方法)。在決定進一步處理訊息之前,記錄器和處理器都會查詢其所有過濾器以獲取許可權。如果任何過濾器返回 false 值,則不會進一步處理該訊息。

基本的過濾器功能允許按特定的記錄器名稱進行過濾。如果使用此功能,則允許通過過濾器傳送到指定記錄器及其子項的訊息,並刪除所有其他訊息。

記錄期間引發的異常

日誌包旨在吞噬登入生產時發生的異常。這樣可以在處理日誌記錄事件時發生錯誤 - 例如記錄錯誤配置,網路或其他類似錯誤 - 不要導致使用日誌記錄的應用程式過早終止。

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

HandlerhandleError() 的預設實現檢查是否設定了模組級變數 raiseExceptions。如果設定,則會向 sys.stderr 列印回溯。如果未設定,則吞下異常。

?> 注意:raiseExceptions 的預設值為 True。這是因為在開發期間,您通常希望收到發生的任何異常的通知。建議您將生產使用的 raiseExceptions 設定為 False

使用任意物件作為訊息

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

相關文章