目錄
日志指南
日志基礎教程
什么時候使用日志
一個簡單的例子
記錄日志到文件
從多個模塊記錄日志
記錄變量數據
更改顯示消息的格式
在消息中顯示日期/時間
后續步驟
進階日志教程
記錄流程
記錄器
處理器
格式器
配置日志記錄
如果沒有提供配置會發生什么
為庫配置日志
日志級別
自定義級別
有用的處理器
記錄日志時引發的異常
使用任意對象作為消息
優化
其他資源
日志指南
本頁面包含教學信息。 要獲取參考信息和日志記錄指導書的鏈接,請查看?其他資源。
日志基礎教程
日志是對軟件執行時所發生事件的一種追蹤方式。軟件開發人員對他們的代碼添加日志調用,借此來指示某事件的發生。一個事件通過一些包含變量數據的描述信息來描述(比如:每個事件發生時的數據都是不同的)。開發者還會區分事件的重要性,重要性也被稱為?等級?或?嚴重性。
什么時候使用日志
對于簡單的日志使用來說日志功能提供了一系列便利的函數。它們是?debug(),info(),warning(),error()?和?critical()。想要決定何時使用日志,請看下表,其中顯示了對于每個通用任務集合來說最好的工具。
你想要執行的任務 | 此任務最好的工具 |
---|---|
對于命令行或程序的應用,結果顯示在控制臺。 | print() |
在對程序的普通操作發生時提交事件報告(比如:狀態監控和錯誤調查) | logging.info()?函數(當有診斷目的需要詳細輸出信息時使用?logging.debug()?函數) |
提出一個警告信息基于一個特殊的運行時事件 | warnings.warn()?位于代碼庫中,該事件是可以避免的,需要修改客戶端應用以消除告警 logging.warning()?不需要修改客戶端應用,但是該事件還是需要引起關注 |
對一個特殊的運行時事件報告錯誤 | 引發異常 |
報告錯誤而不引發異常(如在長時間運行中的服務端進程的錯誤處理) | logging.error(),?logging.exception()?或?logging.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
?。打印的信息包含事件的級別以及在日志調用中的對于事件的描述,例如 “Watch out!”。暫時不用擔心 “root” 部分:之后會作出解釋。輸出格式可按需要進行調整,格式化選項同樣會在之后作出解釋。
記錄日志到文件
一種很常見的情況是將日志事件記錄到文件中,下面讓我們來看這個問題。 請確認在一個新啟動的 Python 解釋器中嘗試以下操作,而非在上面描述的會話中繼續:
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?參數。在更早的 Python 版本中或沒有指定時,編碼會用?open()?使用的默認值。盡管在上面的例子中沒有展示,但也可以傳入一個決定如何處理編碼錯誤的?errors?參數。可使用的值和默認值,請參照?open()?的文檔。
現在,如果我們打開日志文件,我們應當能看到日志信息:
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?
該示例同樣展示了如何設置日志追蹤級別的閾值。該示例中,由于我們設置的閾值是?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()?等之前。否則,這些函數會替你用默認配置調用?basicConfig()。它被設計為一次性的配置,只有第一次調用會進行操作,隨后的調用不會產生有效操作。
如果多次運行上述腳本,則連續運行的消息將追加到文件?example.log?。 如果你希望每次運行重新開始,而不是記住先前運行的消息,則可以通過將上例中的調用更改為來指定?filemode?參數:
logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)
輸出將與之前相同,但不再追加進日志文件,因此早期運行的消息將丟失。
從多個模塊記錄日志
如果你的程序包含多個模塊,這里有一個如何組織日志記錄的示例:
# myapp.py import logging import mylibdef 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 loggingdef 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!
如你所見,將可變數據合并到事件描述消息中使用舊的 %-s形式的字符串格式化。 這是為了向后兼容:logging 包的出現時間早于較新的格式化選項例如?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 包提供了更多功能,但為了充分利用它,你需要花更多的時間來閱讀以下部分。 如果你準備好了,可以拿一些你最喜歡的飲料然后繼續。
如果你的日志記錄需要很簡單,那就使用上術的示例將日志記錄整合到你自己的腳本中,如果你遇到問題或有不理解的地方,請在 comp.lang.python Usenet 群組發帖提問(訪問?https://groups.google.com/g/comp.lang.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 事件日志)。 目標由?handler?類提供。 如果你有任何內置處理器類未滿足的特殊要求,則可以創建自己的日志目標類。
默認情況下,沒有為任何日志消息設置目標。 你可以使用?basicConfig()?指定目標(例如控制臺或文件),如教程示例中所示。 如果你調用函數?debug()?、?info()?、?warning()?、?error()?和?critical()?,它們將檢查是否有設置目標;如果沒有設置,將在委托給根記錄器進行實際的消息輸出之前設置目標為控制臺(?sys.stderr
?)并設置顯示消息的默認格式。
由?basicConfig()?設置的消息默認格式為:
severity:logger name:message
你可以通過使用?format?參數將格式字符串傳遞給?basicConfig()?來更改此設置。有關如何構造格式字符串的所有選項,請參閱?格式器對象?。
記錄流程
記錄器和處理器中的日志事件信息流程如下圖所示。
記錄器
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.bar
?、?foo.bar.baz
?和?foo.bam
?的記錄器都是?foo
?子項。
記錄器具有?有效等級?的概念。如果未在記錄器上顯式設置級別,則使用其父記錄器的級別作為其有效級別。如果父記錄器沒有明確的級別設置,則檢查?其?父級。依此類推,搜索所有上級元素,直到找到明確設置的級別。根記錄器始終具有明確的級別配置(默認情況下為?WARNING
?)。在決定是否處理事件時,記錄器的有效級別用于確定事件是否傳遞給記錄器相關的處理器。
子記錄器將消息傳播到與其父級記錄器關聯的處理器。因此,不必為應用程序使用的所有記錄器定義和配置處理器。一般為頂級記錄器配置處理器,再根據需要創建子記錄器就足夠了。(但是,你可以通過將記錄器的?propagate?屬性設置為?False
?來關閉傳播。)
處理器
Handler?對象負責將適當的日志消息(基于日志消息的嚴重性)分派給處理器的指定目標。?Logger?對象可以使用?addHandler()?方法向自己添加零個或多個處理器對象。作為示例場景,應用程序可能希望將所有日志消息發送到日志文件,將錯誤或更高的所有日志消息發送到標準輸出,以及將所有關鍵消息發送至一個郵件地址。 此方案需要三個單獨的處理器,其中每個處理器負責將特定嚴重性的消息發送到特定位置。
標準庫包含很多處理器類型(參見?有用的處理器?);教程主要使用?StreamHandler?和?FileHandler?。
處理器中很少有方法可供應用程序開發人員使用。使用內置處理器對象(即不創建自定義處理器)的應用程序開發人員能用到的僅有以下配置方法:
-
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
?是?'%'
,?'{'
?或?'{TX-PL-LABEL}#x27;
?之一。 如果未指定其中之一,則將使用?'%'
。
如果?style
?為?'%'
,則消息格式字符串將使用?%(<dictionary?key>)s
?樣式的字符串替換;可用的鍵值記錄在?LogRecord 屬性?中。 如果樣式為?'{'
,則將假定消息格式字符串與?str.format()?兼容(使用關鍵字參數),而如果樣式為?'{TX-PL-LABEL}#x27;
?則消息格式字符串應當符合?string.Template.substitute()?的預期。
在 3.2 版更改:?添加?style
?形參。
以下消息格式字符串將以人類可讀的格式記錄時間、消息的嚴重性以及消息的內容,按此順序:
'%(asctime)s - %(levelname)s - %(message)s'
格式器通過用戶可配置的函數將記錄的創建時間轉換為元組。 默認情況下,使用?time.localtime()?;要為特定格式器實例更改此項,請將實例的?converter
?屬性設置為與?time.localtime()?或?time.gmtime()?具有相同簽名的函數。 要為所有格式器更改它,例如,如果你希望所有記錄時間都以 GMT 顯示,請在格式器類中設置?converter
?屬性(對于 GMT 顯示,設置為?time.gmtime
?)。
配置日志記錄
開發者可以通過三種方式配置日志記錄:
-
使用調用上面列出的配置方法的 Python 代碼顯式創建記錄器、處理器和格式器。
-
創建日志配置文件并使用?fileConfig()?函數讀取它。
-
創建配置信息字典并將其傳遞給?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.configlogging.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()?調用之前存在的任何非 root 記錄器被禁用。有關更多信息,請參閱參考文檔,如果需要,請將此參數指定為?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.StreamHandlerlevel: DEBUGformatter: simplestream: ext://sys.stdout loggers:simpleExample:level: DEBUGhandlers: [console]propagate: no root:level: DEBUGhandlers: [console]
有關使用字典進行日志記錄的更多信息,請參閱?配置函數。
如果沒有提供配置會發生什么
如果未提供日志記錄配置,則可能出現需要輸出日志記錄事件但無法找到輸出事件的處理器的情況。 在這些情況下,logging 包的行為取決于 Python 版本。
對于 3.2 之前的 Python 版本,行為如下:
-
如果?logging.raiseExceptions?為?
False
?(生產模式),則會以靜默方式丟棄該事件。 -
如果?logging.raiseExceptions?為?
True
?(開發模式),則會打印一條消息 'No handlers could be found for logger 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” 。
備注
強烈建議在你的庫中?不要將日志記錄到根記錄器,而為你的庫的最高層級包或模塊使用一個具有唯一的易識別的名稱——例如,__name__
——的記錄器。將日志記錄到根記錄器,會使應用程序開發人員按照他們的意愿配置你的庫的日志的詳細程度或處理器變得困難,或者完全不可能。
備注
強烈建議你?不要將?NullHandler?以外的任何處理器添加到庫的記錄器中?。這是因為處理器的配置是使用你的庫的應用程序開發人員的權利。應用程序開發人員了解他們的目標受眾以及哪些處理器最適合他們的應用程序:如果你在“底層”添加處理器,則可能會干擾他們執行單元測試和提供符合其要求的日志的能力。
日志級別
日志記錄級別的數值在下表中給出。如果你想要定義自己的級別,并且需要它們具有相對于預定義級別的特定值,那么這你可能對以下內容感興趣。如果你定義具有相同數值的級別,它將覆蓋預定義的值;預定義的名稱將失效。
級別 | 數值 |
---|---|
| 50 |
| 40 |
| 30 |
| 20 |
| 10 |
| 0 |
級別也可以與記錄器關聯,可以由開發人員設置,也可以通過加載保存的日志配置來設置。在記錄器上調用記錄方法時,記錄器會將自己的級別與調用的方法的級別進行比較。如果記錄器的級別高于調用的方法的級別,則實際上不會生成任何記錄消息。這是控制日志記錄輸出詳細程度的基本機制。
日志消息被編碼為?LogRecord?類的實例。當記錄器決定實際記錄一個事件時,將從記錄消息創建一個?LogRecord?實例。
使用?Handler?類的子例的實例?handlers,可以為日志消息建立分派機制。處理器負責確保記錄的消息(以?LogRecord?的形式)最終到達對該消息的目標受眾(如最終用戶、技術支持員工、系統管理員或開發人員)有用的一個或多個位置上。想要去到特定目標的?LogRecord?實例會被傳給相應的處理器。每個記錄器可以有零、一或多個與之相關聯的處理器(通過?Logger?的?addHandler()?方法)。除了與記錄器直接關聯的所有處理器之外,還會將消息分派給?記錄器的所有祖先關聯的各個處理器*(除非某個記錄器的 *propagate?旗標被設為假值,這將使向祖先的傳遞在其處終止)。
就像記錄器一樣,處理器可以具有與它們相關聯的級別。處理器的級別作為過濾器,其方式與記錄器級別相同。如果處理器決定調度一個事件,則使用?emit()
?方法將消息發送到其目標。大多數用戶定義的?Handler?子類都需要重載?emit()
?。
自定義級別
定義你自己的級別是可能的,但不一定是必要的,因為現有級別是根據實踐經驗選擇的。但是,如果你確信需要自定義級別,那么在執行此操作時應特別小心,如果你正在開發庫,則?定義自定義級別可能是一個非常糟糕的主意?。 這是因為如果多個庫作者都定義了他們自己的自定義級別,那么使用開發人員很難控制和解釋這些多個庫的日志記錄輸出,因為給定的數值對于不同的庫可能意味著不同的東西。
有用的處理器
作為?Handler?基類的補充,提供了很多有用的子類:
-
StreamHandler?實例發送消息到流(類似文件對象)。
-
FileHandler?實例將消息發送到硬盤文件。
-
BaseRotatingHandler?是輪換日志文件的處理器的基類。它并不應該直接實例化。而應該使用?RotatingFileHandler?或?TimedRotatingFileHandler?代替它。
-
RotatingFileHandler?實例將消息發送到硬盤文件,支持最大日志文件大小和日志文件輪換。
-
TimedRotatingFileHandler?實例將消息發送到硬盤文件,以特定的時間間隔輪換日志文件。
-
SocketHandler?實例將消息發送到 TCP/IP 套接字。從 3.4 開始,也支持 Unix 域套接字。
-
DatagramHandler?實例將消息發送到 UDP 套接字。從 3.4 開始,也支持 Unix 域套接字。
-
SMTPHandler?實例將消息發送到指定的電子郵件地址。
-
SysLogHandler?實例將消息發送到 Unix syslog 守護程序,可能在遠程計算機上。
-
NTEventLogHandler?實例將消息發送到 Windows NT/2000/XP 事件日志。
-
MemoryHandler?實例將消息發送到內存中的緩沖區,只要滿足特定條件,緩沖區就會刷新。
-
HTTPHandler?實例使用?
GET
?或?POST
?方法將消息發送到 HTTP 服務器。 -
WatchedFileHandler?實例會監視他們要寫入日志的文件。如果文件發生更改,則會關閉該文件并使用文件名重新打開。此處理器僅在類 Unix 系統上有用; Windows 不支持依賴的基礎機制。
-
QueueHandler?實例將消息發送到隊列,例如在?queue?或?multiprocessing?模塊中實現的隊列。
-
NullHandler?實例不對錯誤消息執行任何操作。 如果庫開發者希望使用日志記錄,但又希望避免出現“找不到日志記錄器?XXX?的處理句柄”消息則可以使用它們。 更多信息請參閱?為庫配置日志。
3.1 新版功能:?NullHandler?類。
3.2 新版功能:?QueueHandler?類。
The?NullHandler?、?StreamHandler?和?FileHandler?類在核心日志包中定義。其他處理器定義在?logging.handlers?中。(還有另一個子模塊?logging.config?,用于配置功能)
記錄的消息通過?Formatter?類的實例進行格式化后呈現。 它們使用能與 % 運算符一起使用的格式字符串和字典進行初始化。
要批量格式化多個消息,可以使用?BufferingFormatter
?的實例。除了格式字符串(應用于批處理中的每個消息)之外,還提供了標題和尾部格式字符串。
當基于記錄器級別和處理器級別的過濾不夠時,可以將?Filter?的實例添加到?Logger?和?Handler?實例(通過它們的?addFilter()?方法)。在決定進一步處理消息之前,記錄器和處理器都會查詢其所有過濾器以獲得許可。如果任何過濾器返回 false 值,則不會進一步處理該消息。
基本?Filter?的功能允許按特定的記錄器名稱進行過濾。如果使用此功能,則允許通過過濾器發送到指定記錄器及其子項的消息,并丟棄其他所有消息。
記錄日志時引發的異常
logging 包被設計為,當在生產環境下使用時,忽略記錄日志時發生的異常。這樣,處理與日志相關的事件時發生的錯誤(例如日志配置錯誤、網絡或其它類似錯誤)不會導致使用日志的應用程序終止。
SystemExit?和?KeyboardInterrupt?異常永遠不會被忽略。在?Handler?的子類的?emit()
?方法中發生的其它異常將被傳遞給其?handleError()
?方法。
Handler?中的?handleError()
?的默認實現是檢查是否設置了模塊級變量?raiseExceptions
。如有設置,則會將回溯打印到?sys.stderr。如果未設置,則忽略異常。
備注
raiseExceptions
?的默認值是?True
。這是因為在開發期間,你通常想要在發生異常時收到通知。建議你將?raiseExceptions
?設為?False
?供生產環境下使用。
使用任意對象作為消息
在前面的部分和示例中,都假設記錄事件時傳遞的消息是字符串。 但是,這不是唯一的可能性。你可以將任意對象作為消息傳遞,并且當日志記錄系統需要將其轉換為字符串表示時,將調用其?__?str__()
?方法。實際上,如果你愿意,你可以完全避免計算字符串表示。例如,?SocketHandler?用 pickle 處理事件后,通過網絡發送。
優化
消息參數的格式化將被推遲,直到無法避免。但是,計算傳遞給日志記錄方法的參數也可能很消耗資源,如果記錄器只是丟棄你的事件,你可能希望避免這樣做。要決定做什么,可以調用?isEnabledFor()?方法,該方法接受一個 level 參數,如果記錄器為該級別的調用創建了該事件,則返回 true 。 你可以寫這樣的代碼:
if logger.isEnabledFor(logging.DEBUG):logger.debug('Message with %s, %s', expensive_func1(),expensive_func2())
因此,如果記錄器的閾值設置在“DEBUG”以上,則永遠不會調用?expensive_func1()
?和?expensive_func2()
?。
備注
在某些情況下,?isEnabledFor()?本身可能比你想要的更消耗資源(例如,對于深度嵌套的記錄器,其中僅在記錄器層次結構中設置了顯式級別)。在這種情況下(或者如果你想避免在緊密循環中調用方法),你可以在本地或實例變量中將調用的結果緩存到?isEnabledFor()?,并使用它而不是每次調用方法。在日志記錄配置在應用程序運行時動態更改(這不常見)時,只需要重新計算這樣的緩存值即可。
對于需要對收集的日志信息進行更精確控制的特定應用程序,還可以進行其他優化。以下列出了在日志記錄過程中您可以避免的非必須處理操作:
你不想收集的內容 | 如何避免收集它 |
---|---|
有關調用來源的信息 | 將? |
線程信息 | 將? |
當前進程 ID (os.getpid()) | 將? |
當使用? | 將? |
在使用? | 將? |
另請注意,核心日志記錄模塊僅包含基本處理器。如果你不導入?logging.handlers?和?logging.config?,它們將不會占用任何內存。
其他資源
參見
模塊?logging
日志記錄模塊的 API 參考。
logging.config?模塊
日志記錄模塊的配置 API 。
logging.handlers?模塊
日志記錄模塊附帶的有用處理器。
日志操作手冊