PEP 391 – 基於字典的日誌配置
- 作者:
- Vinay Sajip <vinay_sajip at red-dove.com>
- 狀態:
- 最終版
- 型別:
- 標準跟蹤
- 建立日期:
- 2009年10月15日
- Python 版本:
- 2.7, 3.2
- 釋出歷史:
摘要
本PEP描述了一種使用字典來儲存配置資訊的新日誌配置方式。
基本原理
目前配置Python日誌包的方法是:要麼使用日誌API以程式設計方式配置日誌,要麼透過基於ConfigParser的配置檔案進行配置。
程式設計配置雖然提供了最大的控制權,但將配置固定在Python程式碼中。這不利於在執行時輕鬆更改它,因此,靈活地調整使用應用程式不同部分的日誌詳細程度的能力就喪失了。這限制了日誌作為診斷問題輔助工具的可用性——有時,日誌是生產環境中唯一可用的診斷輔助工具。
基於ConfigParser的配置系統可用,但無法讓使用者配置日誌包的所有方面。例如,無法使用此係統配置過濾器。此外,ConfigParser格式似乎在某些方面引起了人們的反感(有時是強烈的反感)。儘管它被選中是因為它是當時Python標準中唯一支援的配置格式,但許多人認為它(或者可能只是日誌配置所選擇的特定模式)“老舊”或“醜陋”,在某些情況下顯然純粹是出於審美原因。
Python的最新版本在標準庫中包含了JSON支援,這也可以用作配置格式。在其他環境中,例如Google App Engine,YAML用於配置應用程式,通常日誌的配置被認為是應用程式配置不可或缺的一部分。儘管標準庫目前不包含YAML支援,但JSON和YAML的支援可以透過通用方式提供,因為這兩種序列化格式都允許反序列化為Python字典。
透過提供一種透過在字典中傳遞配置來配置日誌的方式,不僅對於JSON和/或YAML的使用者來說,日誌將更容易配置,而且對於自定義配置方法的使用者來說也更容易配置,因為它提供了一種描述所需配置的通用格式。
當前基於ConfigParser的配置系統的另一個缺點是它不支援增量配置:新的配置會完全替換現有配置。儘管在多執行緒環境中很難提供增量配置的完全靈活性,但新的配置機制將允許提供對增量配置的有限支援。
規範
該規範由兩部分組成:API 和用於傳遞配置資訊的字典格式(即它必須符合的模式)。
命名
從歷史上看,日誌包一直不符合 PEP 8。將來,這將透過更改包中的方法和函式名稱來糾正,以符合 PEP 8。然而,為了保持一致性,對API的擬議新增內容採用了與日誌當前使用的方案一致的命名方案。
API
logging.config 模組將新增以下內容
- 一個名為
dictConfig()的函式,它接受一個引數——包含配置的字典。如果在處理字典時出現錯誤,將引發異常。
字典模式 - 概述
在詳細描述模式之前,值得提一下物件連線、對使用者定義物件的支援以及對外部和內部物件的訪問。
物件連線
該模式旨在描述一組日誌物件——記錄器、處理程式、格式化程式、過濾器——它們在物件圖中相互連線。因此,該模式需要表示物件之間的連線。例如,假設配置完成後,某個特定記錄器附加了某個特定處理程式。為了討論的目的,我們可以說記錄器代表連線的源,而處理程式代表連線的目的地。當然,在配置的物件中,這由記錄器持有處理程式的引用來表示。在配置字典中,這是透過為每個目標物件提供一個唯一標識它的ID,然後在源物件的配置中使用該ID來指示源和具有該ID的目標物件之間存在連線來完成的。
例如,考慮以下YAML片段
formatters:
brief:
# configuration for formatter with id 'brief' goes here
precise:
# configuration for formatter with id 'precise' goes here
handlers:
h1: #This is an id
# configuration of handler with id 'h1' goes here
formatter: brief
h2: #This is another id
# configuration of handler with id 'h2' goes here
formatter: precise
loggers:
foo.bar.baz:
# other configuration for logger 'foo.bar.baz'
handlers: [h1, h2]
(注意:本文件將使用YAML,因為它比等效的Python原始碼形式的字典更具可讀性。)
記錄器(logger)的ID是用於透過程式設計方式獲取這些記錄器引用的記錄器名稱,例如 foo.bar.baz。格式化程式(Formatter)和過濾器(Filter)的ID可以是任何字串值(例如上面提到的 brief, precise),它們是臨時的,因為它們僅在處理配置字典時有意義,用於確定物件之間的連線,並且在配置呼叫完成後不會持久化到任何地方。
處理程式ID將得到特殊處理,請參閱下面的處理程式ID部分。
上述程式碼片段表示名為 foo.bar.baz 的記錄器應該附加兩個處理程式,這兩個處理程式由處理程式ID h1 和 h2 描述。h1 的格式化程式由ID brief 描述,h2 的格式化程式由ID precise 描述。
使用者定義物件
該模式應支援處理程式、過濾器和格式化程式的使用者定義物件。(記錄器不需要為不同的例項擁有不同的型別,因此在配置中不支援使用者定義的記錄器類。)
要配置的物件通常由詳細說明其配置的字典來描述。在某些地方,日誌系統將能夠根據上下文推斷出如何例項化物件,但是當要例項化使用者定義的物件時,系統將不知道如何執行此操作。為了提供使用者定義物件例項化的完全靈活性,使用者將需要提供一個“工廠”——一個可呼叫物件,它以配置字典作為引數被呼叫並返回例項化的物件。這將透過特殊鍵 '()' 下提供工廠的絕對匯入路徑來發出訊號。這是一個具體示例
formatters:
brief:
format: '%(message)s'
default:
format: '%(asctime)s %(levelname)-8s %(name)-15s %(message)s'
datefmt: '%Y-%m-%d %H:%M:%S'
custom:
(): my.package.customFormatterFactory
bar: baz
spam: 99.9
answer: 42
上述YAML程式碼片段定義了三個格式化程式。第一個,ID為 brief,是一個標準的 logging.Formatter 例項,具有指定的格式字串。第二個,ID為 default,具有較長的格式,並明確定義了時間格式,將生成一個使用這兩個格式字串初始化的 logging.Formatter。以Python原始碼形式顯示,brief 和 default 格式化程式的配置子字典分別為
{
'format' : '%(message)s'
}
和
{
'format' : '%(asctime)s %(levelname)-8s %(name)-15s %(message)s',
'datefmt' : '%Y-%m-%d %H:%M:%S'
}
因此,由於這些字典不包含特殊鍵 '()',因此從上下文推斷出例項化:結果,建立了標準的 logging.Formatter 例項。第三個格式化程式(ID 為 custom)的配置子字典為
{
'()' : 'my.package.customFormatterFactory',
'bar' : 'baz',
'spam' : 99.9,
'answer' : 42
}
它包含特殊鍵 '()',這意味著需要使用者定義的例項化。在這種情況下,將使用指定的工廠可呼叫物件。如果它是一個實際的可呼叫物件,它將直接使用——否則,如果指定一個字串(如示例所示),實際的可呼叫物件將使用正常的匯入機制定位。可呼叫物件將以配置子字典中的*其餘*項作為關鍵字引數進行呼叫。在上面的示例中,ID 為 custom 的格式化程式將被假定為由以下呼叫返回
my.package.customFormatterFactory(bar='baz', spam=99.9, answer=42)
鍵 '()' 被用作特殊鍵,因為它不是一個有效的關鍵字引數名稱,因此不會與呼叫中使用的關鍵字引數名稱衝突。'()' 也作為一個助記符,表示相應的值是一個可呼叫物件。
訪問外部物件
有時配置需要引用配置之外的物件,例如 sys.stderr。如果配置字典是使用Python程式碼構建的,那麼這很簡單,但是當配置透過文字檔案(例如JSON,YAML)提供時會出現問題。在文字檔案中,沒有標準的方法來區分 sys.stderr 和字面字串 'sys.stderr'。為了方便這種區分,配置系統將在字串值中查詢某些特殊字首並對其進行特殊處理。例如,如果字面字串 'ext://sys.stderr' 作為配置中的值提供,那麼 ext:// 將被剝離,值的其餘部分將使用正常的匯入機制進行處理。
此類字首的處理將以類似於協議處理的方式進行:將有一個通用機制來查詢與正則表示式 ^(?P<prefix>[a-z]+)://(?P<suffix>.*)$ 匹配的字首,如果 prefix 被識別,則 suffix 將以依賴於字首的方式進行處理,處理結果將替換字串值。如果字首未被識別,則字串值將保持不變。
該實現將提供一組標準字首,例如 ext://,但可以完全停用該機制,或為特殊處理提供額外或不同的字首。
訪問內部物件
除了外部物件,有時還需要引用配置中的物件。配置系統將隱式地為它知道的事物完成此操作。例如,記錄器或處理程式中 level 的字串值 'DEBUG' 將自動轉換為值 logging.DEBUG,並且 handlers、filters 和 formatter 條目將接受一個物件ID並解析為相應的目標物件。
然而,對於日誌系統未知的使用者定義物件,需要提供一個更通用的機制。例如,以 logging.handlers.MemoryHandler 例項為例,它接受一個 target,該 target 是另一個要委託到的處理程式。由於系統已經知道這個類,那麼在配置中,給定的 target 只需要是相關目標處理程式的物件ID,系統就會從ID解析出處理程式。但是,如果使用者定義了一個具有 alternate 處理程式的 my.package.MyHandler,配置系統將不知道 alternate 指的是一個處理程式。為了解決這個問題,將提供一個通用的解析系統,允許使用者指定
handlers:
file:
# configuration of file handler goes here
custom:
(): my.package.MyHandler
alternate: cfg://handlers.file
字面字串 'cfg://handlers.file' 將以與帶有 ext:// 字首的字串類似的方式進行解析,但會在配置本身而非匯入名稱空間中查詢。該機制將允許透過點或索引進行訪問,其方式類似於 str.format 所提供的方式。因此,給定以下程式碼片段
handlers:
email:
class: logging.handlers.SMTPHandler
mailhost: localhost
fromaddr: my_app@domain.tld
toaddrs:
- support_team@domain.tld
- dev_team@domain.tld
subject: Houston, we have a problem.
在配置中,字串 'cfg://handlers' 將解析為鍵為 handlers 的字典,字串 'cfg://handlers.email' 將解析為 handlers 字典中鍵為 email 的字典,依此類推。字串 'cfg://handlers.email.toaddrs[1]' 將解析為 'dev_team.domain.tld',字串 'cfg://handlers.email.toaddrs[0]' 將解析為值 'support_team@domain.tld'。subject 值可以透過 'cfg://handlers.email.subject' 或等效地 'cfg://handlers.email[subject]' 進行訪問。後一種形式僅在鍵包含空格或非字母數字字元時才需要使用。如果索引值僅由十進位制數字組成,將嘗試使用相應的整數值進行訪問,如果失敗則回退到字串值。
給定字串 cfg://handlers.myhandler.mykey.123,它將解析為 config_dict['handlers']['myhandler']['mykey']['123']。如果字串指定為 cfg://handlers.myhandler.mykey[123],系統將嘗試從 config_dict['handlers']['myhandler']['mykey'][123] 中檢索值,如果失敗則回退到 config_dict['handlers']['myhandler']['mykey']['123']。
處理程式ID
一些特定的日誌配置需要使用處理程式級別才能達到預期的效果。但是,與始終可以透過其名稱識別的日誌記錄器不同,處理程式沒有持久控制代碼,無法透過增量配置呼叫更改級別。
因此,本PEP提議為處理程式新增一個可選的 name 屬性。如果使用,這將向一個字典中新增一個條目,該字典將名稱對映到處理程式。(當處理程式關閉時,該條目將被刪除。)當進行增量配置呼叫時,將在該字典中查詢處理程式,以根據配置中的值設定處理程式級別。有關更多詳細資訊,請參閱增量配置部分。
理論上,這種“持久名稱”功能也可以用於過濾器和格式化器。然而,目前沒有強有力的理由來證明能夠增量配置這些功能。基於實用性勝過純粹性的原則,只有處理程式將擁有這個新的 name 屬性。處理程式在配置中的ID將成為它的 name。
處理程式名稱查詢字典僅供配置使用,不會成為包公共 API 的一部分。
字典模式 - 細節
傳遞給 dictConfig() 的字典必須包含以下鍵
version- 設定為一個表示模式版本的整數值。目前唯一有效的值是 1,但擁有此鍵允許模式演進,同時仍然保持向後相容性。
所有其他鍵都是可選的,但如果存在,將按以下描述進行解釋。在以下所有提及“配置字典”的情況下,都會檢查特殊鍵 '()',以檢視是否需要自定義例項化。如果需要,則使用上述機制進行例項化;否則,根據上下文確定如何例項化。
formatters- 相應的值將是一個字典,其中每個鍵是格式化程式ID,每個值是一個描述如何配置相應Formatter例項的字典。配置字典中會查詢鍵
format和datefmt(預設為None),這些鍵用於構造logging.Formatter例項。filters- 相應的值將是一個字典,其中每個鍵是過濾器ID,每個值是一個描述如何配置相應Filter例項的字典。配置字典中會查詢鍵
name(預設為空字串),並使用它來構造logging.Filter例項。handlers- 相應的值將是一個字典,其中每個鍵是處理程式ID,每個值是一個描述如何配置相應Handler例項的字典。配置字典將查詢以下鍵
class(必填)。這是處理程式類的完全限定名。level(可選)。處理程式的級別。formatter(可選)。此處理程式的格式化程式ID。filters(可選)。此處理程式的過濾器ID列表。
所有*其他*鍵都作為關鍵字引數傳遞給處理程式的建構函式。例如,給定程式碼片段
handlers: console: class : logging.StreamHandler formatter: brief level : INFO filters: [allow_foo] stream : ext://sys.stdout file: class : logging.handlers.RotatingFileHandler formatter: precise filename: logconfig.log maxBytes: 1024 backupCount: 3ID 為
console的處理程式被例項化為logging.StreamHandler,使用sys.stdout作為底層流。ID 為file的處理程式被例項化為logging.handlers.RotatingFileHandler,其關鍵字引數為filename='logconfig.log', maxBytes=1024, backupCount=3。loggers- 相應的值將是一個字典,其中每個鍵是記錄器名稱,每個值是一個描述如何配置相應Logger例項的字典。配置字典將查詢以下鍵
level(可選)。記錄器的級別。propagate(可選)。記錄器的傳播設定。filters(可選)。此記錄器的過濾器ID列表。handlers(可選)。此記錄器的處理程式ID列表。
指定的記錄器將根據指定的級別、傳播、過濾器和處理程式進行配置。
root- 這將是根記錄器的配置。配置的處理方式與任何記錄器相同,但propagate設定不適用。incremental- 配置是否應被解釋為對現有配置的增量。該值預設為False,這意味著指定的配置將替換現有配置,並採用與現有fileConfig()API 相同的語義。如果指定的值為
True,則配置將按照下面增量配置一節中的描述進行處理。disable_existing_loggers- 是否停用任何現有記錄器。此設定與fileConfig()中同名引數相對應。如果缺失,此引數預設為True。如果incremental為True,則此值將被忽略。
一個工作示例
以下是一個實際可用的YAML格式配置(除了電子郵件地址是虛構的)
formatters:
brief:
format: '%(levelname)-8s: %(name)-15s: %(message)s'
precise:
format: '%(asctime)s %(name)-15s %(levelname)-8s %(message)s'
filters:
allow_foo:
name: foo
handlers:
console:
class : logging.StreamHandler
formatter: brief
level : INFO
stream : ext://sys.stdout
filters: [allow_foo]
file:
class : logging.handlers.RotatingFileHandler
formatter: precise
filename: logconfig.log
maxBytes: 1024
backupCount: 3
debugfile:
class : logging.FileHandler
formatter: precise
filename: logconfig-detail.log
mode: a
email:
class: logging.handlers.SMTPHandler
mailhost: localhost
fromaddr: my_app@domain.tld
toaddrs:
- support_team@domain.tld
- dev_team@domain.tld
subject: Houston, we have a problem.
loggers:
foo:
level : ERROR
handlers: [debugfile]
spam:
level : CRITICAL
handlers: [debugfile]
propagate: no
bar.baz:
level: WARNING
root:
level : DEBUG
handlers : [console, file]
增量配置
提供增量配置的完全靈活性很困難。例如,由於過濾器和格式化程式等物件是匿名的,一旦設定了配置,在增加配置時就無法引用這些匿名物件。
此外,一旦配置設定完成,在執行時任意更改記錄器、處理程式、過濾器、格式化程式的物件圖並沒有令人信服的理由;記錄器和處理程式的詳細程度可以透過設定級別(以及記錄器的傳播標誌)來控制。在多執行緒環境中以安全的方式任意更改物件圖是存在問題的;雖然並非不可能,但收益不值得增加實現的複雜性。
因此,當配置字典的 incremental 鍵存在且為 True 時,系統將完全忽略所有 formatters 和 filters 條目,並且只處理 handlers 條目中的 level 設定,以及 loggers 和 root 條目中的 level 和 propagate 設定。
當然可以透過其他方式提供增量配置,例如讓 dictConfig() 接受一個預設為 False 的 incremental 關鍵字引數。建議在配置字典中使用一個值的原因是,它允許將配置作為醃製字典透過網路傳送到套接字監聽器。因此,長期執行的應用程式的日誌詳細程度可以隨著時間改變,而無需停止和重新啟動應用程式。
注意:歡迎您根據實際經驗,就增量配置需求提供反饋。
API定製
基本的 dictConfig() API 無法滿足所有用例。透過提供以下內容,將實現 API 的定製:
- 一個名為
DictConfigurator的類,其建構函式接受用於配置的字典,並具有一個configure()方法。 - 一個可呼叫物件,名為
dictConfigClass,它(預設情況下)將設定為DictConfigurator。提供此物件是為了在需要時,可以將DictConfigurator替換為適當的使用者定義實現。
函式 dictConfig() 將呼叫 dictConfigClass,傳入指定的字典,然後呼叫返回物件上的 configure() 方法以實際使配置生效。
def dictConfig(config):
dictConfigClass(config).configure()
這應該能滿足所有定製需求。例如,DictConfigurator 的子類可以在自己的 __init__() 中呼叫 DictConfigurator.__init__(),然後設定自定義字首,這些字首將在隨後的 configure() call 中使用。dictConfigClass 將繫結到子類,然後可以像在預設的、未定製的狀態下一樣呼叫 dictConfig()。
套接字監聽器實現更改
現有的套接字監聽器實現將按如下方式修改:當收到配置訊息時,將嘗試使用json模組反序列化為字典。如果此步驟失敗,則訊息將被假定為fileConfig格式並照常處理。如果反序列化成功,則將呼叫 dictConfig() 來處理生成的字典。
配置錯誤
如果在配置過程中遇到錯誤,系統將引發一個帶有適當描述性訊息的 ValueError、TypeError、AttributeError 或 ImportError。以下是可能引發錯誤的條件列表(可能不完整)
- 一個
level既不是字串,也不是對應實際日誌級別的字串 - 一個
propagate值不是布林值 - 沒有對應目標的ID
- 在增量呼叫期間發現不存在的處理程式 ID
- 無效的記錄器名稱
- 無法解析為內部或外部物件
社群討論
該PEP已在python-dev和python-list上公佈。雖然討論不多,但這對於一個利基話題來說可能是意料之中的。
python-dev 上的討論帖子
https://mail.python.org/pipermail/python-dev/2009-October/092695.html https://mail.python.org/pipermail/python-dev/2009-October/092782.html https://mail.python.org/pipermail/python-dev/2009-October/093062.html
以及 python-list 上
https://mail.python.org/pipermail/python-list/2009-October/1223658.html https://mail.python.org/pipermail/python-list/2009-October/1224228.html
有一些支援該提案的評論,沒有反對整個提案的意見,以及一些關於具體細節的問題和反對意見。作者認為這些問題已透過對PEP的修改得到解決。
參考實現
更改的參考實現可作為模組 dictconfig.py 獲得,並在 test_dictconfig.py 中附帶單元測試,網址為
http://bitbucket.org/vinay.sajip/dictconfig
這包含了除套接字監聽器更改之外的所有功能。
版權
本文件已置於公共領域。
來源:https://github.com/python/peps/blob/main/peps/pep-0391.rst