🧠 lru_cache を使った「遅延・一度きり」の logger 初期化
🧭 はじめに(What)
このページでは、
functools.lru_cache(maxsize=1) を使って logger を「遅延初期化」し、かつ「一度だけ設定する」設計パターン
を解説する。
これは単なるシングルトン実装ではない。 ログ設定という副作用を、適切なタイミング・回数・責務の境界に閉じ込めるための設計技法である。
🎯 ねらい
- logger 実装で起こりがちな 初期化タイミング事故を防ぐ
importと副作用を切り離す- ログ設定の 責務と実行順序を明示化する
- テスト可能性を高める
🧱 背景:logger が壊れやすい理由
Python の logging 周りで頻発する問題は、ほぼ次の3つに集約できる。
- import 時に設定が走ってしまう
- 設定が複数回適用される
- 初期ログだけ挙動が違う
これらはすべて、「副作用(logging 設定)がどこで・いつ走るか分からない」ことが原因。
logger オブジェクト自体は共有されるが、設定(handlers / level / format)は副作用として何度でも適用できてしまう。
❌ よくあるアンチパターン
import 時に設定してしまう
# logging_.py
configure_logging() # importした瞬間に副作用
logger = logging.getLogger("app")
問題点
- import しただけで挙動が変わる
- import 順序に依存する
- テストや再利用で事故りやすい
ライブラリコードが import 時に logging 設定を行うのは、最も避けるべき設計の一つ。
✅ 解決策:lru_cache(maxsize=1) による遅延初期化
基本形
from functools import lru_cache
import logging
import logging.config
@lru_cache(maxsize=1)
def get_logger() -> logging.Logger:
configure_logging()
return logging.getLogger("app")
何をしているか
- logger を 関数として提供
- 初回呼び出し時のみ
configure_logging()を実行 - 2回目以降はキャッシュされた logger を即返却
🧠 このパターンの本質
この設計の本質は Singleton ではない。
「副作用を伴う初期化処理を、一度だけ、必要になった瞬間に実行する」ことを、言語機能だけで保証している。
🔍 実行順序で見る挙動
初回呼び出し
get_logger()が呼ばれる- キャッシュ未作成 → 関数本体を実行
configure_logging()が実行される- logger を返す
- ログ出力
2回目以降
get_logger()が呼ばれる- キャッシュヒット
- 設定処理は一切走らない
- logger を即返却
この挙動は import 順序・呼び出し順序に依存しない。
🧩 「main 冒頭で設定」との違い
main 冒頭で設定する方式
configure_logging()
import feature_a
import feature_b
- 実行順序を守れれば最も単純
- ただし 順序を破ると即破綻
- 呼び出し経路が複数あると統制が難しい
lru_cache 方式
- 呼び出し側がどこで logger を使っても安全
- import しただけでは副作用が起きない
- 初期化の責務が 関数境界に閉じる
「規律で守る設計」から「構造で守る設計」への転換。
🧪 テストでの嬉しさ
def test_logging():
get_logger.cache_clear()
logger = get_logger()
- 初期化状態を明示的にリセット可能
- テスト間の干渉を防げる
- モジュール変数より圧倒的に扱いやすい
⚠️ 使いどころの見極め
向いているケース
- logging 設定が 副作用を伴う
- import 時に設定したくない
- 呼び出し元の main を制御できない
- テストで初期化をやり直したい
向いていないケース
- 単に
getLogger(__name__)するだけ - アプリの main を完全に掌握している
- logging 設定を一切行わないライブラリ
このパターンは「常に使うもの」ではなく、副作用管理が問題になったときの選択肢。
✅ チェックリスト
- logging 設定は import 時に走っていないか?
- 設定処理は1回だけでよいか?
- 初期化のタイミングを制御したいか?
- テストで状態を戻す必要があるか?
🔚 まとめ
lru_cache(maxsize=1)は Singleton のための道具ではない- 本質は 副作用付き初期化の制御
- logger 実装では特に相性が良い
- main 冒頭で設定できるなら不要
- できない/したくない状況で威力を発揮する
「いつ・どこで・何が起きるか」をコードから読めること。 それが、この logger 実装の最大の価値である。