ストーリー
高橋アーキテクトがシナリオを用意してくれた。
演習の概要
ECサイト(マイクロサービス構成)に対して、ログ基盤を設計してください。
システム構成
[Client] → [API Gateway] → [User Service]
→ [Order Service] → [Inventory Service]
→ [Payment Service] → [外部決済API]
→ [Notification Service]
ミッション一覧
| # | ミッション | 難易度 |
|---|---|---|
| 1 | 構造化ログのスキーマ設計 | 基本 |
| 2 | TypeScriptでのロガー実装 | 基本 |
| 3 | ログレベルのガイドライン策定 | 基本 |
| 4 | ログ集約アーキテクチャの設計 | 応用 |
| 5 | 障害調査用クエリの作成 | 応用 |
| 6 | ログベースアラートの設計 | 応用 |
ミッション1: 構造化ログのスキーマ設計
ECサイトの全サービスで共通して使用するログスキーマを設計してください。
要件
- 必須フィールドとオプションフィールドを定義する
- トレーサビリティ用のフィールドを含める
- ビジネスコンテキスト(orderId, userId等)を含める
- 機密情報を含めないルールを定める
解答例(自分で実装してから確認しよう)
// 共通ログスキーマ
interface BaseLogSchema {
// 必須フィールド
timestamp: string; // ISO 8601 (UTC)
level: 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
message: string; // 人間が読めるメッセージ
service: string; // サービス名 (e.g., "order-service")
environment: string; // 環境 (e.g., "production")
version: string; // アプリケーションバージョン
// トレーサビリティ(必須)
traceId: string; // 分散トレースID
spanId: string; // SpanID
requestId: string; // リクエスト固有ID
// インフラ情報
hostname: string; // ホスト名 / Pod名
pid: number; // プロセスID
}
interface ExtendedLogSchema extends BaseLogSchema {
// ビジネスコンテキスト(オプション)
userId?: string; // ユーザーID(メールアドレスではなくIDを使用)
orderId?: string;
paymentId?: string;
productId?: string;
// パフォーマンス情報(オプション)
duration?: number; // 処理時間(ms)
statusCode?: number; // HTTPステータスコード
// エラー情報(オプション)
error?: {
name: string;
message: string;
code?: string;
stack?: string; // 本番ではサンプリング
};
}
// 機密情報の除外ルール
const SENSITIVE_FIELDS = [
'password', 'token', 'secret', 'creditCard',
'email', 'phoneNumber', 'address',
];
ミッション2: TypeScriptでのロガー実装
pinoを使って、共通ロガーモジュールを実装してください。
要件
- 共通フィールドの自動付与(service, hostname, version)
- リクエストごとの子ロガー生成
- 機密情報のマスキング
解答例(自分で実装してから確認しよう)
import pino, { Logger } from 'pino';
import { IncomingMessage } from 'http';
const SENSITIVE_KEYS = ['password', 'token', 'secret', 'creditCard', 'authorization'];
function redactSensitive(obj: Record<string, unknown>): Record<string, unknown> {
const result = { ...obj };
for (const key of Object.keys(result)) {
if (SENSITIVE_KEYS.some((s) => key.toLowerCase().includes(s))) {
result[key] = '[REDACTED]';
}
}
return result;
}
export function createLogger(serviceName: string): Logger {
return pino({
level: process.env.LOG_LEVEL || 'info',
timestamp: pino.stdTimeFunctions.isoTime,
base: {
service: serviceName,
environment: process.env.NODE_ENV || 'development',
version: process.env.APP_VERSION || 'unknown',
hostname: process.env.HOSTNAME || 'localhost',
pid: process.pid,
},
redact: {
paths: ['password', '*.password', 'authorization', '*.token'],
censor: '[REDACTED]',
},
formatters: {
level(label) {
return { level: label.toUpperCase() };
},
},
});
}
export function createRequestLogger(
baseLogger: Logger,
req: IncomingMessage
): Logger {
return baseLogger.child({
traceId: req.headers['x-trace-id'] || generateId(),
spanId: generateId(),
requestId: req.headers['x-request-id'] || generateId(),
method: req.method,
path: req.url,
});
}
function generateId(): string {
return Math.random().toString(36).substring(2, 15);
}
ミッション3: ログレベルのガイドライン策定
チーム内で共有するログレベルのガイドラインを作成してください。
要件
- 各レベルの定義と使用例を記述
- 「こういう場合はこのレベル」の具体例を5つ以上
- アンチパターン(やってはいけないこと)を3つ以上
解答例(自分で実装してから確認しよう)
# ログレベルガイドライン
## レベル定義
| レベル | 定義 | 本番出力 |
|--------|------|---------|
| TRACE | メソッドの入出力、変数の値 | No |
| DEBUG | 内部処理の詳細(キャッシュヒット等) | No(障害時のみ一時的に有効化) |
| INFO | 正常な業務イベントの記録 | Yes |
| WARN | 注意が必要だが処理は継続可能 | Yes |
| ERROR | エラー発生、個別リクエストが失敗 | Yes |
| FATAL | プロセス停止を伴う致命的エラー | Yes |
## 具体例
1. INFO: 注文の作成成功 → `logger.info({ orderId }, 'Order created')`
2. INFO: ユーザーのログイン成功 → `logger.info({ userId }, 'User logged in')`
3. WARN: リトライ発生 → `logger.warn({ orderId, attempt }, 'Payment retry')`
4. WARN: 在庫残り少 → `logger.warn({ productId, stock }, 'Low stock')`
5. ERROR: 決済タイムアウト → `logger.error({ orderId, err }, 'Payment timeout')`
6. ERROR: DB接続失敗(個別クエリ) → `logger.error({ query, err }, 'DB query failed')`
7. FATAL: DB接続プール枯渇 → `logger.fatal({ err }, 'DB pool exhausted')`
## アンチパターン
1. すべてのログをINFOで出力する(レベルの意味がなくなる)
2. ERRORログにスタックトレースを含めない(デバッグが困難になる)
3. ループ内でINFOログを出力する(ログ量が爆発する)
4. 機密情報(パスワード、トークン)をログに含める
5. メッセージだけのログ(コンテキスト情報なし)
ミッション4: ログ集約アーキテクチャの設計
ECサイトのログ集約アーキテクチャを設計してください。
要件
- 5つのマイクロサービスからログを収集
- コスト効率を考慮したストレージ設計
- 保持ポリシーを定義
解答例(自分で実装してから確認しよう)
# ログ集約アーキテクチャ設計書
architecture:
shipper: Fluent Bit # 軽量なログ転送エージェント
storage: Grafana Loki # 低コストなログストレージ
visualization: Grafana # ダッシュボード
flow:
- サービスがstdoutにJSON構造化ログを出力
- Fluent Bit(DaemonSet)がコンテナログを収集
- Fluent Bitがラベル(service, level)を付与してLokiに送信
- GrafanaでLogQLクエリを使って検索・可視化
retention_policy:
- level: ERROR/FATAL
retention: 90日
storage_class: hot
- level: WARN
retention: 30日
storage_class: warm
- level: INFO
retention: 14日
storage_class: warm
- level: DEBUG
retention: 3日
storage_class: cold
cost_optimization:
- 正常系INFOログは10%サンプリング
- ヘルスチェック(/health)のログは除外
- DEBUG/TRACEは本番では原則出力しない
ミッション5: 障害調査用クエリの作成
以下のシナリオで使用するログクエリを作成してください。
シナリオ
「POST /api/orders エンドポイントのエラー率が急増している」
解答例(自分で実装してから確認しよう)
# 1. エラースパイクの確認(5分単位で集計)
{service=~".*"} | json | level="ERROR"
| line_format "{{.service}}" | rate([5m])
# 2. エラーが多いサービスの特定
{service=~".*"} | json | level="ERROR"
| stats count by (service)
# 3. エラーコード別の内訳
{service="order-service"} | json | level="ERROR"
| stats count by (errorCode)
# 4. 特定のエラーの詳細ログ
{service="payment-service"} | json | level="ERROR"
| line_format "{{.timestamp}} {{.message}} orderId={{.orderId}}"
# 5. 特定リクエストの全経路追跡
{service=~".*"} | json | traceId="abc123def456"
| line_format "{{.timestamp}} [{{.service}}] {{.level}} {{.message}}"
# 6. デプロイイベントとの相関
{service="deployment"} | json
| line_format "{{.timestamp}} {{.message}} version={{.version}}"
ミッション6: ログベースアラートの設計
ECサイトに必要なログベースアラートを設計してください。
要件
- 最低5つのアラートルールを定義
- 重要度(critical/warning)を分類
- アラート疲れを避ける工夫を含める
解答例(自分で実装してから確認しよう)
const alertRules = [
{
name: 'Critical: Payment Error Spike',
condition: 'payment-serviceのERRORが1分間に10件以上',
severity: 'critical',
notification: ['PagerDuty', 'Slack #oncall'],
cooldown: '5分', // 5分以内の再通知を抑制
},
{
name: 'Critical: API Gateway 5xx Spike',
condition: 'api-gatewayの5xxレスポンスが5分間で全体の5%以上',
severity: 'critical',
notification: ['PagerDuty', 'Slack #oncall'],
cooldown: '5分',
},
{
name: 'Warning: Order Service High Latency',
condition: 'order-serviceのduration P95が3秒以上が10分間継続',
severity: 'warning',
notification: ['Slack #monitoring'],
cooldown: '30分',
},
{
name: 'Warning: Retry Rate High',
condition: 'WARNログにretryが含まれるログが5分間に50件以上',
severity: 'warning',
notification: ['Slack #monitoring'],
cooldown: '15分',
},
{
name: 'Critical: FATAL Log Detected',
condition: 'FATALレベルのログが1件でも発生',
severity: 'critical',
notification: ['PagerDuty', 'Slack #oncall'],
cooldown: '1分',
},
];
達成度チェック
| ミッション | 内容 | 完了 |
|---|---|---|
| 1 | 構造化ログのスキーマ設計 | [ ] |
| 2 | TypeScriptでのロガー実装 | [ ] |
| 3 | ログレベルのガイドライン策定 | [ ] |
| 4 | ログ集約アーキテクチャの設計 | [ ] |
| 5 | 障害調査用クエリの作成 | [ ] |
| 6 | ログベースアラートの設計 | [ ] |
まとめ
| ポイント | 内容 |
|---|---|
| スキーマ設計 | 必須フィールド + コンテキスト + 機密情報除外 |
| ロガー実装 | pino + 子ロガー + レダクション |
| ガイドライン | チーム共通のルールで一貫性を確保 |
| アーキテクチャ | 収集 → 集約 → 検索 → 可視化のパイプライン |
チェックリスト
- 構造化ログのスキーマを設計できた
- pinoを使ったロガーを実装できた
- ログレベルのガイドラインを策定できた
- ログ集約アーキテクチャを設計できた
- 障害調査用のクエリを作成できた
- ログベースのアラートを設計できた
次のステップへ
演習が完了したら、Step 2 のチェックポイントクイズに挑戦しましょう。
推定所要時間: 90分