python Logger模塊單例模式


前言

提前祝大家過個好年
最近忙於項目,今天抽出點時間寫寫Blog談談昨天遇到的問題
項目最近要收尾了,想把Logger規整一下,因為很多地方都有用到
Python的Logger模塊是Python自帶的模塊,可方便快捷的進行日志的記錄
python doc

正文

線程安全

該模塊本身就是線程安全的,下面的注釋摘抄至 doc

The logging module is intended to be thread-safe without any special work needing to be done by its clients. It achieves this though using threading locks; there is one lock to serialize access to the module’s shared data, and each handler also creates a lock to serialize access to its underlying I/O.
If you are implementing asynchronous signal handlers using the signal module, you may not be able to use logging from within such handlers. This is because lock implementations in the threading module are not always re-entrant, and so cannot be invoked from such signal handlers.

也就是說你不需要關注多線程的問題,只要 getLogger() 時指定當前空間即可

Loggers have the following attributes and methods. Note that Loggers should NEVER be instantiated directly, but always through the module-level function logging.getLogger(name). Multiple calls to getLogger() with the same name will always return a reference to the same Logger object.
The name is potentially a period-separated hierarchical value, like foo.bar.baz (though it could also be just plain foo, for example). Loggers that are further down in the hierarchical list are children of loggers higher up in the list. For example, given a logger with a name of foo, loggers with names of foo.bar, foo.bar.baz, and foo.bam are all descendants of foo. The logger name hierarchy is analogous to the Python package hierarchy, and identical to it if you organise your loggers on a per-module basis using the recommended construction logging.getLogger(name). That’s because in a module, name is the module’s name in the Python package namespace.

意思是 logger.getLogger() 時傳入相同的變量,會永遠返回同一個對象,比如我在當前進程內的任何地方, 使用 log = logger.getLogger("work") 生成的log對象一直是同一個對象,這就是單例模式,官方推薦傳入 __name__ 因為他是Python包命名空間中模塊的名稱。
如果你是糾結 Logger 的單例怎么解決,你可以關閉網頁了,因為他本身是單例的

手動寫一個單例

手動寫一個單例完全是為了記憶單例模式的使用,只是以 logger 模塊舉例

原始代碼

不考慮 logger 的自帶單例情況下的原始代碼

代碼精簡過,大致意思不變

測試代碼

測試是否可以使用的代碼

利用__new__實現單例

我們知道,python實例化時其實是先走 __new__ 再走 __init__
我們可以重寫 __new__ 方法,如果發現已生成對象直接返回該對象
同時為了防止多線程的資源競爭,我們使用線程鎖來保證同一時間只有一個線程能訪問 __new__

但是測試代碼跑過之后發現每次會輸出接近100條日志,這是為什么呢?
原來,每次請求實例化時,如有對象則直接返回之前生成的對象(MyLogger._instance),但是因為 Python3 默認繼承新式類,
Object ,每次請求時返回了 object.__new__ 然后會再執行一遍 MyLogger__init__ 方法,而我們在 __init__ 中添加了兩個 Handler ,
而上文提到, logger.getLogger 傳入同一個參數則 logger 為一個, 導致每次請求時都會添加兩個 Handler 到同一個 logger ,這樣導致 loggerHandler 越來越多,重復寫入了,解決這個問題需要防止重復走 __init__

其實正確的寫法應該是類的 __init__ 只負責接收參數,像這種 add Handler 的功能放到自寫方法中,這樣 __init__ 不會有任何 add 操作即可

利用元類繼承實現單例

該方法利用元類 Type__call__ 實例化的對象調用不會走 __init__ 的特性來規避問題

如果你覺得本方法需要覆蓋父類不太好,那么還有第三種方法

自寫初始化方法

方法1中,每次都會走 __init__,而我們在 __init__ 中又進行了 add Handler 等操作,那么我們將所有初始化及 add 操作放在自寫方法中即可

如上圖所示,這對請求實例化的用戶是無感知的,它只需要和之前一樣調,但其實內部在實例化時調用了 start, 同時重復實例化時走 __init__ 沒有任何代碼邏輯(走的Object)

小彩蛋

我們在實際測試過程中發現,在配置了log持久化存儲搭配多線程使用的時候,寫入log文件的日志會丟失數據,測試發現應該是實例化后立刻寫入會出現一些延遲,再加上測試代碼
寫入一條后立刻結束,導致的丟失問題,當然,在實際使用中,一般是初始化時實例化,也不可能在log后直接停止
但是問題還是要解決,實例化我們在每次請求實例化時等待一下即可 time.sleep(1) ,等待時間與機器性能有關,好的機器不會出現問題,
1s是保險的
然后最終代碼為


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM