logging.config.dictConfig()を使用してFilterを実装
はじめに
ログフィルターの実装はfileConfig()
では不可能でdictConfig()
では可能。
サンプルなどを探してはみたが自身の欲しかったものは見つからなかった。
だからまとめる。
ログフィルター
loggingでは出力するログメッセージはlogging.LogRecord
オブジェクトに格納されLogger間でやり取りされている。
Loggerが受け取ったLogRecordを出力するか否かを判断するために使用するものがログフィルター
HandlerやFormatterにはある程度テンプレートのようなクラスが存在したがFilterには存在せず、条件の記述がユーザー独自に柔軟にカスタマイズできるようになっている。 それゆえにサンプルコードもあまり落ちていない気がする。
要するに以下みたいなことができる
- メッセージ内に
password
が入っているからこのログはドロップする - INFOだけ出力し、それ以外(DEBUG,ERROR...)はドロップする
- 特定の時間に出力されたメッセージのみ出力する(こんな使い道は存在するのか不明だが可能)
やりたいこと
- loggingの設定記述を外部ファイルにまとめる
- main.pyの宣言部分をなるべく簡素にする
- メッセージ内に特定のメッセージが含まれる場合はメッセージにモザイクをかける
- フィルタリングする特定のメッセージは外部ファイルで複数定義できる
- 複数ファイル間で同一のlogging設定を有効にする
構成
├main.py ├mylogging.py # Logger設定実行ファイル ├account.py # クラス定義ファイル └logging.json # Logger設定ファイル
実行結果
> python .\main.py [DEBUG]account -> account has been imported [DEBUG]__main__ -> default id:example [DEBUG]__main__ -> Filterd: password [DEBUG]account.GoogleAccount -> id:example@gmail.com [DEBUG]account.GoogleAccount -> Filterd: password [DEBUG]account.YahooAccount -> id:example@yahoo.com [DEBUG]account.YahooAccount -> Filterd: password [DEBUG]__main__ -> accounts initialize finish [DEBUG]__main__ -> Filterd: secret
main.py
import mylogging from logging import getLogger mylogging.setLoggerConfig("./logging.json") logger = getLogger(__name__) import account def main(): username = "example" password = "P@ssw0rd" logger.debug("default id:" + username) logger.debug("default password:" + password) google = account.GoogleAccount(username, password) yahoo = account.YahooAccount(username, password) logger.debug("accounts initialize finish") logger.debug("secret word!") if __name__ == "__main__": main()
mylogging.py
def setLoggerConfig(file_path): from json import load from logging import config, Filter class MyFilter(Filter): def __init__(self, words=None): if not isinstance(words, list): words = None self.words = words def filter(self, record): if self.words is not None: for word in self.words: if word in record.msg: record.msg = "Filterd: " + word break return True with open(file_path, "r", encoding="utf-8") as f: log_conf_dic = load(f) log_conf_dic["filters"]["filterExample"]["()"] = MyFilter config.dictConfig(log_conf_dic)
account.py
from logging import getLogger logger = getLogger(__name__) logger.debug("account has been imported") class Credential(object): domain = "" def __init__(self, username, password): self.logger = logger.getChild(self.__class__.__name__) self.username = username + "@" + self.domain self.password = password self.logger.debug("id:" + self.username) self.logger.debug("password:" + self.password) class GoogleAccount(Credential): domain = "gmail.com" pass class YahooAccount(Credential): domain = "yahoo.com" pass
logging.json
{ "version": 1, "disable_existing_loggers": false, "root": { "level": "DEBUG", "handlers": [ "consoleHandler" ] }, "handlers": { "consoleHandler": { "class": "logging.StreamHandler", "level": "DEBUG", "formatter": "simpleFormatter", "filters": [ "filterExample" ], "stream": "ext://sys.stdout" } }, "formatters": { "simpleFormatter": { "format": "[%(levelname)s]%(name)s -> %(message)s" } }, "filters": { "filterExample": { "()": "", "words": [ "password", "secret" ] } } }
解説
上のほうに書いたやりたいことリストをもとに解説
1. loggingの設定記述を外部ファイルにまとめる
- JSONやYAMLなど好きな形式で外部ファイルに保存する(上記の例ではlogging.json)
- プログラム内では設定ファイルを辞書化してそれを
logging.config.dictConfig()
に渡す。
2. main.pyの宣言部分をなるべく簡素にする
- 別ファイルで定義(mylogging.py)
3. メッセージ内に特定のメッセージが含まれる場合はメッセージにモザイクをかける
logging.json
- dict["handlers"]["filters"]要素の追加
- 指定のHandlerに適用するFilterを配列で指定
- dict["filters"][<FILTER_NAME>]要素を追加
"()"
: ユーザ定義FilterClassを格納。別ファイルでは格納不可なので入れ物だけ定義
- dict["handlers"]["filters"]要素の追加
mylogging.setLoggerConfig()
- ユーザ定義FlterClassの作成
logging.Filter
を継承(推奨※filter()
さえちゃんとしてれば動くらしい)filter()
関数を定義(必須)- 引数は
logging.LogRecord
インスタンスを一つとる - 返り値はbooleanとし、Trueであれば出力、Falseであればドロップ
- 条件を自由に記述
- 引数は
- 設定格納辞書にユーザ定義FilterClassを登録
dict["filters"][<FILTER_NAME>]["()"]
の値に作成したユーザ定義FilterClassを代入
- ユーザ定義FlterClassの作成
4. フィルタリングする特定のメッセージは外部ファイルで複数定義できる
logging.json
- dict["filters"][<FILTER_NAME>]["words"]要素を追加
"()"
に格納されるユーザ定義FlterClassの__init__
に渡す引数。例ではwords
としたが__init__
の引数と一致すれば組み合わせは自由
- dict["filters"][<FILTER_NAME>]["words"]要素を追加
mylogging.setLoggerConfig()
- ユーザ定義FlterClassの編集
__init__
を実装する- 引数はキーワード引数とし、
dict["filters"][<FILTER_NAME>]
配下の"()"
以外のすべてが渡される
- 引数はキーワード引数とし、
- ユーザ定義FlterClassの編集
5. 複数ファイル間で同一のlogging設定を有効にする
main.py
- 他モジュールのimport位置を
logging.config.dictConfig()
以降にする
- 他モジュールのimport位置を
account.py
- あまりやりたいことと関係ないけど子Loggerを作成したりしてる