標準モジュールの logging を自作クラスで作って、ログの出力先などをカスタマイズしたい場合、既存のハンドラを削除する必要があります
Python の標準モジュールに logging がありますが、少しクセの強いモジュールになっています
そのうちの1つとして basicConfig を何度も設定することができません
import logginglogging.basicConfig(filename='log.log',level=logging.INFO,encoding='utf-8',)
たとえば、自作ロガーをクラスで作って __init__ にこれを書くと、2回以上の呼び出しでエラーになってしまいます
どうやら、ハンドラの上書きを禁止しているっぽいので、削除する必要があるようです。以下で削除できます
for handler in logging.root.handlers[:]:logging.root.removeHandler(handler)
ただし、設定すべてを削除することになってしまうので、呼び出しのたびに設定する必要がでてきます
私は logging.Filter の継承クラスを使って、初期化設定を毎回行うことにしています
class InitFilter(logging.Filter):"""ロガーの初期化フィルター"""def __init__(self, filepath: str):super().__init__()self.__filepath = filepathdef logger_init(self):"""ロギングの初期化"""# 既存ハンドラを削除するfor handler in logging.root.handlers[:]:logging.root.removeHandler(handler)# ロギングを設定logging.basicConfig(filename=self.__filepath,level=logging.INFO,encoding='utf-8',)def filter(self, record):"""フィルター (logger初期化設定を追加)"""self.logger_init()return super().filter(record)
このようにして、フィルターを作ったら logging に登録します
たとえば Logger クラスのコンストラクタに、以下のようにして初期化フィルターを追加します
class Logger:"""ロガー"""def __init__(self, name: str, filepath: str):"""ロガーの初期化"""self.logger = logging.getLogger(name)self.filepath = logger_config.filename if filepath is None else filepath# 初期化フィルター追加init_filter = self.InitFilter(self.filepath)self.logger.addFilter(init_filter)# ロギングの初期化init_filter.logger_init()# self.__init_loggingを事前に呼び出すメソッドを追加self.debug = self.logger.debugself.info = self.logger.infoself.warning = self.logger.warningself.error = self.logger.errorself.critical = self.logger.critical
ログの必要な場所でインポートし、以下のようにすればカスタムロガーを簡単に呼び出せます 🎉
from .logger import Loggerlog = Logger(__name__, "log.log")log.info("Hello, World!")
おわりに
以下のように上書きすることもできるのですが、
class Logger:def info(self, msg):logging.basicConfig(...)self.logger.info(msg)
これだと呼び出し元の行番号等が上手く取れないので、フィルターを利用してます 🐶
(なにかもっと良い方法があるような気もしています...)