尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️
引言
logging 的基本用法網上很多,這里就不介紹了。在引入正文之前,先來看一個需求:
假設需要將某功能封裝成類庫供他人使用,如何處理類庫中的日志?
數年前在一個 C# 開發的項目中,我用了這樣的方法:定義一個 logging 基類,所有需要用到日志的類都繼承這個基類,這個基類中定義一個 LogHandler 事件,該事件用於做到具體的記錄日志動作,同時可以通過將類 A 的 LogHandler 委托掛到類 B 的 LogHandler 上,做到將兩個類的日志信息添加到一起。
自從看了 python 中 logging 的做到方式,我發現我的做法真是弱爆了。
我在之前的博客 Python:logging.NullHandler 的使用 中介紹了 peewee 框架中的日志輸出,簡單來說就是 peewee 中定義了一個名為 peewee 的 Logger 並添加了一個 NullHandler ,調用者只需要為其添加具體的 Handler 就可以輸出日志了,非常方便。
假設我們在主程序中也有一個 Logger ,調用 peewee 後,我想將兩個日志輸出到同一個日志文件中去。顯然將兩個日志的 FileHandler 指向同一個日志文件是不可取的,存在並發搶占文件的風險。當然我們也可以將主程序中的 Logger 名字定為 peewee ,但這不僅太 low 了,而且如果再調用一個庫,其中也封裝好了一個 Logger,就不好處理了。
樹樁結構的 Logger
Logger 對象被設計為一個樹形結構,它有一個 parent 屬性。 logging 中定義了一個名為 root 的 Logger 作為所有 Logger 的根節點, root 的 parent 屬性為 None 。 root 是全局的。
當調用
logging.getLogger(name=None)
得到一個 Logger 對象的時候,如果 name 為 None ,則返回根節點 root 。如果 name 中含有 . ,比如 name = ‘a.b’ ,這時如果已經存在了名為 a 的 Logger ,則 a.b 為 a 的子節點,如果不存在名為 a 的 Logger ,則 a.b 為 root 的子節點。
child logger在完成對日志消息的處理後,默認會將日志消息傳遞給與它的 parent logger 。因此,我們不必為一個應用程序中使用的所有 Logger 定義和配置 handlers ,只需要為一個頂層的 Logger 配置 handlers ,然後按照需要創建 child loggers 就可足夠了。我們可以通過設置 Logger 的 propagate 屬性設置為 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 次,是因為 logB 是 logA 的子節點,並且 logB 中也定義了 Handler ,所以 logB 的 Handler 輸出了一次, logA 的 Handler 也輸出了一次,就 2 次了。如果想只輸出一次,可以刪掉 logB 中的 Handler 。當然,這也是有用處的,尤其是當你手頭沒有日志管理工具的時候。例如,主程序中需要輸出所有的日志,以便了解程序整體的運行順序,而某模塊的日志,你想單獨輸出一份,以便清晰了解模塊中的報錯或者是執行順序。
之前 peewee 的例子也就很容易解決了,只需要將 peewee 日志的 parent 屬性設置為主程序的日志就可以了。
結語
其實這是一個比較容易說明的問題,完全沒必要寫這麼多。我並不想跟大家分享 python 中的 logging 是怎麼用的,而是想和大家分享 logging 如此做到的一種思想,因為我遇到過這個問題,也設計了解決方案,然後被完爆了。
進群「960410445」 獲取更多案例教程哦~!