【Python】logging 的巧妙設計

引言

logging 的基本用法網上很多,這裏就不介紹了。在引入正文之前,先來看一個需求:

假設需要將某功能封裝成類庫供他人使用,如何處理類庫中的日誌?

數年前在一個 C# 開發的項目中,我用了這樣的方法:定義一個 logging 基類,所有需要用到日誌的類都繼承這個基類,這個基類中定義一個 LogHandler 事件,該事件用於實現具體的記錄日誌動作,同時可以通過將類 A 的 LogHandler 委託掛到類 B 的 LogHandler 上,實現將兩個類的日誌信息添加到一起。

自從看了 python 中 logging 的實現方式,我發現我的做法真是弱爆了。

我在之前的博客 Python:logging.NullHandler 的使用 中介紹了 peewee框架中的日誌輸出,簡單來說就是 peewee中定義了一個名爲peeweeLogger並添加了一個 NullHandler,調用者只需要爲其添加具體的 Handler就可以輸出日誌了,非常方便。

假設我們在主程序中也有一個 Logger,調用 peewee後,我想將兩個日誌輸出到同一個日誌文件中去。顯然將兩個日誌的 FileHandler指向同一個日誌文件是不可取的,存在併發搶佔文件的風險。當然我們也可以將主程序中的 Logger名字定爲 peewee,但這不僅太 low 了,而且如果再調用一個庫,其中也封裝好了一個 Logger,就不好處理了。

樹狀結構的 Logger

Logger對象被設計爲一個樹形結構,它有一個 parent屬性。logging中定義了一個名爲 rootLogger作爲所有 Logger的根節點,rootparent屬性爲 Noneroot是全局的。

當調用

logging.getLogger(name=None)

得到一個 Logger對象的時候,如果 nameNone,則返回根節點 root。如果 name中含有 .,比如 name = 'a.b',這時如果已經存在了名爲 aLogger,則 a.ba的子節點,如果不存在名爲 aLogger,則 a.broot的子節點。

child logger 在完成對日誌消息的處理後,默認會將日誌消息傳遞給與它的 parent logger。因此,我們不必爲一個應用程序中使用的所有 Logger定義和配置 handlers,只需要爲一個頂層的 Logger配置 handlers,然後按照需要創建 child loggers 就可足夠了。我們可以通過設置 Loggerpropagate屬性設置爲 False來關閉這種傳遞機制。

什麼意思呢,我們來看代碼:

import logging

logA = logging.getLogger('a')
logA.setLevel(logging.DEBUG)
logA.addHandler(logging.StreamHandler())

logB = logging.getLogger('a.b')
logB.addHandler(logging.StreamHandler())

輸出結果:

Logger A
Logger B
Logger B

之所以 Logger B被輸出了 2 次,是因爲 logBlogA的子節點,並且 logB中也定義了 Handler,所以 logBHandler 輸出了一次,logAHandler 也輸出了一次,就 2 次了。如果想只輸出一次,可以刪掉 logB中的 Handler。當然,這也是有用處的,尤其是當你手頭沒有日誌管理工具的時候。例如,主程序中需要輸出所有的日誌,以便了解程序整體的運行順序,而某模塊的日誌,你想單獨輸出一份,以便清晰瞭解模塊中的報錯或者是執行順序。

之前 peewee的例子也就很容易解決了,只需要將 peewee日誌的 parent屬性設置爲主程序的日誌就可以了。

結語

其實這是一個比較容易說明的問題,完全沒必要寫這麼多。我並不想跟大家分享 python 中的 logging 是怎麼用的,而是想和大家分享 logging 如此實現的一種思想,因爲我遇到過這個問題,也設計瞭解決方案,然後被完爆了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章