自作 logger を避けられないとき、本当に必要な最小要件は何か: 実務向け要件と結合テスト観点

· · Windows Development, Logging, Integration Testing, Test Design, Reliability

既製の logging framework を使えるなら、そのほうが安全です。それでも、アプリケーション側の制約や運用上の事情で自作 logger を避けられない場面はあります。そこで最初に悩みやすいのが、どこまで実装すれば「雑すぎず、重すぎない」設計になるのか、という点です。

本記事では、対象を障害調査用のアプリケーションログに絞ります。監査証跡、分散トレーシング、メトリクス基盤、クラウド集約まで一度に背負い込まず、まずは現場で役に立つ最小構成を定義し、その構成を本当に信用できるようにするための結合テスト観点を整理します。

目次

  1. 先に結論
  2. まず対象範囲を狭くする
  3. 最低限必要な要件
    • 1. 形式は UTF-8 JSON Lines
    • 2. 必須項目を最初に固定する
    • 3. 1 プロセス 1 ファイルを基本にする
    • 4. 書き込み戦略は負荷で分ける
    • 5. flush 条件を決める
    • 6. 回転と保持は v1 から入れる
    • 7. 保存失敗時に勝手な代替保存をしない
  4. v1 の最小構成イメージ
  5. よくある NG
  6. 結合テストは実ファイル・実スレッド・実プロセスで考える
  7. 通しておきたい結合テスト項目
    • 単一書き込みの健全性
    • 同一プロセス内の並行実行
    • flush と終了時の挙動
    • 回転と保持
    • 異常系
    • 複数プロセスの扱い
  8. v1 で最低限通したい本数
  9. まとめ

先に結論

最初の版で押さえたい要点は次のとおりです。

  • 形式は UTF-8JSON Lines にする
  • 1 レコード 1 行を崩さない
  • 必須項目は 時刻レベルカテゴリメッセージ構造化 fieldssessionIdprocessId
  • 基本は 1 プロセス 1 ファイル
  • 低負荷なら同期書き込み、多めに出るなら single writer + bounded queue
  • Error / Critical とセッション開始・終了は同期 flush する
  • 回転と保持は v1 から入れる
  • 保存先が使えないときに、黙って別の場所へ逃がさない

このくらいまで絞ると、実装と運用の両方で破綻しにくくなります。

まず対象範囲を狭くする

自作 logger が難しくなりがちなのは、最初から何でも扱おうとするからです。診断ログ、監査ログ、性能計測、分散トレース、ユーザー行動分析を一つの仕組みでまとめようとすると、要件が一気に増えます。

今回の対象は、アプリケーション障害の切り分けに使う診断ログです。つまり、「いつ」「どの処理で」「何が起き」「そのときどんな文脈だったか」を後から追えることを優先します。これに絞るだけで、最初の設計判断はかなり楽になります。

最低限必要な要件

1. 形式は UTF-8 JSON Lines

プレーンテキストの連結でもログは残せますが、後から機械的に扱いにくくなります。逆に、最初から重い独自バイナリ形式にすると、運用時の可観測性が落ちます。

その中間として扱いやすいのが UTF-8JSON Lines です。1 行が 1 レコードであれば、テキストとしても読みやすく、後でスクリプトやツールから解析しやすくなります。途中で書き込みが切れても、壊れたのがどの行かを切り分けやすい点も実務向きです。

2. 必須項目を最初に固定する

最低限そろえておきたい項目は次の 7 つです。

  • timestamp
  • level
  • category
  • message
  • fields
  • sessionId
  • processId

message だけの文字列ログにすると、あとから検索条件が増えたときに困ります。逆に項目を増やしすぎると、呼び出し側の負担が急に上がります。最初はこのくらいに固定し、追加は本当に必要になってから検討するのが安全です。

3. 1 プロセス 1 ファイルを基本にする

複数プロセスから同じファイルへ追記させる設計は、見た目以上に事故要因が多くなります。排他制御、部分書き込み、回転タイミング、異常終了時の扱いが一気に難しくなるからです。

まずは 1 プロセス 1 ファイル を基本にしてください。複数プロセスをまとめたいなら、後段で集約するか、専用の集約プロセスを明示的に立てるほうが安全です。

4. 書き込み戦略は負荷で分ける

ログ量が少ない段階では、同期書き込みのほうが分かりやすく、障害調査もしやすいです。無理に非同期化すると、終了直前のログを失ったり、例外時の flush 条件が曖昧になったりします。

一方で、ログ量が多くなり同期 I/O が律速になるなら、single writer + bounded queue を採用します。このとき重要なのは、キューがあふれたときの方針を先に決めることです。古いログを捨てるのか、新しいログを落とすのか、警告を出すのかを曖昧にしないでください。

5. flush 条件を決める

ErrorCritical、それからセッション開始・終了のログは、同期 flush しておくと障害調査で助かります。普段の Info まで全部 flush すると遅くなるため、全部を同じ扱いにしないのが現実的です。

6. 回転と保持は v1 から入れる

回転は「後で入れればよい」と思われがちですが、運用に入ると急に困る機能です。サイズ、日次、起動ごとなど方式は何でもよいので、少なくとも「無限に増え続けない」ことと「何本残すか」が決まっている状態にしておくべきです。

7. 保存失敗時に勝手な代替保存をしない

ログ保存先が使えないときに、黙って別の場所へ書く設計は、後で調査を難しくします。運用担当が「あるはずの場所」にログがないだけで、障害対応の初動が遅れます。

保存できないなら、アプリ側の通知、イベントログ、標準エラーなど、明示的に分かる手段で失敗を表に出してください。少なくとも「どこに行ったか分からない」状態は避けるべきです。

v1 の最小構成イメージ

最初の版では、次のくらいで十分なことが多いです。

  • UTF-8 JSON Lines
  • 1 プロセス 1 ファイル
  • セッション単位のファイル名
  • サイズベースまたは起動単位の回転
  • 保持本数の上限
  • Error / Critical の同期 flush
  • 構造化 fields を受け取れる API

これ以上の機能は、実際の運用で「本当に困ったこと」が見えてから足すほうが、結果として保守しやすくなります。

よくある NG

避けたい典型例も挙げておきます。

  • message 文字列にすべてを詰め込む
  • 複数プロセスで同じファイルを共有する
  • flush 条件を決めずに全面非同期化する
  • 回転と保持を後回しにする
  • 保存失敗時に黙って別フォルダへ逃がす
  • ネットワーク転送やローカル DB 保存まで v1 に入れる

どれも一見便利そうですが、切り分けや運用を重くしやすい項目です。

結合テストは実ファイル・実スレッド・実プロセスで考える

logger はユニットテストだけでは安心しにくい部品です。文字列整形や JSON 化だけを検証しても、実運用で問題になるのは I/O、並行性、回転、終了時 flush、権限エラーのほうだからです。

そのため、結合テストでは実ファイル、実スレッド、必要なら実プロセスを使って確認するのが重要です。少なくとも「普段は通るが、障害時に信用できない」という状態は避けたいところです。

通しておきたい結合テスト項目

単一書き込みの健全性

  • 1 行が 1 JSON レコードになっているか
  • UTF-8 で再読できるか
  • 必須項目が毎回入っているか
  • 改行の混入で複数行に壊れていないか

同一プロセス内の並行実行

  • 複数スレッドから同時に書いてもレコードが壊れないか
  • レコード数の過不足がないか
  • queue 利用時に順序や欠落の方針が仕様どおりか

flush と終了時の挙動

  • Error / Critical が即時反映されるか
  • 正常終了時に queue 内が空になるか
  • 例外終了に近い経路でも必要な終了ログが残るか

回転と保持

  • 回転条件を満たしたら新しいファイルへ切り替わるか
  • 保持上限を超えた古いファイルが仕様どおりに削除されるか
  • 回転直前・直後でも JSON 行が壊れないか

異常系

  • 保存先ディレクトリが存在しないときの扱い
  • 書き込み権限がないときの扱い
  • ディスクフル相当で失敗したときの通知や戻り値
  • queue あふれ時の動作

複数プロセスの扱い

もし仕様が 1 プロセス 1 ファイル なら、別プロセスが同じファイルへ入ろうとしないこと自体を確認対象にできます。逆に集約プロセス方式なら、そこへの引き渡し失敗も含めて確認が必要です。

v1 で最低限通したい本数

最初に全部やろうとするとテストが重くなりすぎます。v1 で最低限通したいのは、次の 6 本くらいです。

  1. 単一スレッドでの正常書き込み
  2. 複数スレッド同時書き込み
  3. Error / Critical の flush
  4. 回転と保持
  5. 保存先異常時の失敗通知
  6. 正常終了時の drain と最終 flush

この 6 本が通っているだけでも、「文字列は出たが運用で信用できない logger」からはかなり離れられます。

まとめ

自作 logger の最初の目標は、高機能化ではなく「障害時に信じられること」です。そのためには、形式を UTF-8 JSON Lines に固定し、必須項目を絞り、1 プロセス 1 ファイル を基本にし、flush・回転・保持・失敗時挙動を早めに決めておくのが有効です。

そして、その設計が本当に機能するかどうかは、実ファイル・実スレッド・実プロセスを使う結合テストで確認する必要があります。実装を大きくする前に、まずは最小構成と最低限のテストセットを先に固めると、あとから無理なく育てやすくなります。

関連トピック

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

このテーマがつながるサービス

この記事は次のサービスページにつながります。近い入口からご覧ください。

ブログ一覧に戻る