以下の要件を満たすロギングの方法を調べていたのですが、目的の情報が全然見つからなかったので書き残しておくことにしました。
我流ですが"ベストプラクティス"に大きく逸れてはいないと思われます。
要件
- 生成したloggerは共有
- コンソールにはINFO以上を出力
- ファイルにはDEBUG以上を出力
- ERROR以上のログはメール通知
- 複数回呼び出したときにログが重複しない
実装
同じ階層にmain.py
とmy_logger.py
があるとします。
my_logger.py
import logging
import logging.handlers
def set_logger(module_name):
logger = logging.getLogger(module_name)
logger.handlers.clear()
streamHandler = logging.StreamHandler()
fileHandler = logging.handlers.RotatingFileHandler("./test.log", maxBytes=10000, backupCount=5)
formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] (%(filename)s | %(funcName)s | %(lineno)s) %(message)s")
streamHandler.setFormatter(formatter)
fileHandler.setFormatter(formatter)
logger.setLevel(logging.DEBUG)
streamHandler.setLevel(logging.INFO)
fileHandler.setLevel(logging.DEBUG)
logger.addHandler(streamHandler)
logger.addHandler(fileHandler)
logger.addHandler(emailHandler)
return logger
main.py
import my_logger
logger = my_logger.set_logger(__name__)
logger.debug("debug")
logger.info("info")
logger.warning("warn")
logger.error("error")
logger.critical("critical")
結果
コンソール
$ python /app/tmp/main.py
2021-05-22 01:21:42,966 [INFO] (main.py | <module> | 7) info
2021-05-22 01:21:42,966 [WARNING] (main.py | <module> | 8) warn
2021-05-22 01:21:45,395 [ERROR] (main.py | <module> | 9) error
2021-05-22 01:21:47,376 [CRITICAL] (main.py | <module> | 10) critical
test.log
2021-05-22 01:21:42,965 [DEBUG] (main.py | <module> | 6) debug
2021-05-22 01:21:42,966 [INFO] (main.py | <module> | 7) info
2021-05-22 01:21:42,966 [WARNING] (main.py | <module> | 8) warn
2021-05-22 01:21:45,395 [ERROR] (main.py | <module> | 9) error
2021-05-22 01:21:47,376 [CRITICAL] (main.py | <module> | 10) critical
my_logger.pyの解説
streamHandler = logging.StreamHandler()
fileHandler = logging.handlers.RotatingFileHandler("./test.log", maxBytes=10000, backupCount=5)
各種ハンドラを定義します。
ログファイルは1MB毎に分割して、直近5つまで保存しておきます。
formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] (%(filename)s | %(funcName)s | %(lineno)s) %(message)s"
)
streamHandler.setFormatter(formatter)
fileHandler.setFormatter(formatter)
emailHandler.setFormatter(formatter)
ログの形式は好きに設定します。
必要であれば、ハンドラごとにフォーマットを分けることもできます。
例えば、ログファイルはシンプルな形式にするとか。
logger.setLevel(logging.DEBUG)
streamHandler.setLevel(logging.INFO)
fileHandler.setLevel(logging.DEBUG)
emailHandler.setLevel(logging.WARNING)
ここが今回のミソです。
この設定をしなければ、プログラム内で使用しているライブラリによっては、それらのログが混じってしまうことがあります。
ベースレベルはWARNINGに設定されているようです。
print(logging.root.level)
# 30
そこで一旦、logger自体のレベルをDEBUGにし、ルートロガーの設定を上書きします。
これで改めて、子ロガー側からレベルを上書きできます。