目次
- まず「何のためのログか」を決める
- ログレベルを正しく使う
- 構造化ログ(JSON)を標準にする
- 必ず含めるべき共通フィールド
- 相関 ID とトレーシング
- 機密情報・個人情報を出さない
- 例外・エラーのログ
- 量とコストの管理
- パフォーマンスへの配慮
- 出力先と収集
- 監査ログの追加要件
- 運用・アラートとの連携
- チームでのルール化
- チェックリスト
1. まず「何のためのログか」を決める
ログは目的によって設計が変わる。最初に用途を分けて考える。
| 種類 | 目的 | 主な読み手 | 保持期間の目安 |
|---|---|---|---|
| アプリケーションログ | 障害調査・デバッグ | 開発者・SRE | 数日〜数週間 |
| アクセスログ | リクエストの記録・傾向分析 | SRE・分析担当 | 数週間〜数か月 |
| 監査ログ | 「誰が・いつ・何をしたか」の証跡 | セキュリティ・監査・法務 | 規制に従う(年単位のことも) |
| セキュリティログ | 不正アクセスの検知 | セキュリティチーム | 数か月〜年単位 |
| ビジネスイベントログ | KPI 集計・行動分析 | PM・データ分析 | 用途次第 |
ポイント
- 監査ログとデバッグログを同じストリームに混ぜない(保持期間・アクセス権・改ざん耐性の要件がまったく違う)。
- 「何か起きたときに、このログで何を答えたいか」を先に書き出してから出力項目を決める。
2. ログレベルを正しく使う
| レベル | 使いどころ | 例 |
|---|---|---|
| FATAL / CRITICAL | プロセスが継続不能 | 起動時に必須設定が読めない |
| ERROR | 処理が失敗し、人の対応が必要になりうる | 外部 API がリトライ後も失敗、DB 書き込み失敗 |
| WARN | 異常だが処理は継続できた/将来問題になりうる | リトライで成功、非推奨 API の利用、閾値接近 |
| INFO | 正常系の重要な節目 | 起動・停止、ジョブ開始・完了、重要な状態遷移 |
| DEBUG | 開発・調査時の詳細 | 分岐の判定値、中間データ |
| TRACE | さらに細かい追跡 | 関数の入出力すべて |
ルール
- ERROR は「誰かが見るべきもの」に限定する。 ユーザーの入力ミス(バリデーションエラー、404)は通常 ERROR ではなく INFO か WARN。ERROR が多すぎるとアラートが形骸化する。
- 本番の既定レベルは INFO。DEBUG は再デプロイなしで動的に切り替えられるようにしておくと調査が楽になる。
- 同じ例外を複数レイヤーで重複して ERROR 出力しない(「ログして再スロー」のアンチパターン)。捕捉して処理する場所で一度だけ記録する。
3. 構造化ログ(JSON)を標準にする
人間向けの自由文ではなく、機械が解析できる形式で出す。
悪い例
2026-11-02 10:15:32 ERROR Failed to process order 12345 for user 678 after 3 retries
良い例
{
"timestamp": "2026-11-02T01:15:32.481Z",
"level": "ERROR",
"service": "order-service",
"env": "production",
"version": "1.42.0",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"event": "order.process.failed",
"message": "Failed to process order",
"order_id": "12345",
"user_id": "678",
"retry_count": 3,
"error": {
"type": "PaymentGatewayTimeout",
"message": "upstream timed out after 5000ms",
"stack": "..."
},
"duration_ms": 15230
}ポイント
- メッセージは固定文字列にし、可変値はフィールドに分ける。これで「同じ種類のエラーが何件か」を集計できる。
eventのような機械可読なイベント名(ドメイン.対象.動作形式など)を持たせると検索・集計・アラート設定が楽になる。- フィールド名は組織全体で統一する(
user_id/userId/uidの混在を避ける)。OpenTelemetry のセマンティック規約や Elastic Common Schema (ECS) など既存の規約に乗るのが手っ取り早い。 - 型を揃える(あるログでは
order_idが数値、別のログでは文字列、だとインデックスで衝突する)。
4. 必ず含めるべき共通フィールド
| フィールド | 理由 |
|---|---|
timestamp | UTC、ISO 8601、ミリ秒以上の精度。タイムゾーン混在は調査の大敵 |
level | フィルタ・アラートの基本 |
service / app | どのサービスのログか |
env | production / staging などの区別 |
version / commit | どのデプロイで起きたか(リリース起因の切り分けに必須) |
host / pod / instance_id | 特定ノード固有の問題の切り分け |
trace_id / span_id | 分散トレースとの紐付け |
request_id | 1 リクエストのログをまとめて追う |
event / message | 何が起きたか |
状況に応じて追加するもの:user_id(仮名化済み)、tenant_id、duration_ms、status_code、http.method、http.route、エラー詳細など。
5. 相関 ID とトレーシング
マイクロサービスでは 1 つのリクエストが複数サービスをまたぐ。ID がないとログを繋げられない。
- 入口(API ゲートウェイ/ロードバランサ)で
trace_id(またはrequest_id)を発行する。 - サービス間呼び出しでは HTTP ヘッダ(W3C Trace Context の
traceparentなど)やメッセージ属性で必ず伝搬する。 - 非同期処理(キュー、バッチ、イベント)にも ID を引き継ぐ。ここが抜けがち。
- ロガーのコンテキスト(MDC、contextvars、AsyncLocalStorage など)に ID を入れておき、各ログ行に自動付与させる。手で毎回書かない。
- 可能なら OpenTelemetry でログ・メトリクス・トレースを同じ ID で結合する。
6. 機密情報・個人情報を出さない
ログは多くの人・システムが閲覧でき、長期間残る。漏洩経路になりやすい。
絶対に出力しないもの
- パスワード、API キー、アクセストークン、セッション ID、秘密鍵
- クレジットカード番号、銀行口座番号
- マイナンバーなどの公的識別番号
慎重に扱うもの(マスキング・ハッシュ化・仮名化)
- メールアドレス、電話番号、氏名、住所、IP アドレス(法域によっては個人情報)
- 健康情報などの要配慮情報
実装上の対策
- リクエスト/レスポンスのボディや HTTP ヘッダを丸ごとログに出さない(
Authorizationヘッダや Cookie が混入する典型)。 - オブジェクトを丸ごとシリアライズしない。出すフィールドを許可リスト方式で明示する。
- ロガー側にマスキングフィルタを入れて、既知のキー(
password、token、secretなど)やパターン(カード番号など)を自動で伏せる。 - 定期的に本番ログをスキャンして機密情報の混入を検知する。
- GDPR・個人情報保護法などの削除要求に対応できるよう、個人を直接特定する値はログに入れない設計が望ましい。
7. 例外・エラーのログ
- スタックトレースは 1 つのログエントリ内に収める(複数行に分かれると集約基盤で分断される)。構造化ログならフィールドに入れれば済む。
- 原因例外(cause / inner exception)も含める。
- エラーには「何をしようとして」「何が入力で」「何が起きたか」を残す。
Error occurredだけのログは役に立たない。 - エラー種別をフィールド(
error.type)として持ち、集計できるようにする。 - 外部呼び出しの失敗では、宛先・ステータスコード・所要時間・リトライ回数を記録する。
8. 量とコストの管理
ログは放っておくと増え続け、ストレージ・転送・検索コストが膨らむ。
- ループ内でログを出さない(特に INFO 以上)。集計してから 1 行にする。
- 高頻度の正常系ログはサンプリングする(例:成功リクエストは 1%、エラーは 100%)。
- 同一エラーの大量発生にはレート制限・重複抑制を入れる。
- ヘルスチェックのアクセスログは除外するか別扱いにする。
- 保持期間をログ種別ごとに設定し、ホット(検索可能)→ コールド(アーカイブ)→ 削除のライフサイクルを決める。
- 数値で追いたいもの(リクエスト数、レイテンシ分布など)はログから毎回集計せずメトリクスとして出す。ログ・メトリクス・トレースを使い分ける。
9. パフォーマンスへの配慮
- ログ出力は非同期にする(バッファリングして別スレッドで書き出す)。ただしクラッシュ時の取りこぼしとのトレードオフを理解しておく。
- レベルが無効なときに重い文字列組み立てが走らないようにする(遅延評価、プレースホルダ形式、
isDebugEnabled()ガードなど)。 - ログ基盤の障害でアプリが止まらない設計にする(バッファ溢れ時は破棄するなど、方針を決めておく)。
10. 出力先と収集
- コンテナ環境では標準出力(stdout/stderr)に出すのが基本(The Twelve-Factor App の考え方)。ファイル管理やローテーションはアプリの責務にしない。
- 収集エージェント(Fluent Bit、Vector、OpenTelemetry Collector など)で集約基盤へ送る。
- 集約先の例:Elasticsearch / OpenSearch、Grafana Loki、Datadog、Cloud Logging、CloudWatch Logs など。
- ファイル出力する場合はローテーション(サイズ・日付)と圧縮、ディスク溢れ対策を必ず設定する。
- 時刻同期(NTP)を全ホストで確実にする。ずれていると複数サービスのログを時系列で並べられない。
11. 監査ログの追加要件
監査ログは通常のアプリログより要件が厳しい。
- 誰が(ユーザー ID・ロール・認証方式)、いつ、どこから(IP・クライアント)、何に対して(リソース ID)、何をしたか(操作)、結果(成功/失敗)を必ず記録する。
- 変更操作では変更前後の値(または差分)を残す(ただし機密値は除く)。
- 改ざん防止:追記専用(WORM)ストレージ、ハッシュチェーン、署名などを使う。
- アクセス権を限定し、監査ログ自体の閲覧も記録する。
- 失敗した認証・権限エラーも必ず記録する(攻撃検知に必要)。
12. 運用・アラートとの連携
- アラートは「ERROR が 1 件出たら通知」ではなく、率や継続時間(エラー率が 5 分間 X% 超など)で設計する。
- ダッシュボードとよく使う検索クエリを用意しておき、障害時にすぐ使える状態にする。
- ログから runbook(対応手順)へたどれるよう、エラー種別ごとに対応方法を紐付けておくとよい。
- 障害の振り返りで「このログがあれば早く原因がわかった」という知見を、ログ設計にフィードバックする。
13. チームでのルール化
- ログ規約(レベルの基準、必須フィールド、命名規則、禁止事項)を文書化する。
- 共通ロガーライブラリ/ラッパーを用意し、必須フィールドの付与やマスキングを自動化する。各チームが個別実装しない。
- コードレビューでログの妥当性(レベル、機密情報、情報量)もチェック項目に入れる。
- ログスキーマの変更は後方互換を意識する(フィールド名変更でダッシュボードやアラートが壊れる)。
チェックリスト
- ログの用途(デバッグ/アクセス/監査/セキュリティ)ごとに出力先と保持期間を分けている
- 構造化ログ(JSON 等)で出力している
- タイムスタンプは UTC・ISO 8601・ミリ秒以上
- service / env / version / host などの共通フィールドが自動付与される
- trace_id / request_id がサービス間・非同期処理でも伝搬している
- ログレベルの基準がチームで合意されている(ERROR は要対応のみ)
- パスワード・トークン・個人情報がマスキングされている
- メッセージは固定文字列、可変値はフィールドに分離している
- 例外はスタックトレースと原因を含め、1 エントリにまとまっている
- ループ内大量出力がなく、サンプリング・レート制限を検討済み
- ログ出力が非同期で、ログ基盤障害でアプリが止まらない
- 保持期間とライフサイクル(ホット/コールド/削除)が決まっている
- アラートはエラー率・継続時間ベースで設計されている
- 監査ログは改ざん防止とアクセス制御がされている