logging.config.dictConfigとpydantic-settingsを組み合わせてファイルや環境変数からログ設定を読み込む
概要
ファイルからログ設定を読み込むにはlogging.config.fileConfig
がありますが、次の理由から使う気になれません。
-
設定ファイルの仕様を覚えるのが面倒
-
設定の一部を環境変数で上書きしたいけれど、できない
-
logging.config.dictConfig
に比べて機能不足という注意書きがある注釈 fileConfig() API は dictConfig() API よりも古く、ロギングのある種の側面についてカバーする機能に欠けています。たとえば fileConfig() では数値レベルを超えたメッセージを単に拾うフィルタリングを行う Filter オブジェクトを構成出来ません。 Filter のインスタンスをロギングの設定において持つ必要があるならば、 dictConfig() を使う必要があるでしょう。設定の機能における将来の拡張は dictConfig() に対して行われることに注意してください。ですから、そうするのが便利であるときに新しい API に乗り換えるのは良い考えです。
これらのことからlogging.config.dictConfig
とpydantic-settingsを組み合わせてファイルからログ設定を読み込みつつ一部を環境変数で上書きできる方法を考えました。
ログ設定を読み込むコード
pydantic-settingsで.env
ファイルおよび環境変数から設定値を読み込むための設定クラスを定義し、読み込んだ設定値をもとにlogging.config.dictConfig
でログを設定しています。
from logging.config import dictConfig
from typing import Any
from pydantic_settings import (
BaseSettings,
SettingsConfigDict,
)
class LoggingSettings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
env_nested_delimiter="__",
)
logging: dict[str, Any] = {}
dictConfig(LoggingSettings().logging)
pydantic-settingsで.env
ファイルから設定値を読み込むためのクラスはDotEnvSettingsSource
、環境変数から設定値を読み込むためのクラスはEnvSettingsSource
ですが、これらは設定値にJSONを書くとdecode_complex_value
メソッドで辞書型に変換してくれます。
この性質を利用することで.env
ファイルの内容は例えば次のように書けます。
LOGGING='
{
"version": 1,
"formatters": {
"default": {
"format": "[%(levelname)s] %(name)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "default"
}
},
"loggers": {
"foo": {
"level": "DEBUG",
"handlers": ["console"]
},
"bar": {
"level": "INFO",
"handlers": ["console"]
}
}
}
'
また、設定クラスのmodel_config
でenv_nested_delimiter
を指定しているため、次のようにしてネストした設定値を環境変数で表現できます。
LOGGING__LOGGERS__BAR__LEVEL=DEBUG
動作確認
いくつかのロガーを定義してINFO
レベルとDEBUG
レベルでログを出力するコードを用いて動作確認します。
loggers = [
getLogger("foo"),
getLogger("bar"),
getLogger("baz"),
]
for logger in loggers:
logger.info("message1")
logger.debug("message2")
設定値は先ほど示した通りです。
環境変数で上書きしない場合、各ロガーの設定は.env
ファイルの内容が適用されます。
ロガー | 設定 |
---|---|
foo |
DEBUG レベルのログを出力する |
bar |
INFO レベルのログを出力する |
baz |
設定されていないのでログを出力しない |
まずは環境変数で上書きせず実行してみます。
$ python -m app.main
[INFO] foo - message1
[DEBUG] foo - message2
[INFO] bar - message1
次に環境変数で設定値を上書きし、bar
がDEBUG
レベルのログを出力できるようにします。
$ LOGGING__LOGGERS__BAR__LEVEL=DEBUG python -m app.main
[INFO] foo - message1
[DEBUG] foo - message2
[INFO] bar - message1
[DEBUG] bar - message2
最後に環境変数でJSON形式の設定値を追加してみます。
baz
にハンドラーを設定してDEBUG
レベルのログを出力できるようにします。
$ LOGGING__LOGGERS__BAZ='
{
"level": "DEBUG",
"handlers": ["console"]
}
' python -m app.main
[INFO] foo - message1
[DEBUG] foo - message2
[INFO] bar - message1
[INFO] baz - message1
[DEBUG] baz - message2
期待通りに動作してくれました。
参考
logging.config.dictConfig
へ渡す辞書について説明しているページです。
pyproject.toml
なども含むコード例の全体です。
Discussion