障害対応でログを開いたとき、「必要な情報が出ていない」「grepで拾えない形式」「逆にノイズが多すぎて追えない」——ログの問題は、いちばん困る瞬間にしか発覚しません。そして障害の真っ最中にログ設計を直すことはできないので、平時に仕込んでおくしかありません。
この記事では、運用の現場で機能するログにするための実務ポイントを、構造化ログ・ログレベル・リクエストID・出力禁止情報の4つの観点で整理します。
ログはJSONの構造化ログで出す
ログをCloudWatch LogsやElasticsearchなどの基盤で集約・検索する前提に立つと、フリーテキストのログは検索性が著しく落ちます。1行1JSONの構造化ログにしておけば、フィールド単位での絞り込みや集計がそのまま可能になります。
{
"timestamp": "2026-07-02T10:15:30.123+09:00",
"level": "ERROR",
"logger": "OrderService",
"message": "在庫引当に失敗",
"request_id": "8f3a-42d1",
"user_id": "u_1024",
"order_id": "o_5501",
"error_code": "STOCK_SHORTAGE"
}
ポイントは、メッセージ文字列に変数を埋め込まず(「注文o_5501の在庫引当に失敗」ではなく)、検索キーになる値は独立フィールドにすることです。JavaならLogbackのlogstash-encoder、Pythonならstructlogなど、主要言語には定番のライブラリがあります。
ログレベルは「誰が何をするか」で決める
ログレベルの基準が曖昧だと、ERRORがアラート疲れを起こして誰も見なくなります。「そのログが出たとき、誰が・何をするのか」で線を引くのが実務的です。
- ERROR: 即時対応が必要。アラート通知の対象。放置するとユーザー影響が続くもの
- WARN: すぐの対応は不要だが、頻発したら調査するもの(リトライで回復した失敗など)
- INFO: 業務イベントの記録(注文受付、バッチ開始・終了)。障害調査の道しるべ
- DEBUG: 開発時の詳細。本番では原則出さない
ありがちなのが「業務上起こり得る失敗(残高不足など)をERRORで出す」パターンで、これがアラートを鳴らし続けるとERRORの信頼性が失われます。ユーザー起因の想定内エラーはINFOかWARNに落とすのが原則です。
リクエストIDで処理を追跡可能にする
「このユーザーのこの操作で何が起きたか」を追うには、1リクエストに一意なIDを振り、そのリクエスト中のすべてのログに同じIDを出す仕組みが必要です。
- リクエスト受信時にIDを採番(ロードバランサ付与の
X-Request-IDがあれば引き継ぐ) - JavaはMDC、Pythonはcontextvarsに格納し、ログ出力時に自動で付与する
- 外部APIや非同期ジョブの呼び出し時にもIDを伝搬させる
- エラーレスポンスにIDを含めておくと、問い合わせから該当ログへ一発で到達できる
マイクロサービス構成であればOpenTelemetryのトレースIDに統一する選択肢もありますが、モノリスでもリクエストIDだけは最低限入れておく価値があります。
出してはいけない情報を仕組みで防ぐ
ログは想定より広い範囲の人が見るもので、外部の分析基盤に転送されることもあります。次の情報はログに出してはいけません。
- パスワード、APIキー、アクセストークン、セッションID
- クレジットカード番号などの決済情報
- 氏名・住所・電話番号・メールアドレスなどの個人情報(必要ならユーザーIDで代替)
- リクエストボディの無差別ダンプ(上記が混入する典型経路)
「気をつける」だけでは防げないので、ログライブラリのマスキング機能で特定フィールド名(password、token など)を自動マスクする、リクエストボディはホワイトリストしたフィールドだけ出す、といった仕組み側の対策を入れておきます。
まとめ
ログ設計の良し悪しは、障害対応のスピードに直結します。1行1JSONの構造化ログ、対応アクションと紐づいたログレベル、リクエストIDによる横串の追跡、そして機密情報の自動マスク。この4点をアプリケーションの共通基盤として整備しておけば、「ログはあるのに何も分からない」状況をなくせます。
