🧮

ログ欠落は、防ぐものではなく「説明できるもの」として設計する

に公開

ログが欠けたとき、目に見える現象はだいたい同じだ。

ファイルに行がない。

でも、その裏にある原因は同じではない。

  • ロガーに届く前に子プロセスが落ちた
  • キューが詰まって取りこぼした
  • 出力先が拒否した
  • 一部の出力先には届いた
  • ロガーが説明できない欠落がある

全部を「ログがない」で片付けると、調査の入口を間違える。

前回、ログ設定は運用契約だと書いた。その契約で一番重いのが、「何を失敗として扱うのか」だと思う。

なぜ調査は「ログがない」で止まるのか

Python の標準 logging は、ログレコードを handler に渡す仕組みを持っている。エラー時には handleError を通じて標準エラーへ出ることもある。

ただし、それだけでは「届いた」「拒否された」「取りこぼした」「説明できない」を同じ場所で数える構造にはならない。

その語彙がないと、調査は「ログがない」で止まる。止まった後、OS・キュー・終了処理・出力先・アプリケーション例外のどれを見ればいいのかを選びにくい。

欠落に名前を付ける

自分なら、ログ欠落を少なくとも次の 5 つに分けて見る。

状態 意味
delivered 必要な出力先へ配送できた
partial_delivered 一部の出力先には届いた
known_rejected 書き込み側 / 出力先が既知の理由で受け付けなかった
known_dropped 上限付きキューや配送ルールにより既知の理由で取りこぼした
unexplained_lost 届くはずだったがロガーが理由を説明できない

unexplained_lost という名前は、できれば見たくない。

でも、隠すより残した方がいい。そこに異常があると分かるからだ。

分類は保証ではない

ログ設計でよくある危険は、「失敗しない」と言いたくなることだ。

でも、OS 障害、電源断、強制終了、子プロセスの異常終了まで含めて、ローカルのロガーが全部を保証するのは無理がある。

現実的には、次の線引きになる。

  • ロガーが受け入れたものをどこまで追えるか
  • 取りこぼしや拒否を既知のものとして数えられるか
  • 説明できない欠落を説明できないまま残せるか

この方が、派手ではないが誠実だと思っている。

分類は、ログ欠落を消すためのものではない。保証できないものを保証したふりをしないためのものだ。

ケーススタディ: D-SafeLogger ではこう名付けた

D-SafeLogger のマルチプロセス構成では、この考え方を、親プロセス側の書き込み役に寄せて実装している。子プロセスは共有ログファイルを直接開かず、ログレコードを書き込み役へ送る。書き込み役が出力先ファイル、出力先の切り替え、終了処理、配送状態の集計を持つ。

子プロセス -> キュー -> 親プロセスの書き込み役 -> 出力先ファイル

所有権を親プロセス側へ寄せる理由は、Qiita #3 で実装寄りに書いた。ここで見たいのは、その構成の速さではなく、欠落をどう説明できる形にするかだ。

D-SafeLogger の mp.GetDeliveryStatus() は、実行中の集計を見る API だ。終了時の報告は、終了時点の最終集計を残すためのものだ。両方があることで、「いま何が起きているか」と「最後にどう終わったか」を切り分けて読める。

参考

終わりに

ログ欠落に名前を付けるのは、気持ちのよい作業ではない。

でも、名前がない欠落は調査しにくい。

「ログがない」で止めずに、「なぜないのか」を分類する。キューや非同期バッファを挟むログ経路では、そのために少し複雑な設計を選ぶ価値がある。

関連記事

Zenn 連載:

Qiita の関連回:

Discussion