構造化ログで「あとから追えない」を防ぐ
ログはあるのに原因が分からない、という状態
バグ調査で一番時間を溶かすのは「再現できない」ことより「ログを見ても分からない」ことです。アクセスログはあるのにどのユーザーのどの操作か特定できない、エラーログはあるのに直前に何が起きていたか追えない。これは調査の技術の問題ではなく、多くの場合、実装時に「何を・どの粒度で・どんな形式で残すか」を決めずにログ出力を後回しにした結果です。
ログは実装が終わった後に足りるものではありません。実装と同時に設計しておく必要があります。
構造化ログの最小フィールドセット
まず、テキストの垂れ流しではなく、キーと値の組み合わせ(構造化ログ)で残すことが前提です。最低限、次の5項目は毎回同じキー名で出力します。
- request_id — リクエスト単位で一意なID。エッジからDBまで同じ値を引き回す
- user_id(または session_id)— 誰の操作かを特定する
- event — 処理段階を表す固定文字列(例:
payment.charge.started) - status — success / failure などの結果
- duration_ms — 処理にかかった時間
Rails であれば、リクエストの入口でミドルウェアが request_id を発行し、Rails.logger.info にハッシュを渡す構成が簡単です。
Rails.logger.info({
event: "payment.charge.started",
request_id: request.request_id,
user_id: current_user&.id,
order_id: order.id,
status: "started"
}.to_json)
# 処理完了時
Rails.logger.info({
event: "payment.charge.completed",
request_id: request.request_id,
order_id: order.id,
status: charge.success? ? "success" : "failure",
duration_ms: (Time.current - started_at) * 1000
}.to_json)フロントエンドでも同様に、API呼び出しの前後で request_id をレスポンスヘッダーから受け取り、コンソールログや後述のエラー収集に含めておくと、サーバー側のログと突き合わせられます。
実装時に決めておく3つのこと
- どのイベントを残すか — 外部API呼び出しの前後、状態遷移(pending→completed 等)、認可判定の分岐点。「境界」を跨ぐ処理は必ずログの対象にする
- 粒度 — INFO は正常系の通過記録、WARN はリトライ可能な異常、ERROR は人が対応すべき異常、と役割を決めて混同しない
- 形式 — キー名の辞書(
event,request_idなど)をチームで統一する。担当者ごとにreqIdとrequest_idが混在すると検索できなくなる
残しすぎ・残さなすぎで起きた失敗
残さなすぎた例
外部決済APIへのリクエストが稀にタイムアウトしていたが、タイムアウト自体をログに出していなかったため、ユーザーからの「決済が固まって進まない」という報告だけでは手がかりがなく、再現条件の絞り込みに数日かかったケースがあります。境界(外部API呼び出し)はログの対象にする、という原則を最初から適用していれば防げた遅れでした。
残しすぎた例
逆に、デバッグのしやすさを優先してリクエストボディをそのままログに出力していたところ、フォームに入力されたメールアドレスや氏名がログ基盤にそのまま蓄積されていた、という例もあります。ログは検索性が高く長期保存されるため、個人情報を含めるとアクセス制御・保持期間の両方でコンプライアンス対応が必要になります。ログに何を出すかは「デバッグに便利か」だけでなく「漏れたら困るか」でも判断する必要があります。
実装チェックリスト
- request_id はエントリポイントで発行し、非同期ジョブやリトライにも引き継いでいるか
- 外部API呼び出し・状態遷移・認可判定の前後にログがあるか
- 個人情報・認証情報(パスワード、トークン、フルのカード番号など)を出力していないか
- ERROR ログには対応すべきアクションが分かる情報(何が・どのIDで・次に何をすべきか)が含まれているか
- ログのキー名がチーム内の他の箇所と揃っているか
これらは実装のレビュー観点にそのまま追加できます。機能のレビューと同時に「このログで3ヶ月後の自分が原因を追えるか」を確認する習慣が、調査時間を大きく左右します。
Bugoon での実践
Bugoon はブラウザ上でのバグ報告時に、スクリーンショットとアノテーションに加えて、報告直前の操作ステップをそのまま記録します。UIの不具合として報告された内容が、実は直前に発生したAPIエラーが原因だった、というケースでも、操作履歴とエラー情報を1つの報告としてまとめて開発者に渡せるため、ログを別途探しに行く手間が減ります。
今回のテーマで触れた request_id のような識別子をバグレポート送信時に一緒に受け渡せると、サーバー側の構造化ログとバグレポートを同じ調査の中で突き合わせやすくなるかもしれません。現時点でそうした仕組みが用意されているわけではありませんが、構造化ログを実装に組み込んでおくこと自体は、報告経路がどうであれ調査を速くする土台になります。