每個 Python 程序員都要知道的日志實踐


在現實生活中,記錄日志非常重要。銀行轉賬時會有轉賬記錄;飛機飛行過程中,會有黑盒子(飛行數據記錄器)記錄飛行過程中的一切。如果有出現什么問題,人們可以通過日志數據來搞清楚到底發生了什么。

對於系統開發、調試以及運行,記錄日志都是同樣的重要。如果沒有日志記錄,程序崩潰時你幾乎就沒辦法弄明白到底發生了什么事情。舉個例子,當你在寫一個服務器程序時,記錄日志是非常有必要的。下面展示的就是 EZComet.com 服務器的日志文件截圖。

服務崩潰后,如果沒有日志,我幾乎沒辦法知道到底發生了錯誤。日志不僅對於服務器很重要,對於桌面圖形應用同樣十分重要。比如,當你的客戶的 PC 機程序崩潰時,你可以讓他們把日志文件發給你,這樣你就可以找到問題到底出在哪兒。相信我,在不同的 PC 環境下,你永遠不會知道會有怎樣奇怪的問題。我曾經就接收到過這樣的錯誤日志。

我最終發現,這個客戶的 PC 機被一種病毒感染,導致了調用 gethostname 函數失敗。看吧,如果沒有日志可以查你怎么可能知道這些。

打印輸出不是個好辦法

盡管記錄日志非常重要,但是並不是所有的開發者都能正確地使用它。我曾看到一些開發者是這樣記錄日志的,在開發的過程中插入 print 語句,開發結束后再將這些語句移除。就像這樣:

這種方式對於簡單腳本型程序有用,但是如果是復雜的系統,你最好不要使用這樣的方式。首先,你沒辦法做到在日志文件中只留下極其重要的消息。你會看到大量的消息日志。但是你卻找不到任何有用的信息。你除了移除這輸出語句這外,沒別的辦法控制代碼,但是極有可能的是你忘記了移出那些沒用的輸出。再者,print 輸出的所有信息都到了標准輸出中,這將嚴重影響到你從標准輸出中查看其它輸出數據。當然,你也可以把消息輸出到 stderr ,但是用 print 做日志記錄的方式還是不好。

使用 python 的標准日志模塊

那么,怎么樣記錄日志才是正確的呢?其實非常簡單,使用 python 的標准日志模塊。多虧 python 社區將日志做成了一個標准模塊。它非常簡單易用且十分靈活。你可以像這樣使用日志系統:

運行的時候就可看到:

你可能會問這與使用 print 有什么不同呢。它有以下的優勢:

  • 你可以控制消息的級別,過濾掉那些並不重要的消息。
  • 你可決定輸出到什么地方,以及怎么輸出。

有許多的重要性別級可供選擇,debug、info、warning、error 以及 critical。通過賦予 logger 或者 handler 不同的級別,你就可以只輸出錯誤消息到特定的記錄文件中,或者在調試時只記錄調試信息。讓我們把 logger 的級別改成 DEBUG 再看一下輸出結果:

輸出變成了:

正如看到的那樣,我們把 logger 的等級改為 DEBUG 后,調試記錄就出現在了輸出當中。你也可以選擇怎么處理這些消息。例如,你可以使用 FileHandler 把記錄寫進文件中:

標准庫模塊中提供了許多的 handler ,你可以將記錄發送到郵箱甚至發送到一個遠程的服務器。你也可以實現自己的記錄 handler 。這里將不具體講述實現的細節,你可以參考官方文檔:Basci TurialAdvanced Tutorial 與 Logging Cookbook

以合適的等級輸出日志記錄

有了靈活的日志記錄模塊后,你可以按適當的等級將日志記錄輸出到任何地方然后配置它們。那么你可能會問,什么是合適的等級呢?在這兒我將分享一些我的經驗。

大多數的情況下,你都不想閱讀日志中的太多細節。因此,只有你在調試過程中才會使用 DEBUG 等級。我只使用 DEBUG 獲取詳細的調試信息,特別是當數據量很大或者頻率很高的時候,比如算法內部每個循環的中間狀態。

在處理請求或者服務器狀態變化等日常事務中,我會使用 INFO 等級。

當發生很重要的事件,但是並不是錯誤時,我會使用 WARNING 。比如,當用戶登錄密碼錯誤時,或者連接變慢時。

有錯誤發生時肯定會使用 ERROR 等級了。比如拋出異常,IO 操作失敗或者連接問題等。

我很少使用 CRITICAL 。當一些特別糟糕的事情發生時,你可以使用這個級別來記錄。比方說,內存耗盡,磁盤滿了或者核危機(希望永遠別發生 :S)。

使用 __name__ 作為 logger 的名稱

雖然不是非得將 logger 的名稱設置為 __name__ ,但是這樣做會給我們帶來諸多益處。在 python 中,變量 __name__ 的名稱就是當前模塊的名稱。比如,在模塊 “foo.bar.my_module” 中調用 logger.getLogger(__name__) 等價於調用logger.getLogger(“foo.bar.my_module”) 。當你需要配置 logger 時,你可以配置到 “foo” 中,這樣包 foo 中的所有模塊都會使用相同的配置。當你在讀日志文件的時候,你就能夠明白消息到底來自於哪一個模塊。

捕捉異常並使用 traceback 記錄它

出問題的時候記錄下來是個好習慣,但是如果沒有 traceback ,那么它一點兒用也沒有。你應該捕獲異常並用 traceback 把它們記錄下來。比如下面這個例子:

使用參數 exc_info=true 調用 logger 方法, traceback 會輸出到 logger 中。你可以看到下面的結果:

你也可以調用 logger.exception(msg, _args),它等價於 logger.error(msg, exc_info=True, _args)。

千萬不要在模塊層次獲取 Logger,除非 disable_existing_loggers 被設置為 False

你可以看到很多在模塊層次獲取 logger 的例子(在這篇文章我也使用了很多,但這僅僅為了讓示例更短一些)。它們看上去沒什么壞處,但事實上,這兒是有陷阱的 – 如果你像這樣在模塊中使用 Logger,Python 會保留從文件中讀入配置前所有創建的所有 logger。

my_module.py

main.py

logging.ini

本應該在日志中看到記錄,但是你卻什么也沒有看到。為什么呢?這就是因為你在模塊層次創建了 logger,然后你又在加載日志配置文件之前就導入了模塊。logging.fileConfig 與 logging.dictConfig 默認情況下會使得已經存在的 logger 失效。所以,這些配置信息不會應用到你的 Logger 上。你最好只在你需要 logger 的時候才獲得它。反正創建或者取得 logger 的成本很低。你可以這樣寫你的代碼:

這樣,logger 就會在你加載配置后才會被創建。這樣配置信息就可以正常應用。

python2.7 之后的版本中 fileConfg 與 dictConfig 都新添加了 “disable_existing_loggers” 參數,將其設置為 False,上面提到的問題就可以解決了。例如:

 

使用 JSON 或者 YAML 記錄配置

雖然你可以在 python 代碼中配置你的日志系統,但是這樣並不夠靈活。最好的方法是使用一個配置文件來配置。在 Python2.7 及之后的版本中,你可以從字典中加載 logging 配置。這也就意味着你可以從 JSON 或者 YAML 文件中加載日志的配置。盡管你還能用原來 .ini 文件來配置,但是它既很難讀也很難寫。下面我給你們看一個用 JSON 和 YAML 文件配置的例子:

logging.json

logging.yaml

接下來將展示怎樣從 JSON 文件中讀入日志的配置信息:

使用 JSON 的一個優點就是 json是一個標准庫,你不需要額外安裝它。但是從我個人來說,我比較喜歡 YAML 一些。它無論是讀起來還是寫起來都比較容易。你也可以使用下面的方法來加載一個 YAML 配置文件:

接下來,你就可以在運行程序的時候調用 setup_logging 來啟動日志記錄了。它默認會讀取 logging.json 或 logging.yaml 文件 。你也可以設置環境變量 LOG_CCFG 從指定路徑加載日志配置。例如:

如果你喜歡 YAML:

 

使用旋轉文件句柄

如果你用 FileHandler 寫日志,文件的大小會隨着時間推移而不斷增大。最終有一天它會占滿你所有的磁盤空間。為了避免這種情況出現,你可以在你的生成環境中使用 RotatingFileHandler 替代 FileHandler。

如果你有多個服務器可以啟用一個專用的日志服務器

當你有多個服務器和不同的日志文件時,你可以創建一個集中式的日志系統來收集重要的(大多數情況是警告或者錯誤消息)信息。然后通過監測這些日志信息,你就可以很容易地發現系統中的問題了。

總結

Python 的日志庫設計得如此之好,真是讓人欣慰,我覺得這是標准庫中最好的一部分了,你不得不選擇它。它很靈活,你可以用你自己的 handler 或者 filter。已經有很多的第三方的 handler 了,比如 pyzmq 提供的 ZeroMQ 日志句柄,它允許你通過 zmq 套接字發送日志消息。如果你還不知道怎么正確的使用日志系統,這篇文章將會非常有用。有了很好的日志記錄實踐,你就能非常容易地發現系統中的問題。這是很非常值得投資的。:)


免責聲明!

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



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