日志常用指引?
- 作者
Vinay Sajip <vinay_sajip at red-dove dot com>
日志基礎教程?
日志是對軟件執(zhí)行時所發(fā)生事件的一種追蹤方式。軟件開發(fā)人員對他們的代碼添加日志調用,借此來指示某事件的發(fā)生。一個事件通過一些包含變量數據的描述信息來描述(比如:每個事件發(fā)生時的數據都是不同的)。開發(fā)者還會區(qū)分事件的重要性,重要性也被稱為 等級 或 嚴重性。
什么時候使用日志?
對于簡單的日志使用來說日志功能提供了一系列便利的函數。它們是 debug()
,info()
,warning()
,error()
和 critical()
。想要決定何時使用日志,請看下表,其中顯示了對于每個通用任務集合來說最好的工具。
你想要執(zhí)行的任務 |
此任務最好的工具 |
---|---|
對于命令行或程序的應用,結果顯示在控制臺。 |
|
在對程序的普通操作發(fā)生時提交事件報告(比如:狀態(tài)監(jiān)控和錯誤調查) |
|
提出一個警告信息基于一個特殊的運行時事件 |
|
對一個特殊的運行時事件報告錯誤 |
引發(fā)異常 |
報告錯誤而不引發(fā)異常(如在長時間運行中的服務端進程的錯誤處理) |
|
日志功能應以所追蹤事件級別或嚴重性而定。各級別適用性如下(以嚴重性遞增):
級別 |
何時使用 |
---|---|
|
細節(jié)信息,僅當診斷問題時適用。 |
|
確認程序按預期運行。 |
|
表明有已經或即將發(fā)生的意外(例如:磁盤空間不足)。程序仍按預期進行。 |
|
由于嚴重的問題,程序的某些功能已經不能正常執(zhí)行 |
|
嚴重的錯誤,表明程序已不能繼續(xù)執(zhí)行 |
默認的級別是 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
消息并沒有出現(xiàn),因為默認級別是 WARNING
。打印的信息包含事件的級別以及在日志調用中的對于事件的描述,例如 “Watch out!”。暫時不用擔心 “root” 部分:之后會作出解釋。輸出格式可按需要進行調整,格式化選項同樣會在之后作出解釋。
記錄日志到文件?
一種非常常見的情況是將日志事件記錄到文件,讓我們繼續(xù)往下看。請確認啟動新的Python 解釋器,不要在上一個環(huán)境中繼續(xù)操作:
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 參數??墒褂玫闹岛湍J值,請參照 open()
的文檔。
現(xiàn)在,如果我們打開日志文件,我們應當能看到日志信息:
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
并且在一些 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, ...)
The call to basicConfig()
should come before any calls to
debug()
, info()
, etc. Otherwise, those functions will call
basicConfig()
for you with the default options. As it's intended as a
one-off simple configuration facility, only the first call will actually do
anything: subsequent calls are effectively no-ops.
如果多次運行上述腳本,則連續(xù)運行的消息將追加到文件 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!
如你所見,將可變數據合并到事件描述消息中使用舊的 %-s形式的字符串格式化。 這是為了向后兼容:logging 包的出現(xiàn)時間早于較新的格式化選項例如 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
請注意,前面示例中出現(xiàn)的 “root” 已消失。 對于可以出現(xiàn)在格式字符串中的全部內容,你可以參考以下文檔 LogRecord 屬性 ,但為了簡單使用,你只需要 levelname (嚴重性), message (事件描述,包括可變數據),也許在事件發(fā)生時顯示。 這將在下一節(jié)中介紹。
在消息中顯示日期/時間?
要顯示事件的日期和時間,你可以在格式字符串中放置 '%(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()
支持的格式相同。
后續(xù)步驟?
基本教程到此結束。 它應該足以讓你啟動并運行日志記錄。 logging 包提供了更多功能,但為了充分利用它,你需要花更多的時間來閱讀以下部分。 如果你準備好了,可以拿一些你最喜歡的飲料然后繼續(xù)。
如果你的日志記錄需求很簡單,那么使用上面的示例將日志記錄合并到你自己的腳本中,如果你遇到問題或者不理解某些內容,請在 comp.lang.python Usenet 組上發(fā)布一個問題(在 https://groups.google.com/forum/#!forum/comp.lang.python ) ,你將在短時間內得到幫助。
還不夠? 你可以繼續(xù)閱讀接下來的幾個部分,這些部分提供了比上面基本部分更高級或深入的教程。 之后,你可以看一下 日志操作手冊 。
進階日志教程?
日志庫采用模塊化方法,并提供幾類組件:記錄器、處理器、過濾器和格式器。
記錄器暴露了應用程序代碼直接使用的接口。
處理器將日志記錄(由記錄器創(chuàng)建)發(fā)送到適當的目標。
過濾器提供了更細粒度的功能,用于確定要輸出的日志記錄。
格式器指定最終輸出中日志記錄的樣式。
日志事件信息在 LogRecord
實例中的記錄器、處理器、過濾器和格式器之間傳遞。
通過調用 Logger
類(以下稱為 loggers , 記錄器)的實例來執(zhí)行日志記錄。 每個實例都有一個名稱,它們在概念上以點(句點)作為分隔符排列在命名空間的層次結構中。 例如,名為 'scan' 的記錄器是記錄器 'scan.text' ,'scan.html' 和 'scan.pdf' 的父級。 記錄器名稱可以是你想要的任何名稱,并指示記錄消息源自的應用程序區(qū)域。
在命名記錄器時使用的一個好習慣是在每個使用日志記錄的模塊中使用模塊級記錄器,命名如下:
logger = logging.getLogger(__name__)
這意味著記錄器名稱跟蹤包或模塊的層次結構,并且直觀地從記錄器名稱顯示記錄事件的位置。
記錄器層次結構的根稱為根記錄器。 這是函數 debug()
、 info()
、 warning()
、 error()
和 critical()
使用的記錄器,它們就是調用了根記錄器的同名方法。 函數和方法具有相同的簽名。 根記錄器的名稱在輸出中打印為 'root' 。
當然,可以將消息記錄到不同的地方。 軟件包中的支持包含,用于將日志消息寫入文件、 HTTP GET/POST 位置、通過 SMTP 發(fā)送電子郵件、通用套接字、隊列或特定于操作系統(tǒng)的日志記錄機制(如 syslog 或 Windows NT 事件日志)。 目標由 handler 類提供。 如果你有任何內置處理器類未滿足的特殊要求,則可以創(chuàng)建自己的日志目標類。
默認情況下,沒有為任何日志消息設置目標。 你可以使用 basicConfig()
指定目標(例如控制臺或文件),如教程示例中所示。 如果你調用函數 debug()
、 info()
、 warning()
、 error()
和 critical()
,它們將檢查是否有設置目標;如果沒有設置,將在委托給根記錄器進行實際的消息輸出之前設置目標為控制臺( sys.stderr
)并設置顯示消息的默認格式。
由 basicConfig()
設置的消息默認格式為:
severity:logger name:message
你可以通過使用 format 參數將格式字符串傳遞給 basicConfig()
來更改此設置。有關如何構造格式字符串的所有選項,請參閱 格式器對象 。
記錄流程?
記錄器和處理器中的日志事件信息流程如下圖所示。

記錄器?
Logger
對象有三重任務。首先,它們向應用程序代碼公開了幾種方法,以便應用程序可以在運行時記錄消息。其次,記錄器對象根據嚴重性(默認過濾工具)或過濾器對象確定要處理的日志消息。第三,記錄器對象將相關的日志消息傳遞給所有感興趣的日志處理器。
記錄器對象上使用最廣泛的方法分為兩類:配置和消息發(fā)送。
這些是最常見的配置方法:
Logger.setLevel()
指定記錄器將處理的最低嚴重性日志消息,其中 debug 是最低內置嚴重性級別, critical 是最高內置嚴重性級別。 例如,如果嚴重性級別為 INFO ,則記錄器將僅處理 INFO 、 WARNING 、 ERROR 和 CRITICAL 消息,并將忽略 DEBUG 消息。Logger.addHandler()
和Logger.removeHandler()
從記錄器對象中添加和刪除處理器對象。處理器在以下內容中有更詳細的介紹 處理器 。Logger.addFilter()
和Logger.removeFilter()
可以添加或移除記錄器對象中的過濾器。 過濾器對象 包含更多的過濾器細節(jié)。
你不需要總是在你創(chuàng)建的每個記錄器上都調用這些方法。 請參閱本節(jié)的最后兩段。
配置記錄器對象后,以下方法將創(chuàng)建日志消息:
Logger.debug()
、Logger.info()
、Logger.warning()
、Logger.error()
和Logger.critical()
都創(chuàng)建日志記錄,包含消息和與其各自方法名稱對應的級別。該消息實際上是一個格式化字符串,它可能包含標題字符串替換語法%s
、%d
、%f
等等。其余參數是與消息中的替換字段對應的對象列表。關于**kwargs
,日志記錄方法只關注exc_info
的關鍵字,并用它來確定是否記錄異常信息。Logger.exception()
創(chuàng)建與Logger.error()
相似的日志信息。 不同之處是,Logger.exception()
同時還記錄當前的堆棧追蹤。僅從異常處理程序調用此方法。Logger.log()
將日志級別作為顯式參數。對于記錄消息而言,這比使用上面列出的日志級別便利方法更加冗長,但這是使用自定義日志級別的方法。
getLogger()
返回對具有指定名稱的記錄器實例的引用(如果已提供),或者如果沒有則返回 root
。名稱是以句點分隔的層次結構。多次調用 getLogger()
具有相同的名稱將返回對同一記錄器對象的引用。在分層列表中較低的記錄器是列表中較高的記錄器的子項。例如,給定一個名為 foo
的記錄器,名稱為 foo.bar
、 foo.bar.baz
和 foo.bam
的記錄器都是 foo
子項。
記錄器具有 有效等級 的概念。如果未在記錄器上顯式設置級別,則使用其父記錄器的級別作為其有效級別。如果父記錄器沒有明確的級別設置,則檢查 其 父級。依此類推,搜索所有上級元素,直到找到明確設置的級別。根記錄器始終具有明確的級別配置(默認情況下為 WARNING
)。在決定是否處理事件時,記錄器的有效級別用于確定事件是否傳遞給記錄器相關的處理器。
子記錄器將消息傳播到與其父級記錄器關聯(lián)的處理器。因此,不必為應用程序使用的所有記錄器定義和配置處理器。一般為頂級記錄器配置處理器,再根據需要創(chuàng)建子記錄器就足夠了。(但是,你可以通過將記錄器的 propagate 屬性設置為 False
來關閉傳播。)
處理器?
Handler
對象負責將適當的日志消息(基于日志消息的嚴重性)分派給處理器的指定目標。 Logger
對象可以使用 addHandler()
方法向自己添加零個或多個處理器對象。作為示例場景,應用程序可能希望將所有日志消息發(fā)送到日志文件,將錯誤或更高的所有日志消息發(fā)送到標準輸出,以及將所有關鍵消息發(fā)送至一個郵件地址。 此方案需要三個單獨的處理器,其中每個處理器負責將特定嚴重性的消息發(fā)送到特定位置。
標準庫包含很多處理器類型(參見 有用的處理器 );教程主要使用 StreamHandler
和 FileHandler
。
處理器中很少有方法可供應用程序開發(fā)人員使用。使用內置處理器對象(即不創(chuàng)建自定義處理器)的應用程序開發(fā)人員能用到的僅有以下配置方法:
setLevel()
方法,就像在記錄器對象中一樣,指定將被分派到適當目標的最低嚴重性。為什么有兩個setLevel()
方法?記錄器中設置的級別確定將傳遞給其處理器的消息的嚴重性。每個處理器中設置的級別確定該處理器將發(fā)送哪些消息。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'
格式器通過用戶可配置的函數將記錄的創(chuàng)建時間轉換為元組。 默認情況下,使用 time.localtime()
;要為特定格式器實例更改此項,請將實例的 converter
屬性設置為與 time.localtime()
或 time.gmtime()
具有相同簽名的函數。 要為所有格式器更改它,例如,如果你希望所有記錄時間都以 GMT 顯示,請在格式器類中設置 converter
屬性(對于 GMT 顯示,設置為 time.gmtime
)。
配置日志記錄?
開發(fā)者可以通過三種方式配置日志記錄:
使用調用上面列出的配置方法的 Python 代碼顯式創(chuàng)建記錄器、處理器和格式器。
創(chuàng)建日志配置文件并使用
fileConfig()
函數讀取它。創(chuàng)建配置信息字典并將其傳遞給
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 模塊創(chuàng)建的記錄器、處理器和格式器幾乎與上面列出的示例中的相同,唯一的區(qū)別是對象的名稱:
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 代碼方法有一些優(yōu)勢,主要是配置和代碼的分離以及非開發(fā)者輕松修改日志記錄屬性的能力。
警告
fileConfig()
函數接受一個默認參數 disable_existing_loggers
,出于向后兼容的原因,默認為 True
。這可能與您的期望不同,因為除非在配置中明確命名它們(或其父級),否則它將導致在 fileConfig()
調用之前存在的任何非 root 記錄器被禁用。有關更多信息,請參閱參考文檔,如果需要,請將此參數指定為 False
。
傳遞給 dictConfig()
的字典也可以用鍵 disable_existing_loggers
指定一個布爾值,如果沒有在字典中明確指定,也默認被解釋為 True
。這會導致上面描述的記錄器禁用行為,這可能與你的期望不同——在這種情況下,請明確地為其提供 False
值。
請注意,配置文件中引用的類名稱需要相對于日志記錄模塊,或者可以使用常規(guī)導入機制解析的絕對值。因此,你可以使用 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]
有關使用字典進行日志記錄的更多信息,請參閱 配置函數。
如果沒有提供配置會發(fā)生什么?
如果未提供日志記錄配置,則可能出現(xiàn)需要輸出日志記錄事件但無法找到輸出事件的處理器的情況。 在這些情況下,logging 包的行為取決于 Python 版本。
對于 3.2 之前的 Python 版本,行為如下:
如果 logging.raiseExceptions 為
False
(生產模式),則會以靜默方式丟棄該事件。如果 logging.raiseExceptions 為
True
(開發(fā)模式),則會打印一條消息 'No handlers could be found for logger X.Y.Z'。
在 Python 3.2 及更高版本中,行為如下:
事件使用 “最后的處理器” 輸出,存儲在
logging.lastResort
中。 這個內部處理器與任何記錄器都沒有關聯(lián),它的作用類似于StreamHandler
,它將事件描述消息寫入sys.stderr
的當前值(因此服從任何可能的重定向影響)。 沒有對消息進行格式化——只打印裸事件描述消息。處理器的級別設置為WARNING
,因此將輸出此級別和更高級別的所有事件。
要獲得 3.2 之前的行為,可以設置 logging.lastResort
為 None
。
配置庫的日志記錄?
在開發(fā)使用日志記錄的庫時,你應該注意記錄庫如何使用日志記錄——例如,使用的記錄器的名稱。還需要考慮其日志記錄配置。如果應用程序不使用日志記錄,并且?guī)齑a進行日志記錄調用,那么(如上一節(jié)所述)嚴重性為 WARNING
及更高級別的事件將打印到 sys.stderr
。這被認為是最好的默認行為。
如果由于某種原因,你 不 希望在沒有任何日志記錄配置的情況下打印這些消息,則可以將無操作處理器附加到庫的頂級記錄器。這樣可以避免打印消息,因為將始終為庫的事件找到處理器:它不會產生任何輸出。如果庫用戶配置應用程序使用的日志記錄,可能是配置將添加一些處理器,如果級別已適當配置,則在庫代碼中進行的日志記錄調用將正常地將輸出發(fā)送給這些處理器。
日志包中包含一個不做任何事情的處理器: NullHandler
(自 Python 3.1 起)。可以將此處理器的實例添加到庫使用的日志記錄命名空間的頂級記錄器中( 如果 你希望在沒有日志記錄配置的情況下阻止庫的記錄事件輸出到 sys.stderr
)。如果庫 foo 的所有日志記錄都是使用名稱匹配 'foo.x' , 'foo.x.y' 等的記錄器完成的,那么代碼:
import logging
logging.getLogger('foo').addHandler(logging.NullHandler())
應該有預計的效果。如果一個組織生成了許多庫,則指定的記錄器名稱可以是 “orgname.foo” 而不僅僅是 “foo” 。
備注
It is strongly advised that you do not log to the root logger
in your library. Instead, use a logger with a unique and easily
identifiable name, such as the __name__
for your library's top-level package
or module. Logging to the root logger will make it difficult or impossible for
the application developer to configure the logging verbosity or handlers of
your library as they wish.
備注
強烈建議你 不要將 NullHandler
以外的任何處理器添加到庫的記錄器中 。這是因為處理器的配置是使用你的庫的應用程序開發(fā)人員的權利。應用程序開發(fā)人員了解他們的目標受眾以及哪些處理器最適合他們的應用程序:如果你在“底層”添加處理器,則可能會干擾他們執(zhí)行單元測試和提供符合其要求的日志的能力。
日志級別?
日志記錄級別的數值在下表中給出。如果你想要定義自己的級別,并且需要它們具有相對于預定義級別的特定值,那么這你可能對以下內容感興趣。如果你定義具有相同數值的級別,它將覆蓋預定義的值;預定義的名稱將失效。
級別 |
數值 |
---|---|
|
50 |
|
40 |
|
30 |
|
20 |
|
10 |
|
0 |
級別也可以與記錄器相關聯(lián),由開發(fā)人員設置或通過加載已保存的日志記錄配置。在記錄器上調用日志記錄方法時,記錄器會將其自己的級別與與方法調用關聯(lián)的級別進行比較。如果記錄器的級別高于方法調用的級別,則實際上不會生成任何記錄消息。這是控制日志記錄輸出詳細程度的基本機制。
記錄消息被編碼為 LogRecord
類的實例。當記錄器決定實際記錄事件時,將用記錄消息創(chuàng)建 LogRecord
實例。
記錄消息受 handlers 建立的調度機制控制,它們是 Handler
類的子類實例。處理器負責確保記錄的消息(以 LogRecord
的形式)最終位于對該消息的目標受眾(例如最終用戶、 支持服務臺員工、系統(tǒng)管理員、開發(fā)人員)有用的特定位置(或一組位置)上。處理器傳遞適用于特定目標的 LogRecord
實例。 每個記錄器可以有零個、一個或多個與之關聯(lián)的處理器(通過 Logger
的 addHandler()
方法)。除了與記錄器直接關聯(lián)的所有處理器之外,還調用與記錄器的 所有祖先關聯(lián)的處理器來分派消息(除非記錄器的 *propagate 標志設置為 false 值,這將停止傳遞到上級處理器)。
就像記錄器一樣,處理器可以具有與它們相關聯(lián)的級別。處理器的級別作為過濾器,其方式與記錄器級別相同。如果處理器決定調度一個事件,則使用 emit()
方法將消息發(fā)送到其目標。大多數用戶定義的 Handler
子類都需要重載 emit()
。
自定義級別?
定義你自己的級別是可能的,但不一定是必要的,因為現(xiàn)有級別是根據實踐經驗選擇的。但是,如果你確信需要自定義級別,那么在執(zhí)行此操作時應特別小心,如果你正在開發(fā)庫,則 定義自定義級別可能是一個非常糟糕的主意 。 這是因為如果多個庫作者都定義了他們自己的自定義級別,那么使用開發(fā)人員很難控制和解釋這些多個庫的日志記錄輸出,因為給定的數值對于不同的庫可能意味著不同的東西。
有用的處理器?
作為 Handler
基類的補充,提供了很多有用的子類:
StreamHandler
實例發(fā)送消息到流(類似文件對象)。FileHandler
實例將消息發(fā)送到硬盤文件。BaseRotatingHandler
是輪換日志文件的處理器的基類。它并不應該直接實例化。而應該使用RotatingFileHandler
或TimedRotatingFileHandler
代替它。RotatingFileHandler
實例將消息發(fā)送到硬盤文件,支持最大日志文件大小和日志文件輪換。TimedRotatingFileHandler
實例將消息發(fā)送到硬盤文件,以特定的時間間隔輪換日志文件。SocketHandler
實例將消息發(fā)送到 TCP/IP 套接字。從 3.4 開始,也支持 Unix 域套接字。DatagramHandler
實例將消息發(fā)送到 UDP 套接字。從 3.4 開始,也支持 Unix 域套接字。SMTPHandler
實例將消息發(fā)送到指定的電子郵件地址。SysLogHandler
實例將消息發(fā)送到 Unix syslog 守護程序,可能在遠程計算機上。NTEventLogHandler
實例將消息發(fā)送到 Windows NT/2000/XP 事件日志。MemoryHandler
實例將消息發(fā)送到內存中的緩沖區(qū),只要滿足特定條件,緩沖區(qū)就會刷新。HTTPHandler
實例使用GET
或POST
方法將消息發(fā)送到 HTTP 服務器。WatchedFileHandler
實例會監(jiān)視他們要寫入日志的文件。如果文件發(fā)生更改,則會關閉該文件并使用文件名重新打開。此處理器僅在類 Unix 系統(tǒng)上有用; Windows 不支持依賴的基礎機制。QueueHandler
實例將消息發(fā)送到隊列,例如在queue
或multiprocessing
模塊中實現(xiàn)的隊列。NullHandler
實例對錯誤消息不執(zhí)行任何操作。它們由想要使用日志記錄的庫開發(fā)人員使用,但是想要避免如果庫用戶沒有配置日志記錄,則顯示 'No handlers could be found for logger XXX' 消息的情況。更多有關信息,請參閱 配置庫的日志記錄 。
3.1 新版功能: NullHandler
類。
3.2 新版功能: QueueHandler
類。
The NullHandler
、 StreamHandler
和 FileHandler
類在核心日志包中定義。其他處理器定義在 logging.handlers
中。(還有另一個子模塊 logging.config
,用于配置功能)
記錄的消息通過 Formatter
類的實例進行格式化后呈現(xiàn)。 它們使用能與 % 運算符一起使用的格式字符串和字典進行初始化。
要批量格式化多個消息,可以使用 BufferingFormatter
的實例。除了格式字符串(應用于批處理中的每個消息)之外,還提供了標題和尾部格式字符串。
當基于記錄器級別和處理器級別的過濾不夠時,可以將 Filter
的實例添加到 Logger
和 Handler
實例(通過它們的 addFilter()
方法)。在決定進一步處理消息之前,記錄器和處理器都會查詢其所有過濾器以獲得許可。如果任何過濾器返回 false 值,則不會進一步處理該消息。
基本 Filter
的功能允許按特定的記錄器名稱進行過濾。如果使用此功能,則允許通過過濾器發(fā)送到指定記錄器及其子項的消息,并丟棄其他所有消息。
記錄日志時引發(fā)的異常?
logging 包設計為忽略記錄日志生產時發(fā)生的異常。這樣,處理日志記錄事件時發(fā)生的錯誤(例如日志記錄錯誤配置、網絡或其他類似錯誤)不會導致使用日志記錄的應用程序過早終止。
SystemExit
和 KeyboardInterrupt
異常永遠不會被忽略。 在 Handler
子類的 emit()
方法中發(fā)生的其他異常被傳遞給它的 handleError()
方法。
Handler
中默認實現(xiàn)的 handleError()
檢查是否設置了模塊級變量 raiseExceptions
。如果有設置,則會將回溯打印到 sys.stderr
。如果未設置,則忽略異常。
備注
raiseExceptions
默認值是 True
。 這是因為在開發(fā)期間,你通常希望收到任何發(fā)生異常的通知。建議你將 raiseExceptions
設置為 False
以供生產環(huán)境使用。
使用任意對象作為消息?
在前面的部分和示例中,都假設記錄事件時傳遞的消息是字符串。 但是,這不是唯一的可能性。你可以將任意對象作為消息傳遞,并且當日志記錄系統(tǒng)需要將其轉換為字符串表示時,將調用其 __ str__()
方法。實際上,如果你愿意,你可以完全避免計算字符串表示。例如, SocketHandler
用 pickle 處理事件后,通過網絡發(fā)送。
優(yōu)化?
消息參數的格式化將被推遲,直到無法避免。但是,計算傳遞給日志記錄方法的參數也可能很消耗資源,如果記錄器只是丟棄你的事件,你可能希望避免這樣做。要決定做什么,可以調用 isEnabledFor()
方法,該方法接受一個 level 參數,如果記錄器為該級別的調用創(chuàng)建了該事件,則返回 true 。 你可以寫這樣的代碼:
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Message with %s, %s', expensive_func1(),
expensive_func2())
因此,如果記錄器的閾值設置在“DEBUG”以上,則永遠不會調用 expensive_func1()
和 expensive_func2()
。
備注
在某些情況下, isEnabledFor()
本身可能比你想要的更消耗資源(例如,對于深度嵌套的記錄器,其中僅在記錄器層次結構中設置了顯式級別)。在這種情況下(或者如果你想避免在緊密循環(huán)中調用方法),你可以在本地或實例變量中將調用的結果緩存到 isEnabledFor()
,并使用它而不是每次調用方法。在日志記錄配置在應用程序運行時動態(tài)更改(這不常見)時,只需要重新計算這樣的緩存值即可。
對于需要對收集的日志信息進行更精確控制的特定應用程序,還可以進行其他優(yōu)化。以下列出了在日志記錄過程中您可以避免的非必須處理操作:
你不想收集的內容 |
如何避免收集它 |
---|---|
有關調用來源的信息 |
將 |
線程信息 |
將 |
當前進程 ID ( |
將 |
當使用 |
將 |
Current |
Set |
另請注意,核心日志記錄模塊僅包含基本處理器。如果你不導入 logging.handlers
和 logging.config
,它們將不會占用任何內存。
參見
- 模塊
logging
日志記錄模塊的 API 參考。
logging.config
模塊日志記錄模塊的配置 API 。
logging.handlers
模塊日志記錄模塊附帶的有用處理器。