ログ設計とは - アプリ開発で最初に決めるログレベルと出力項目

ログ設計とは、何をどの形式 / どのレベルで記録するかを実装前に決める設計活動。ログレベルの使い分け、含めるべき項目と禁止項目、保持期間とコストの目安を解説

オブザーバビリティ運用

ログ設計とは

ログ設計は、アプリケーションのログを構造化・分類し、効果的なデバッグと監視を実現する設計手法である。「何をログに出すか」「どの形式で出すか」「どの重要度で出すか」を実装前に決めておく活動で、障害が起きてから後悔する典型部位でもある。実行時の記録行為そのものはロギング、出力形式の話は構造化ログとして個別に解説しており、本項はその設計判断の全体を扱う。

ログレベルの使い分け

最初に決めるのはログレベルの規約だ。チームで基準を揃えないと、同じ事象が人によって ERROR にも INFO にもなり、アラートの信頼性が壊れる。

レベル使いどころ
ERROR処理が失敗し、対応が必要決済 API の呼び出し失敗
WARN処理は継続できたが正常でないリトライで回復した一時エラー
INFO正常系の重要イベント注文の受付完了、バッチの開始・終了
DEBUG開発時の詳細な変数・分岐情報リクエストペイロードの中身

「ERROR は人が動く必要があるものだけ」と決めるのが要点で、放置してよい ERROR を許すと、監視は狼少年化して本物の障害を見逃す。

もう一つ決めておくのがレベル名の表記である。上の表は ERROR / WARN / INFO と大文字で示しているが、後述の構造化ログの例では level: 'error' と小文字で出している。CloudWatch Logs Insights の filter は文字列比較で大文字と小文字を区別するため、出力側で ERRORerror が混在すると filter level = 'error' は片方を取りこぼす。どちらに寄せるかを規約に書き、出力する直前で小文字化するなど機械的に揃えておく。

構造化ログ vs 非構造化ログ

非構造化ログは人間が読みやすいが、ツールでの検索・集計が困難である。構造化ログ (JSON) はフィールドごとにクエリできるため、CloudWatch Logs Insights で orderIderror を条件にした検索が即座に行える。

// ❌ 非構造化: 検索・分析が困難
console.log(`[ERROR] Order 123 failed: timeout after 5000ms`);

// ✅ 構造化 (JSON): CloudWatch Logs Insights で検索可能
console.log(JSON.stringify({
  level: 'error',
  message: 'Order processing failed',
  orderId: '123',
  error: 'timeout',
  duration: 5000,
  requestId: context.awsRequestId,
}));

ログに含めるべき情報

ログには、タイムスタンプ (いつ発生したか)、ログレベル (重要度の判断)、リクエスト ID (リクエストの追跡)、ユーザー ID (誰の操作か、PII に注意)、処理時間 (パフォーマンスの分析)、エラー情報 (スタックトレース、エラーコード) を含める。

ログに含めてはいけない情報

パスワード (セキュリティリスク)、アクセストークン (漏洩すると不正アクセス)、クレジットカード番号 (PCI DSS 違反)、個人情報 (GDPR / 個人情報保護法) はログに出力してはならない。

表で DEBUG の例に挙げたリクエストペイロードも、丸ごと出せばこの禁止項目を巻き込む。DEBUG なら許されるわけではなく、開発環境のログにも同じ基準を当て、ペイロードを出すならフィールドを選ぶかマスクしてから出す。

CloudWatch Logs Insights

CloudWatch Logs Insights の例を示す。

-- エラーの集計
filter level = 'error'
| stats count() as errorCount by error
| sort errorCount desc

-- 遅いリクエストの特定
filter duration > 3000
| fields @timestamp, orderId, duration
| sort duration desc

相関 ID (Correlation ID)

相関 ID (Correlation ID) のコード例を示す。

// API GatewayLambda A → Lambda B の呼び出しチェーンを追跡
// ヘッダー名は小文字化してから引く (下の注記を参照)
const headers = Object.fromEntries(
  Object.entries(event.headers ?? {}).map(([k, v]) => [k.toLowerCase(), v]),
);
const correlationId = headers['x-correlation-id'] ?? crypto.randomUUID();

console.log(JSON.stringify({
  correlationId,
  message: 'Processing order',
}));

// 下流の Lambda にも correlationId を渡す
await invokeDownstream({ ...payload, correlationId });

ヘッダー名を決め打ちで引く実装は、X-Correlation-Id のように大文字混じりで送られた瞬間に undefined になり、下流で新しい ID が振られて追跡が切れる。例外は出ないため、障害調査でログを繋ごうとした時に初めて気付く。API Gateway の HTTP API (ペイロード形式 2.0) はヘッダー名を小文字化して Lambda へ渡すが、この正規化に頼らず受け取り側で小文字化してから引くほうが安全である。

ログの保持とコスト

dev 環境は 7 日 (コスト削減)、prod 環境は 90 日 (障害調査に十分)、監査ログは 1 年以上 (コンプライアンス) を目安に保持期間を設定する。

ログのアンチパターン

全てを DEBUG で出力するとコスト増とノイズの原因になる。エラーを握りつぶすと障害の検知が遅れる。非構造化ログは検索・分析が困難になる。機密情報の出力はセキュリティリスクを生む。

理論と実装の両面から学ぶなら関連書籍が参考になる。

この記事は役に立ちましたか?

関連用語

関連する記事