Blogへ戻る

Language: 日本語

「ログを 1 箇所に垂れ流していませんか?」 本番ログの lane 分割と CI ラチェットによる衛生管理

Cloud Logging や Datadog などのログ基盤に、**「監査ログも、デバッグ用のアプリケーションログも、アクセスログも、すべて同じ場所に垂れ流している……」** という状態になっていませんか?

Cloud Logging や Datadog などのログ基盤に、「監査ログも、デバッグ用のアプリケーションログも、アクセスログも、すべて同じ場所に垂れ流している……」 という状態になっていませんか?

すべてのログを 1 つのストリーム(stdout 等)に集約してしまうと、運用上非常にやっかいな問題が発生します。

  • アクセス権のジレンマ: 厳格なアクセス制御が必要な「監査ログ」に引きずられ、一般の開発者がトラブル調査用のアプリケーションログを閲覧できなくなる
  • 保持コストの爆発: 監査ログに合わせて保持期間を数年間に設定すると、膨大な量のデバッグログまで長期保存されてクラウド費用が跳ね上がる
  • 機密情報の漏洩: リクエストボディや SQL クエリに含まれる PII(個人情報)や認証トークンが生ログとして出力され、外部分析基盤や AI に流出するリスク

監査ログの配送保証(『「ログが出ないのに送信完了扱い?」 監査ログの silent 破棄と OTel の沈黙を暴いた観測設計』)に続き、本番ログの置き場所と出力内容の統制を根本から見直しました。

この記事では、ログを用途・保持期間・IAM 権限ごとに切り分ける lane 分割のアーキテクチャ と、共通層での機密情報マスキング、そしてレガシーログを増やさない CI ラチェット(Ratchet)の仕組み を解説します。

得られること

  • ログを用途ごとに切り分け、保持期間とアクセス権(IAM)を最適化する lane 分割の設計がわかる
  • 個別実装に依存せず、ロガーの共通ポート層で PII やクレデンシャルを一括マスキングする出力封じ込めの方法が学べる
  • 「現状を維持しつつ、これ以上レガシーコードを増やさない」CI ラチェットによる現実的な移行手法が身につく!

1. Telemetry の lane 分割: 保持期間と IAM の分離

ログを単一の場所に集めると、「最も要件が厳しいログ」に全体の保持期間や権限が引っ張られてしまいます。

そこで、ログの特性に応じて 2 つの独立した lane に分離しました。

【 ログの lane 分割アーキテクチャ 】
[ アプリケーション ]
   ├─ (監査ログ lane) ─────→ Log Sink ──→ BigQuery / Cold Storage(数年間保管・厳格な IAM 制限 🔒)
   └─ (アプリログ lane) ───→ Cloud Logging(30 日間保管・一般開発者が閲覧可能 ⚡️)
lane保持期間アクセス権限(IAM)主な用途
監査ログ lane長期保管(数年〜永久)最小権限(セキュリティ・監査担当のみ)不正アクセス調査、法的要件、コンプライアンス
アプリケーションログ lane短期保管(30 日等)開発者全体に公開日常のデバッグ、エラー調査、パフォーマンス分析

lane を物理的に分けることで、「監査ログは安全に長期保管しつつ、アプリケーションログは安価に短期でローテーションさせ、開発者が日常のトラブルシューティングで自由に閲覧できる」 という理想的なバランスを実現しました。

これに伴い、Terraform の観測性モジュールも audit_store(監査ログ保存)、slack_relay(通知)、alerting(アラート)の 3 つに分割し、インフラ構成の責務も明確に分離しました(詳細は 『「terraform state mv」を手動で叩いていませんか? 745 件のリソースを安全に移動させた moved ブロック設計』 を参照)。

2. stdout への出力封じ込めと一元 Redaction

次に着手したのは、アプリケーションの stdout に垂れ流されていた機密情報の封じ込めです。

以前のコードベースでは、エラー時やデバッグ時にリクエストボディ、SQL クエリ、トークン、PII(メールアドレスや氏名)が生のままログに出力されていました。 さらに、本番の raw log を外部の AI 分析ツールへ渡す経路が存在していたため、情報漏洩のリスクがありました。

共通ポートでの一括 Redaction

各機能の実装者が手動でマスキングを書く運用にすると、「書き忘れた 1 箇所」から必ず情報が漏洩 します。

そこで、ロガーの抽象(Logger Port)の直前にある 単一の共通トランスポート層 に Redaction(秘匿化)処理を配置しました。

// ロガー共通層での自動マスキング処理
export function sanitizeLogPayload(payload: Record<string, unknown>): Record<string, unknown> {
  const SENSITIVE_KEYS = ['password', 'token', 'authorization', 'secret', 'creditCard'];
  
  return deepTransform(payload, (key, value) => {
    if (SENSITIVE_KEYS.includes(key.toLowerCase())) {
      return '[REDACTED]';
    }
    if (typeof value === 'string' && isEmail(value)) {
      return maskEmail(value); // [email protected] -> u***@example.com
    }
    return value;
  });
}

すべてのログ出力がこの層を必ず通過するため、開発者が意識せずとも機密情報が確実にマスキングされた状態で出力されます。また、外部 AI 分析への生ログ送信経路は全環境で完全に遮断しました。

3. 負債を増やさない「CI ラチェット(Ratchet)」

新しい構造化ロガーを導入しても、既存のコードベースには何千箇所もの古い console.log やレガシーロガーの呼び出しが残っています。

これらを「1 つの PR で一括置換する」のは、マージコンフリクトやデグレのリスクが高すぎて現実的ではありません。かといって放置すれば、新規開発の PR で古いロガーが使われ続け、負債が永遠に減りません。

そこで、merge-base との差分を比較して「レガシーロガーの呼び出し件数が 1 件でも増えていたら CI を落とす」ラチェット機構 を導入しました。

# CI ワークフローでのレガシーロガー・ラチェット検査
BASE_COUNT=$(git grep "legacyLogger" $(git merge-base origin/main HEAD) | wc -l)
CURRENT_COUNT=$(git grep "legacyLogger" HEAD | wc -l)

if [ "$CURRENT_COUNT" -gt "$BASE_COUNT" ]; then
  echo "❌ エラー: レガシーロガーの呼び出しが増えています(Base: $BASE_COUNT -> Current: $CURRENT_COUNT)"
  echo "👉 新しいコードでは新しい構造化ロガーを使用してください。"
  exit 1
fi

echo "✅ ラチェット通過: レガシーロガー呼び出しは増加していません。"

ラチェット運用の効果

  • 現状の負債は許容する: 既存の古いコードがあっても PR は Green になるため、開発速度を一切止めない。
  • 悪化だけを 100% 阻止する: 新規追加や修正箇所で古いロガーを使うと CI で即座に弾かれる。
  • 自然減を促す: 各開発者が自分の担当ファイルを触るついでに新しいロガーへ置き換えていくことで、呼び出し総数が着実に右肩下がりで減っていく。

まとめ: ログの衛生管理が Observability の土台

可観測性(Observability)を高めようとすると、メトリクスやダッシュボードの構築ばかりに目が行きがちですが、最も基本的かつ重要なのは「ログの衛生管理」 です。

  1. lane を分ける: 監査ログとアプリログを分離し、保管期間と IAM を最適化する
  2. 共通層で塞ぐ: 個別実装に頼らず、ロガーの共通経路で PII や機密情報を一括マスクする
  3. ラチェットで止める: CI で負債の増加を食い止め、漸進的に移行を進める

ノイズや機密情報が取り除かれ、適切に分類された構造化ログ基盤があるからこそ、人間も AI エージェントも迷わず迅速に本番の障害調査を進めることができます。

参考リンク