2017年7月12日 星期三

python 的 logging 機制

log 是寫程式最常用到的功能之一, C# 有好用的 log4net, java 有 log4j, 而 python 不需要第三方函式庫, 內建就有 logging 模組可使用

如果只是想做個小工具或 prototype, logging 模組有提供 module level 的函式, 直接呼叫 logging.info 或 logging.error 就可以在 console 印出 log 來, 不過預設設定是會濾掉 info 以下 level 的 log, 這之後可以用 Logger.setLevel 修改

import logging
logging.warning('Watch out!')  # will print a message to the console
logging.info('I told you so')  # will not print anything 

若是想將 log 寫入到檔案中, 只需用 logging.basicConfig 做些設定即可 

import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')

但如果我有更複雜的需求, 比如想同時寫到 console 跟 file, 或是希望自訂 log 印出的格式應該怎麼做?這時需要先理解 logging 模組的架構, 可以先看下面的 log 架構圖

https://docs.python.org/2/_images/logging_flow.png

Logger 

Logger 是用來產生 log message 的類別, 他有 info, error, debug, warn 跟 critical 函式可以產生不同 level 的 log, client 必須先取得 logger 才能寫入 log message

程式中可以有多個 logger, 每個 logger 有不同的 name, name 還可以決定 logger 間的繼承關係, 例如有個 logger 叫 foo, 另一個 looger 叫 foo.bar, 則 foo.bar 就會繼承 foo, logging 模組內建有一個 root logger, 所有的 logger 都會繼承它, 而前面所說的 logging.info 等模組層級函式內部就是使用 root logger

Handler

logger 中可以 attach 上多個 handler, logger 產生 log 後, handler 負責將其送到目的地去, 前面所說若想同時將 log 寫到 console 和 file 中, 只需要將 logging 模組內建的 StreamHandler 和 FileHandler 都 attach 到同一個 looger 中, 就可以將 log 同時寫到 console 跟 file

Formatter

logging 中有 Formatter 可以讓使用者客製化想印出的 log 格式, 只要將 formatter 設定好並且 attach 到 handler 中就可以

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)


繼承關係

在 create logger 時你可以不指定這些 handler, formatter, 那新創建的 logger 的 handler 與 formatter 等設定就會繼承它的 parent, 例如 root logger


以上這些都可以用 code 設定好, 但是一般建議的方式還是把這些關於 log 設定的部分寫在額外的 log config 中方便管理維護, python logging 模組提供了兩種 config 方式, ini 式的 config 和 dictionary 式的 config,

dictionary 式的 config 看起來更直覺一點, 不過需要使用到 yaml package 來做讀取

logging.config.dictConfig(yaml.load(open('logging.conf', 'r')))

沒有留言:

張貼留言