LESSON 30分

ストーリー

あなた
ログが多すぎて、肝心なエラーログが埋もれてしまうんです…

高橋アーキテクトはうなずいた。

高橋アーキテクト
ログレベルの使い分けが曖昧なプロジェクトでよくある問題だ。“何をどのレベルで出すか”のルールをチーム内で統一すること。そしてフィルタリングの仕組みを作ることが大切だ

ログレベルの定義

// ログレベルの優先度(低い → 高い)
enum LogLevel {
  TRACE = 10,  // 最も詳細なデバッグ情報
  DEBUG = 20,  // デバッグ用の情報
  INFO  = 30,  // 通常の動作記録
  WARN  = 40,  // 注意が必要な状態
  ERROR = 50,  // エラー発生(処理は継続可能)
  FATAL = 60,  // 致命的エラー(プロセス停止)
}

各レベルの使い分けガイドライン

レベル用途環境
TRACE変数の値、ループの各ステップ"Entering function X with params..."開発のみ
DEBUGデバッグに役立つ内部状態"Cache hit for key: user_42"開発/ステージング
INFO正常な業務イベント"Order ORD-001 created successfully"全環境
WARN異常だが処理は継続可能"Retry attempt 2/3 for payment"全環境
ERRORエラー発生、個別処理が失敗"Payment failed: timeout"全環境
FATAL致命的エラー、プロセス停止"Database connection lost, shutting down"全環境

よくある間違いと正しい使い方

// 間違い: すべてをINFOで出す
logger.info('Starting to process order');        // OK
logger.info('Order validation passed');          // DEBUG が適切
logger.info('SQL query: SELECT * FROM ...');     // TRACE が適切
logger.info('Payment failed: timeout');          // ERROR が適切

// 正しい使い分け
logger.debug('Order validation passed', { orderId });
logger.trace({ query: sql, params }, 'Executing SQL query');
logger.info({ orderId, amount }, 'Order created successfully');
logger.warn({ orderId, attempt: 2 }, 'Payment retry');
logger.error({ orderId, error: err.message }, 'Payment failed');

環境ごとのログレベル設定

// 環境変数でログレベルを制御
const logConfig: Record<string, string> = {
  development: 'debug',
  staging: 'debug',
  production: 'info',
};

const logger = pino({
  level: process.env.LOG_LEVEL || logConfig[process.env.NODE_ENV || 'development'],
});

// 本番環境で一時的にDEBUGレベルに変更する仕組み
// 環境変数の動的変更 or 設定ファイルのホットリロード
interface DynamicLogConfig {
  defaultLevel: string;
  overrides: {
    service?: string;
    module?: string;
    level: string;
    expiresAt: string;  // 自動で元に戻る
  }[];
}
環境推奨レベル理由
開発DEBUG/TRACEデバッグ情報が必要
ステージングDEBUG本番に近い環境でのデバッグ
本番INFOノイズを減らし、コストを抑える
障害調査時DEBUG(一時的)詳細情報が必要な場合のみ

フィルタリングとサンプリング

大量のログを効率的に管理するための戦略です。

// サンプリング: 正常系ログは一部だけ記録
import pino from 'pino';

const logger = pino({
  level: 'info',
  hooks: {
    logMethod(inputArgs, method, level) {
      // ERRORとWARNは100%記録、INFOは10%サンプリング
      if (level >= 40) {
        method.apply(this, inputArgs);
      } else if (Math.random() < 0.1) {
        method.apply(this, inputArgs);
      }
    },
  },
});

// フィルタリング: 特定の条件でログを除外
function shouldLog(logEntry: LogContext): boolean {
  // ヘルスチェックのログは除外
  if (logEntry.path === '/health') return false;

  // 静的ファイルのアクセスログは除外
  if (logEntry.path?.startsWith('/static/')) return false;

  // Kubernetes のプローブは除外
  if (logEntry.userAgent?.includes('kube-probe')) return false;

  return true;
}

ログローテーションと保持ポリシー

// ログの保持期間設計
interface LogRetentionPolicy {
  level: string;
  retentionDays: number;
  storageClass: string;
}

const retentionPolicies: LogRetentionPolicy[] = [
  { level: 'ERROR', retentionDays: 90,  storageClass: 'hot' },
  { level: 'WARN',  retentionDays: 30,  storageClass: 'warm' },
  { level: 'INFO',  retentionDays: 14,  storageClass: 'warm' },
  { level: 'DEBUG', retentionDays: 3,   storageClass: 'cold' },
];
レベル保持期間ストレージコスト
ERROR/FATAL90日ホット(即時検索可能)
WARN30日ウォーム(数秒で検索可能)
INFO14日ウォーム
DEBUG3日コールド(復元に時間がかかる)

まとめ

ポイント内容
ログレベルTRACE〜FATALの6段階を正しく使い分ける
環境別設定本番はINFO以上、開発はDEBUG以上
フィルタリングヘルスチェック等のノイズを除外
保持ポリシーレベルに応じた保持期間とストレージ階層

チェックリスト

  • 6つのログレベルの使い分けを説明できる
  • 環境ごとのログレベル設定の考え方を理解した
  • フィルタリングとサンプリングの戦略を把握した
  • ログローテーションと保持ポリシーを設計できる

次のステップへ

次は「集約と検索」を学びます。ELK StackやCloudWatch Logsを使ったログの集中管理方法を見ていきましょう。


推定読了時間: 30分