如何使用日誌記錄 (Logging)¶
此頁面包含教學資訊。有關參考資訊和日誌記錄手冊的連結,請參閱 其他資源。
基礎日誌記錄教學¶
日誌記錄是一種追蹤軟體運行時有發生過哪些事件的手段。軟體開發者會在程式碼中加入日誌記錄呼叫,以表示特定事件已經發生。事件會以一段描述訊息來說明,訊息中可以選擇性地包含變數資料(即每次事件發生時都可能帶有不同的資料)。開發者也會為事件指定重要程度,重要程度也可以稱為等級 (level) 或嚴重性 (severity)。
何時使用日誌記錄¶
你可以透過 logger = logging.getLogger(__name__) 建立一個記錄器 (logger) 來存取日誌記錄功能,然後呼叫記錄器的 debug()、info()、warning()、error() 和 critical() 方法。為了確定何時該使用日誌記錄,以及何時該使用哪個記錄器方法,請參閱下表,表中針對每個常見任務列出最適合的工具。
你想要執行的任務 |
此任務最好的工具 |
|---|---|
為命令列腳本或程式的一般用途顯示控制台輸出 |
|
回報在程式正常運作期間發生的事件(例如用於狀態監控或故障調查) |
|
針對特定的 runtime 事件發出警告 |
如果問題是可以避免的,且客戶端應用程式應修改以消除此警告,請在函式庫程式碼中使用 如果客戶端應用程式無法處理該情況,但該事件仍應被注意,可以使用記錄器的 |
回報與特定 runtime 事件相關的錯誤 |
引發例外 |
回報一個被抑制而未引發例外的錯誤(例如長時間執行的伺服器行程中的錯誤處理程式) |
依特定錯誤及應用領域的需要,使用記錄器的 |
記錄器方法是以其所追蹤事件的等級或嚴重性來命名。以下說明各標準等級及其適用情境(依嚴重性遞增排序):
等級 |
何時使用 |
|---|---|
|
詳細的資訊,通常只在診斷問題時才會需要。 |
|
確認一切都按預期運作。 |
|
表示發生了非預期的事情,或預示近期可能會出現某些問題(例如「磁碟空間不足」)。軟體仍按預期運作。 |
|
因為有更嚴重的問題,軟體無法執行某些功能。 |
|
嚴重錯誤,表示此程式本身可能無法繼續執行。 |
預設等級為 WARNING,這表示除非 logging 套件被設定為其他行為,否則只會追蹤此嚴重性及以上的事件。
被追蹤的事件可以採取不同的處理方式。處理追蹤事件最簡單的方法是將它們輸出到控制台。另一個常見的方法是將它們寫入磁碟檔案。
一個簡單範例¶
一個非常簡單的例子是:
import logging
logging.warning('Watch out!') # 將會印出訊息至控制台
logging.info('I told you so') # 不會印出任何東西
若你將這幾行程式碼加入腳本中並且執行,你就能看到:
WARNING:root:Watch out!
這段訊息會印在控制台上。由於預設等級為 WARNING,所以 INFO 訊息並未出現。印出的訊息包含等級,以及在 logging 呼叫中提供的事件描述,即 'Watch out!'。如果需要,實際輸出可以非常彈性地格式化;格式化選項也會在稍後說明。
請注意,在這個範例中,我們直接在 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
且已將傳給 --log 的參數值存放在某個變數 loglevel 中,你可以使用:
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!
如你所見,將變數資料合併到事件描述訊息中使用的是舊的 %-style 字串格式化方式。這是為了向後相容:logging 套件的出現早於 str.format() 和 string.Template 等較新的格式化選項。這些較新的格式化選項確實受到支援,但探索這些選項超出了本教學的範圍:請參閱 Using particular formatting styles throughout your application 以獲得更多資訊。
變更顯示訊息的格式¶
如果你要變更顯示訊息使用的格式,需要指定你想使用的格式,如下:
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 套件還提供了更多功能,但如果想要充分利用它,你需要再花些時間閱讀以下章節。如果你準備好了,就拿杯你喜愛的飲料繼續閱讀吧。
如果你的日誌需求很簡單,可以參考上面的範例將 logging 加入你自己的腳本中。如果你遇到問題或不理解某些內容,可以在 Python 討論區 的 Help 分類中發問,應該很快就會得到幫助。
還在嗎?你可以繼續閱讀下面幾節,這比上面的基礎教學稍微進階/深入一些。之後,你可以看看 Logging Cookbook。
進階日誌記錄教學¶
日誌記錄函式庫採用模組化方法,並提供多種類型的元件:記錄器、處理器、過濾器和格式化器。
記錄器 (logger) 公開應用程式程式碼可以直接使用的介面。
處理器 (handler) 將(由記錄器建立的)日誌紀錄 (log record) 傳送到適當的目的地。
過濾器 (filter) 提供更精細的機制來決定要輸出哪些日誌紀錄。
格式化器 (formatter) 指定日誌紀錄在最終輸出中的格式。
日誌事件資訊在記錄器、處理器、過濾器和格式化器之間透過 LogRecord 實例傳遞。
日誌記錄是透過呼叫 Logger 類別實例(以下稱為 loggers)上的方法來進行。每個實例都有一個名稱,並在概念上以點(句號)作為分隔符號排列成命名空間階層。例如,名為 'scan' 的記錄器是記錄器 'scan.text'、'scan.html' 和 'scan.pdf' 的父記錄器。記錄器名稱可以取任何你想要的名稱,用來表示記錄訊息源自應用程式中的哪個區域。
命名記錄器的一個良好慣例,是在每個使用 logging 的模組中使用模組層級的記錄器,並如下命名:
logger = logging.getLogger(__name__)
這表示記錄器名稱會對應套件/模組的階層,因此光從記錄器名稱就能直觀地看出事件是在哪裡被記錄的。
記錄器階層的根部稱為根記錄器 (root logger)。這是由函式 debug()、info()、warning()、error() 和 critical() 使用的記錄器,這些函式僅呼叫根記錄器的同名方法。這些函式和方法具有相同的簽名。根記錄器的名稱在日誌輸出中顯示為 'root'。
當然,也可以將訊息記錄到不同的目的地。該套件支援將日誌訊息寫入檔案、HTTP GET/POST 位置、透過 SMTP 寄送的電子郵件、通用 socket、佇列,或像 syslog 或 Windows NT 事件日誌等作業系統特定的日誌機制。這些目的地由 handler 類別負責。如果你有內建處理器類別無法滿足的特殊需求,可以建立自己的日誌目的地類別。
預設情況下,對於所有的日誌訊息都沒有設定目的地。你可以使用教學範例中的 basicConfig() 指定目的地(例如控制台或檔案)。如果你呼叫 debug()、info()、warning()、error() 和 critical() 函式,它們會檢查是否已設定目的地,如果沒有,它們會設定目的地為控制台 (sys.stderr) 以及顯示訊息的預設格式,然後委派給根記錄器來進行實際的訊息輸出。
basicConfig() 預設設定的訊息格式為:
severity:logger name:message
你可以透過傳遞格式字串到 basicConfig() 的 format 關鍵字引數來進行更改。有關格式字串建構的所有選項,請參閱 Formatter Objects。
日誌記錄流程¶
下圖說明了日誌事件資訊在記錄器和處理器中的流程。
記錄器 (Loggers)¶
Logger 物件有三項職責。首先,它們向應用程式程式碼公開多個方法,讓應用程式可以在 runtime 記錄訊息。其次,記錄器物件根據嚴重性(預設的過濾機制)或過濾器物件來決定要處理哪些日誌訊息。第三,記錄器物件將相關的日誌訊息傳遞給所有感興趣的日誌處理器。
記錄器物件上最常使用的方法可分為兩類:設定和傳送訊息。
以下是最常見的設定方法:
Logger.setLevel()指定記錄器會處理的最低嚴重性日誌訊息,其中 debug 是內建最低的嚴重性等級,而 critical 是內建最高的嚴重性等級。例如,如果嚴重性等級是 INFO,記錄器只會處理 INFO、WARNING、ERROR 和 CRITICAL 訊息,並忽略 DEBUG 訊息。Logger.addHandler()和Logger.removeHandler()可以在記錄器物件中新增或移除處理器物件。有關處理器的詳細說明,請參閱 處理器 (Handlers)。Logger.addFilter()和Logger.removeFilter()分別是在記錄器物件中新增和移除過濾器物件。更多關於過濾器的細節可以參閱 Filter Objects。
你並不需要在每個你建立的記錄器上都呼叫這些方法,請參閱本節的最後兩段。
有了已設定的記錄器物件之後,下列這些方法可以產生日誌訊息:
Logger.debug()、Logger.info()、Logger.warning()、Logger.error()和Logger.critical()都會建立一筆日誌紀錄,其中包含訊息以及與各自方法名稱對應的等級。訊息實際上是格式字串,可以包含%s、%d、%f等標準字串替換語法。其餘的引數則是與訊息中替換欄位相對應的物件串列。至於**kwargs,記錄方法只關心exc_info這個關鍵字,並以它來決定是否記錄例外資訊。Logger.exception()建立的日誌訊息與Logger.error()相似。差別在於Logger.exception()會同時傾印出堆疊追蹤 (stack trace)。請只在例外處理程式中呼叫此方法。Logger.log()需要以明確的引數傳入日誌等級。相較於使用上面列出的各等級便捷方法,用這種方式記錄訊息會稍微冗長一些,但這是以自訂日誌等級進行記錄的方式。
getLogger() 會回傳指定名稱的記錄器實例的參照,若未提供名稱則回傳 root。名稱是以點號分隔的階層結構。以相同名稱多次呼叫 getLogger() 會回傳同一個記錄器物件的參照。在階層中位置較低的記錄器是位置較高之記錄器的子記錄器。例如,對於名稱為 foo 的記錄器,名稱為 foo.bar、foo.bar.baz 和 foo.bam 的記錄器都是 foo 的後代。
記錄器有有效等級 (effective level) 的概念。如果記錄器上沒有明確設定等級,則會改用其父記錄器的等級作為有效等級。如果父記錄器也沒有明確設定等級,就再檢查它的父記錄器,依此類推——會往上搜尋所有上層記錄器,直到找到明確設定的等級為止。根記錄器一定有明確設定的等級(預設為 WARNING)。在決定是否處理某個事件時,會使用記錄器的有效等級來判斷該事件是否要傳遞給記錄器的處理器。
子記錄器會將訊息向上傳播給與其上層記錄器關聯的處理器。因此,不需要為應用程式使用的所有記錄器定義和設定處理器,只需要為最上層的記錄器設定處理器,然後依需要建立子記錄器即可。(不過,你可以將記錄器的 propagate 屬性設為 False 來關閉傳播。)
處理器 (Handlers)¶
Handler 物件負責將適當的日誌訊息(依據日誌訊息的嚴重性)分派到處理器指定的目的地。Logger 物件可以使用 addHandler() 方法替自己新增零個或多個處理器物件。舉例來說,一個應用程式可能想要將所有日誌訊息發送到日誌檔、將所有 error 或更高等級的日誌訊息發送到 stdout,並將所有 critical 訊息發送到某個電子郵件地址。這個情境需要三個獨立的處理器,每個處理器負責將特定嚴重性的訊息發送到特定位置。
標準函式庫中包含了相當多的處理器型別(請見 有用的處理器),本教學範例主要會使用 StreamHandler 與 FileHandler。
對於應用程式開發者而言,需要關心的處理器方法非常少。使用內建的處理器物件(即未建立自訂處理器)時,似乎只有以下設定方法與應用程式開發者有關:
setLevel()方法與記錄器物件中的一樣,指定了會被分派到適當目的地的最低嚴重性。為什麼會有兩個setLevel()方法?記錄器中設定的等級決定了哪些嚴重性的訊息會被傳遞給它的處理器;每個處理器中設定的等級則決定了該處理器會將哪些訊息送出。setFormatter()方法可為此處理器選擇一個格式化器物件使用。addFilter()和removeFilter()分別用來在處理器上設定和移除過濾器物件。
應用程式程式碼不應直接實例化和使用 Handler 的實例。Handler 類別是一個基礎類別,它定義了所有處理器應具備的介面,並建立了一些子類別可以使用(或覆寫)的預設行為。
格式化器 (Formatters)¶
格式化器物件設定日誌訊息的最終順序、結構和內容。和基礎的 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 屬性(設為 time.gmtime 即可以 GMT 顯示)。
設定日誌¶
開發者有三種設定日誌的方式:
以 Python 程式碼呼叫上述設定方法來明確地建立記錄器、處理器和格式化器。
建立日誌設定檔,並使用
fileConfig()函式讀取它。建立一個包含設定資訊的字典,並將其傳遞給
dictConfig()函式。
請參閱 Configuration functions 來了解關於最後兩個選項的參考文件。以下範例使用 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 程式碼中建立字典、透過 socket 以 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]
欲了解更多使用字典進行日誌設定的資訊,請參閱 Configuration functions。
如果未提供設定,會發生什麼¶
如果沒有提供日誌設定,則可能會出現需要輸出日誌事件,但找不到處理器來輸出該事件的情況。
該事件會使用 'handler of last resort' 輸出,該處理器儲存在 lastResort 中。這個內部處理器不與任何記錄器關聯,作用類似於 StreamHandler,會將事件描述訊息寫入 sys.stderr 的目前值(因此會遵循任何可能生效中的重新導向)。訊息不會經過任何格式化——只會印出單純的事件描述訊息。該處理器的等級設為 WARNING,因此所有此嚴重性及以上的事件都會被輸出。
在 3.2 版的變更: 在 Python 3.2 之前的版本中,其行為如下:
如果
raiseExceptions為False(正式環境模式),該事件會被靜默捨棄。如果
raiseExceptions為True(開發模式),則會印出一次訊息 'No handlers could be found for logger X.Y.Z'。
若要恢復 3.2 之前的行為,可以將 lastResort 設為 None。
為函式庫設定日誌¶
開發使用 logging 的函式庫時,應該在文件中說明函式庫如何使用 logging,例如所使用的記錄器名稱。也需要考慮其日誌設定。如果使用該函式庫的應用程式沒有使用 logging,而函式庫程式碼有呼叫 logging,那麼(如上一節所述)嚴重性為 WARNING 及以上的事件會被印到 sys.stderr。這被視為最佳的預設行為。
如果因為某些原因,你不希望在沒有任何日誌設定的情況下印出這些訊息,你可以將一個什麼也不做的處理器附加到你函式庫的頂層記錄器。這樣可以避免印出訊息,因為函式庫的事件總會找到處理器:只是該處理器不會產生任何輸出。如果函式庫的使用者為應用程式設定了日誌,該設定大概會新增一些處理器,而如果等級設定得當,函式庫程式碼中的 logging 呼叫就會像平常一樣將輸出發送到這些處理器。
logging 套件中包含了一個什麼也不做的處理器:NullHandler(自 Python 3.1 起)。可以將此處理器的實例新增到函式庫所使用的 logging 命名空間的頂層記錄器中(如果你希望在沒有日誌設定的情況下,防止函式庫的日誌事件被輸出到 sys.stderr)。如果函式庫 foo 的所有日誌記錄都是透過名稱符合 'foo.x'、'foo.x.y' 等的記錄器來進行,那麼以下程式碼:
import logging
logging.getLogger('foo').addHandler(logging.NullHandler())
就能達到預期效果。如果一個組織開發了多個函式庫,則指定的記錄器名稱可以是 'orgname.foo',而不只是 'foo'。
備註
強烈建議你在函式庫中不要記錄到根記錄器。請改用一個具有獨特且易於識別名稱的記錄器,例如你函式庫頂層套件或模組的 __name__。記錄到根記錄器會讓應用程式開發者很難或無法依自己的需求設定你函式庫的日誌詳細程度或處理器。
備註
強烈建議你不要在你的函式庫記錄器中新增除 NullHandler 之外的處理器。這是因為處理器的設定是使用你函式庫的應用程式開發者的特權。應用程式開發者了解他們的目標受眾,也知道哪些處理器最適合他們的應用程式:如果你在「幕後」新增處理器,很可能會干擾他們進行單元測試,以及產出符合其需求之日誌的能力。
日誌等級¶
日誌等級的數值如下表所示。如果你想定義自己的等級,並需要它們相對於預定義等級具有特定的值,這些數值就很重要。如果你定義的等級與某個預定義等級的數值相同,它會覆寫該預定義的值;預定義的名稱也會因此消失。
等級 |
數值 |
|---|---|
|
50 |
|
40 |
|
30 |
|
20 |
|
10 |
|
0 |
等級也可以與記錄器關聯,由開發者設定或透過載入已儲存的日誌設定來設定。當在記錄器上呼叫記錄方法時,記錄器會將自己的等級與該方法呼叫所對應的等級進行比較。如果記錄器的等級高於方法呼叫的等級,就不會真的產生日誌訊息。這是控制日誌輸出詳細程度的基本機制。
日誌訊息被編碼為 LogRecord 類別的實例。當記錄器決定要實際記錄某個事件時,會從日誌訊息建立一個 LogRecord 實例。
Logging messages are subjected to a dispatch mechanism through the use of
handlers, which are instances of subclasses of the Handler
class. Handlers are responsible for ensuring that a logged message (in the form
of a LogRecord) ends up in a particular location (or set of locations)
which is useful for the target audience for that message (such as end users,
support desk staff, system administrators, developers). Handlers are passed
LogRecord instances intended for particular destinations. Each logger
can have zero, one or more handlers associated with it (via the
addHandler() method of Logger). In addition to any
handlers directly associated with a logger, all handlers associated with all
ancestors of the logger are called to dispatch the message (unless the
propagate flag for a logger is set to a false value, at which point the
passing to ancestor handlers stops).
Just as for loggers, handlers can have levels associated with them. A handler's
level acts as a filter in the same way as a logger's level does. If a handler
decides to actually dispatch an event, the emit() method is used
to send the message to its destination. Most user-defined subclasses of
Handler will need to override this emit().
自訂等級¶
Defining your own levels is possible, but should not be necessary, as the existing levels have been chosen on the basis of practical experience. However, if you are convinced that you need custom levels, great care should be exercised when doing this, and it is possibly a very bad idea to define custom levels if you are developing a library. That's because if multiple library authors all define their own custom levels, there is a chance that the logging output from such multiple libraries used together will be difficult for the using developer to control and/or interpret, because a given numeric value might mean different things for different libraries.
有用的處理器¶
除了基礎的 Handler 類別,還提供了許多有用的子類別:
StreamHandler實例會將訊息傳送到串流(類檔案物件)中。FileHandler實例會將訊息傳送至磁碟檔案。BaseRotatingHandleris the base class for handlers that rotate log files at a certain point. It is not meant to be instantiated directly. Instead, useRotatingFileHandlerorTimedRotatingFileHandler.RotatingFileHandlerinstances send messages to disk files, with support for maximum log file sizes and log file rotation.TimedRotatingFileHandlerinstances send messages to disk files, rotating the log file at certain timed intervals.SocketHandlerinstances send messages to TCP/IP sockets. Since 3.4, Unix domain sockets are also supported.DatagramHandlerinstances send messages to UDP sockets. Since 3.4, Unix domain sockets are also supported.SMTPHandlerinstances send messages to a designated email address.SysLogHandlerinstances send messages to a Unix syslog daemon, possibly on a remote machine.NTEventLogHandlerinstances send messages to a Windows NT/2000/XP event log.MemoryHandlerinstances send messages to a buffer in memory, which is flushed whenever specific criteria are met.HTTPHandlerinstances send messages to an HTTP server using eitherGETorPOSTsemantics.WatchedFileHandlerinstances watch the file they are logging to. If the file changes, it is closed and reopened using the file name. This handler is only useful on Unix-like systems; Windows does not support the underlying mechanism used.QueueHandlerinstances send messages to a queue, such as those implemented in thequeueormultiprocessingmodules.NullHandlerinstances do nothing with error messages. They are used by library developers who want to use logging, but want to avoid the 'No handlers could be found for logger XXX' message which can be displayed if the library user has not configured logging. See 為函式庫設定日誌 for more information.
在 3.1 版被加入: The NullHandler class.
在 3.2 版被加入: The QueueHandler class.
The NullHandler, StreamHandler and FileHandler
classes are defined in the core logging package. The other handlers are
defined in a sub-module, logging.handlers. (There is also another
sub-module, logging.config, for configuration functionality.)
Logged messages are formatted for presentation through instances of the
Formatter class. They are initialized with a format string suitable for
use with the % operator and a dictionary.
For formatting multiple messages in a batch, instances of
BufferingFormatter can be used. In addition to the format
string (which is applied to each message in the batch), there is provision for
header and trailer format strings.
When filtering based on logger level and/or handler level is not enough,
instances of Filter can be added to both Logger and
Handler instances (through their addFilter() method).
Before deciding to process a message further, both loggers and handlers consult
all their filters for permission. If any filter returns a false value, the
message is not processed further.
The basic Filter functionality allows filtering by specific logger
name. If this feature is used, messages sent to the named logger and its
children are allowed through the filter, and all others dropped.
Exceptions raised during logging¶
The logging package is designed to swallow exceptions which occur while logging in production. This is so that errors which occur while handling logging events - such as logging misconfiguration, network or other similar errors - do not cause the application using logging to terminate prematurely.
SystemExit and KeyboardInterrupt exceptions are never
swallowed. Other exceptions which occur during the emit() method
of a Handler subclass are passed to its handleError()
method.
The default implementation of handleError() in Handler
checks to see if a module-level variable, raiseExceptions, is set. If
set, a traceback is printed to sys.stderr. If not set, the exception is
swallowed.
備註
The default value of raiseExceptions is True. This is
because during development, you typically want to be notified of any
exceptions that occur. It's advised that you set raiseExceptions to
False for production usage.
Using arbitrary objects as messages¶
In the preceding sections and examples, it has been assumed that the message
passed when logging the event is a string. However, this is not the only
possibility. You can pass an arbitrary object as a message, and its
__str__() method will be called when the logging system needs to
convert it to a string representation. In fact, if you want to, you can avoid
computing a string representation altogether - for example, the
SocketHandler emits an event by pickling it and sending it
over the wire.
最佳化¶
Formatting of message arguments is deferred until it cannot be avoided.
However, computing the arguments passed to the logging method can also be
expensive, and you may want to avoid doing it if the logger will just throw
away your event. To decide what to do, you can call the
isEnabledFor() method which takes a level argument and returns
true if the event would be created by the Logger for that level of call.
You can write code like this:
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Message with %s, %s', expensive_func1(),
expensive_func2())
so that if the logger's threshold is set above DEBUG, the calls to
expensive_func1 and expensive_func2 are never made.
備註
In some cases, isEnabledFor() can itself be more
expensive than you'd like (e.g. for deeply nested loggers where an explicit
level is only set high up in the logger hierarchy). In such cases (or if you
want to avoid calling a method in tight loops), you can cache the result of a
call to isEnabledFor() in a local or instance variable, and use
that instead of calling the method each time. Such a cached value would only
need to be recomputed when the logging configuration changes dynamically
while the application is running (which is not all that common).
There are other optimizations which can be made for specific applications which need more precise control over what logging information is collected. Here's a list of things you can do to avoid processing during logging which you don't need:
What you don't want to collect |
How to avoid collecting it |
|---|---|
Information about where calls were made from. |
Set |
Threading information. |
將 |
目前的行程 ID ( |
將 |
Current process name when using |
將 |
Current |
將 |
Also note that the core logging module only includes the basic handlers. If
you don't import logging.handlers and logging.config, they won't
take up any memory.
其他資源¶
也參考
logging模組API reference for the logging module.
logging.config模組Configuration API for the logging module.
logging.handlers模組Useful handlers included with the logging module.