概要(loggingのレベルは「重要度のフィルタ」で情報量を制御する仕組み)
Pythonのloggingには重要度を表す5段階のレベル(DEBUG/INFO/WARNING/ERROR/CRITICAL)があり、設定したレベル以上のログだけが出力されます。これにより「開発では詳細を出す/本番では必要最小限に抑える」をコード変更なしで切り替えられます。肝は、ロガー・ハンドラ・メッセージ各所のレベルが“合成されて”最終出力が決まること。レベル設計を正しく行うと、原因追跡に十分なログを保ちながら、ノイズやコストを最小化できます。
標準の5レベル(何をどのレベルで書くかの指針)
レベルの意味と使い分け
DEBUGは「開発者だけが知りたい内部状態」、INFOは「ユーザーや運用で意味のあるイベント」、WARNINGは「想定外だが継続可能」、ERRORは「機能として失敗」、CRITICALは「システム継続が困難」。実務では、開始・終了・成功はINFO、再試行や劣化運転はWARNING、例外発生や機能中断はERROR、致命的設定欠落や依存サービス停止はCRITICALにします。内部変数や分岐結果はDEBUGへ寄せ、運用では出さない前提で設計します。
最短のレベル設定と出力例
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logging.debug("詳細(通常は非表示)")
logging.info("処理開始")
logging.warning("設定が見つからないためデフォルト使用")
logging.error("外部API呼び出し失敗")
logging.critical("必須設定が欠落。起動不能")
PythonbasicConfigのlevelにINFOを指定すると、INFO以上(INFO/WARNING/ERROR/CRITICAL)のみ表示され、DEBUGは抑制されます。必要なタイミングだけレベルを切り替えることで、同じコードでも情報量を変えられます。
ロガー・ハンドラ・メッセージの“合成”で決まる出力(ここが重要)
出力可否の決まり方(効果的レベル)
ログが出るかは「ロガーのレベル」「ハンドラのレベル」「メッセージのレベル」の3つで決まります。まずロガーがメッセージを通すか判定し、通ったものが各ハンドラに渡され、ハンドラごとに「自分のレベル以上か」を再判定して出力します。つまり、画面はINFO以上、ファイルはDEBUG以上、といった“先ごとのレベル差”が可能です。
実例(コンソールはINFO、ファイルはDEBUGで詳細)
import logging
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG) # ロガーは広めに通す
console = logging.StreamHandler()
console.setLevel(logging.INFO) # 画面は要約
console.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
file = logging.FileHandler("app.log", encoding="utf-8")
file.setLevel(logging.DEBUG) # ファイルは詳細
file.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s"))
logger.addHandler(console)
logger.addHandler(file)
logger.info("起動")
logger.debug("内部状態: config=%s", {"retry": 3, "timeout": 5})
PythonロガーはDEBUGで「全部通す」設計にし、出力先のハンドラで粒度を絞るのが鉄板です。
レベル設計の実務指針(情報量・再現性・コストのバランス)
何をどのレベルにするか(誤用の典型を避ける)
成功や終了をERRORで記録するのは誤用です。ERRORは「ただちに調査すべき失敗」に限定し、成功はINFO、非推奨や自動回復した小問題はWARNINGにします。DEBUGは「再現に必要な内部状態」をピンポイントに入れ、ループ全件や大きなペイロードの常時出力は避けます(性能・ログ肥大の観点)。
再試行と状態遷移のレベル
再試行開始はWARNING、成功はINFO、再試行尽きて断念はERROR。UIやジョブの状態遷移(開始/完了)はINFOで統一すると、運用ダッシュボードの集計が行いやすくなります。
本番・検証・開発の切り替え
本番はINFO以上、検証はWARNING以上にしつつ特定モジュールだけDEBUGを一時解放、開発は全体DEBUG。この切り替えは環境変数や設定ファイルで行い、コードにはレベル固定の値を埋め込まない設計が望ましいです。
import logging, os
level = logging.DEBUG if os.getenv("APP_DEBUG") == "1" else logging.INFO
logging.basicConfig(level=level, format="%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s")
Pythonモジュール別レベル、子ロガー、フィルタ(柔軟に絞る高度な使い方)
モジュールごとにレベルを変える
大きなプロジェクトでは「通信だけDEBUG、その他はINFO」のように、ロガー名ベースで細かく制御します。
import logging
root = logging.getLogger()
root.setLevel(logging.INFO)
net = logging.getLogger("app.net")
net.setLevel(logging.DEBUG) # 通信モジュールだけ詳細
logging.getLogger("app").addHandler(logging.StreamHandler())
Pythonロガーは階層構造です。親ロガーのハンドラに子ロガーのログが伝播するため、親で出力先をまとめ、子でレベルだけ上書きすると管理が楽になります。
フィルタで条件出力(レベル以外の抑制)
レベルでは表現しづらい「特定のURLだけ抑制」「大きすぎるメッセージを切る」をFilterで実装できます。実務では「機密情報のマスク」や「ノイズの抑制」に有効です。
例外・構造化・パフォーマンス(レベル運用の落とし穴を避ける)
例外はレベルERROR以上+スタックを必ず残す
例外をINFOで出すのは誤用です。失敗はERROR以上で記録し、logger.exceptionを使ってスタックトレースを必ず残すことで、原因まで一気に辿れます。
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
logger.addHandler(handler)
try:
1/0
except Exception:
logger.exception("計算失敗")
Pythonメッセージ構築のコスト(遅延評価で軽くする)
f文字列で巨大な文字列を作ると、レベルで抑制されても整形は済んでしまい無駄です。パラメータ埋め込み(%s)にすれば、実際に出力されるまで文字列整形が行われないため軽量です。
val = {"big": "payload"}
logger.debug("内部状態=%s", val) # 出力されないなら整形コストゼロ
Python構造化ログとレベル
JSONや構造化ログにする場合も、レベルは同じ思想で運用します。パーサ側でレベルフィルタをかけ、ERROR以上をアラート、WARNING以上をダッシュボード、INFOを監査ログ、DEBUGは開発向けに保存という役割分担が機械処理と相性が良いです。
まとめ
loggingのレベルは「情報量を制御し、重要度に応じて出し分ける」ための軸です。DEBUGは内部状態、INFOはイベント、WARNINGは回復可能な異常、ERRORは機能失敗、CRITICALは致命的障害に割り当てます。出力の可否はロガー・ハンドラ・メッセージのレベルが合成されて決まり、先ごとに粒度を変えるのが実務の基本。環境でレベルを切り替え、モジュール別に細かく調整し、例外はERROR以上+スタックで確実に残す。遅延評価でコストを抑え、構造化ログにも同じ考えを適用する。これらを型として体に入れると、初心者でも“見える・追える・うるさくない”ログ運用が短いコードで実現できます。
