← ブログ一覧

ログ設計とオブザーバビリティ入門:実務実装ガイド

障害調査を効率化するログ設計の実践手法を解説。構造化ログ・ログレベル・分散トレーシング・メトリクス収集まで、受託開発で即使える実務ガイド。

#DevOps#インフラ#技術解説#アーキテクチャ
ログ設計とオブザーバビリティ入門:実務実装ガイド

ログ設計とオブザーバビリティ入門:実務実装ガイド

「本番で障害が起きたけど、ログを見ても原因が分からない」「どのログを見ればいいか分からず、調査に時間がかかる」──こうした課題は、ログ設計とオブザーバビリティの不足から生じます。

本記事では、障害調査を効率化するログ設計の実践手法を、構造化ログ・ログレベル・分散トレーシング・メトリクス収集まで網羅して解説します。受託開発・自社開発の現場で即使える実務ガイドです。


1. ログ設計の基本方針

1.1 ログの目的を明確にする

ログは以下の目的で記録します:

| 目的 | 具体例 | 推奨ログレベル | |------|--------|----------------| | 障害調査 | エラースタックトレース、リクエスト ID | ERROR, WARN | | パフォーマンス分析 | クエリ実行時間、API レスポンスタイム | INFO | | セキュリティ監査 | ログイン試行、権限変更 | INFO, WARN | | ビジネス分析 | ユーザー行動、コンバージョン | INFO | | デバッグ | 変数の値、処理フロー | DEBUG |

1.2 構造化ログを採用する

非構造化ログ(避けるべき):

console.log('User login: user_id=123, status=success, ip=192.168.1.1');

構造化ログ(推奨):

import { logger } from './logger';

logger.info('User login', {
  userId: 123,
  status: 'success',
  ip: '192.168.1.1',
  timestamp: new Date().toISOString(),
});

構造化ログのメリット:

  • 検索性: JSON フィールドで絞り込み可能
  • 集計: ログ集約ツールで統計分析が容易
  • パース不要: 文字列解析が不要

1.3 ログレベルの使い分け

| レベル | 用途 | 本番出力 | 例 | |--------|------|----------|----| | ERROR | 即対応が必要な異常 | ✓ | DB 接続失敗、API エラー | | WARN | 異常だが処理続行可能 | ✓ | リトライ成功、非推奨 API 使用 | | INFO | 重要なイベント | ✓ | ユーザーログイン、決済完了 | | DEBUG | 詳細な動作情報 | △ | 関数の入出力、条件分岐 | | TRACE | 最も詳細なデバッグ情報 | × | ループ内の変数値 |

実装例(Node.js + Winston):

import winston from 'winston';

export const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});

2. リクエストトレーシングの実装

2.1 リクエスト ID の付与

リクエストごとに一意な ID を付与し、すべてのログに含めます。

Express ミドルウェア例:

import { v4 as uuidv4 } from 'uuid';
import { Request, Response, NextFunction } from 'express';

export function requestIdMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
) {
  req.id = uuidv4();
  res.setHeader('X-Request-ID', req.id);
  next();
}

// ログ出力時に含める
logger.info('Request received', {
  requestId: req.id,
  method: req.method,
  path: req.path,
});

2.2 分散トレーシング

マイクロサービス間の呼び出しを追跡するため、OpenTelemetry を導入します。

Next.js での設定例:

// instrumentation.ts
import { registerOTel } from '@vercel/otel';

export function register() {
  registerOTel({
    serviceName: 'my-app',
    traceExporter: 'otlp',
    endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
  });
}

手動でスパンを作成:

import { trace } from '@opentelemetry/api';

const tracer = trace.getTracer('my-service');

export async function fetchUserData(userId: string) {
  const span = tracer.startSpan('fetchUserData');
  span.setAttribute('user.id', userId);

  try {
    const data = await db.user.findUnique({ where: { id: userId } });
    span.setStatus({ code: 1 }); // OK
    return data;
  } catch (error) {
    span.setStatus({ code: 2, message: error.message }); // ERROR
    span.recordException(error);
    throw error;
  } finally {
    span.end();
  }
}

3. コンテキスト情報の記録

3.1 必須コンテキスト項目

すべてのログに以下を含めます:

| 項目 | 説明 | 例 | |------|------|----| | timestamp | ISO 8601 形式 | 2026-06-09T12:34:56.789Z | | level | ログレベル | error, warn, info | | service | サービス名 | api-server, worker | | environment | 環境 | production, staging | | requestId | リクエスト ID | uuid | | userId | ユーザー ID(あれば) | 123 |

3.2 エラーログの詳細情報

try {
  await processPayment(orderId);
} catch (error) {
  logger.error('Payment processing failed', {
    requestId: req.id,
    userId: req.user.id,
    orderId,
    error: {
      name: error.name,
      message: error.message,
      stack: error.stack,
      code: error.code, // カスタムエラーコード
    },
    context: {
      amount: order.amount,
      currency: order.currency,
      paymentMethod: order.paymentMethod,
    },
  });
  throw error;
}

3.3 パフォーマンスログ

const startTime = Date.now();

try {
  const result = await fetchData();
  const duration = Date.now() - startTime;

  logger.info('Data fetch completed', {
    requestId: req.id,
    duration,
    recordCount: result.length,
  });

  if (duration > 1000) {
    logger.warn('Slow query detected', {
      requestId: req.id,
      duration,
      query: 'fetchData',
    });
  }

  return result;
} catch (error) {
  logger.error('Data fetch failed', { requestId: req.id, error });
  throw error;
}

4. ログ収集・可視化の構成

4.1 ログ収集アーキテクチャ

[アプリ] → [ログ出力] → [ログ収集] → [ストレージ] → [可視化]
  ↓           ↓            ↓             ↓            ↓
 Next.js    stdout      Fluentd      S3/BQ      Datadog
  API      ファイル    Vector      Elasticsearch  Grafana
 Worker               CloudWatch                Kibana

4.2 ツール比較表

| ツール | 用途 | 料金 | おすすめ度 | |--------|------|------|------------| | Datadog | ログ・メトリクス・APM 統合 | $15~/host | ★★★★★ | | Grafana Loki | ログ集約(軽量) | OSS | ★★★★☆ | | Elasticsearch + Kibana | ログ検索・可視化 | OSS | ★★★★☆ | | CloudWatch Logs | AWS 環境 | 従量課金 | ★★★☆☆ | | Google Cloud Logging | GCP 環境 | 従量課金 | ★★★☆☆ |

4.3 Grafana Loki 構成例

docker-compose.yml:

version: '3.8'
services:
  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"
    volumes:
      - ./loki-config.yaml:/etc/loki/local-config.yaml

  promtail:
    image: grafana/promtail:latest
    volumes:
      - /var/log:/var/log
      - ./promtail-config.yaml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true

アプリからの送信:

import winston from 'winston';
import LokiTransport from 'winston-loki';

const logger = winston.createLogger({
  transports: [
    new LokiTransport({
      host: 'http://localhost:3100',
      labels: { app: 'my-app', env: 'production' },
      json: true,
    }),
  ],
});

5. メトリクス収集の実装

5.1 主要メトリクス種別

| 種別 | 説明 | 例 | |------|------|----| | Counter | 増加のみ | リクエスト数、エラー数 | | Gauge | 増減する値 | CPU 使用率、接続数 | | Histogram | 分布 | レスポンスタイム | | Summary | パーセンタイル | p95, p99 レイテンシ |

5.2 Prometheus メトリクスの実装

import express from 'express';
import promClient from 'prom-client';

const app = express();

// デフォルトメトリクス(CPU、メモリなど)
promClient.collectDefaultMetrics();

// カスタムメトリクス
const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.1, 0.5, 1, 2, 5],
});

const httpRequestTotal = new promClient.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status_code'],
});

// ミドルウェア
app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    const labels = {
      method: req.method,
      route: req.route?.path || req.path,
      status_code: res.statusCode,
    };

    httpRequestDuration.observe(labels, duration);
    httpRequestTotal.inc(labels);
  });

  next();
});

// メトリクスエンドポイント
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', promClient.register.contentType);
  res.end(await promClient.register.metrics());
});

6. アラート設計

6.1 アラート設定のベストプラクティス

| 原則 | 説明 | |------|------| | Actionable | 対応可能な異常のみ通知 | | Threshold | 適切な閾値設定(過検知を避ける) | | Context | 通知に十分なコンテキストを含める | | Escalation | 深刻度に応じた通知先を設定 |

6.2 推奨アラート項目チェックリスト

### 必須アラート
- [ ] エラー率が 5% 以上
- [ ] レスポンスタイム p95 が 3 秒以上
- [ ] DB 接続エラー
- [ ] ディスク使用率 90% 以上
- [ ] メモリ使用率 90% 以上

### 推奨アラート
- [ ] API エラー率が 1% 以上
- [ ] ログイン失敗率の急増
- [ ] 決済失敗率の上昇
- [ ] キューの滞留数増加
- [ ] SSL 証明書の有効期限(30 日前)

### 運用アラート
- [ ] デプロイ通知(成功・失敗)
- [ ] スケーリングイベント
- [ ] バックアップ失敗

6.3 Prometheus アラートルール例

groups:
  - name: api_alerts
    rules:
      - alert: HighErrorRate
        expr: |
          rate(http_requests_total{status_code=~"5.."}[5m])
          / rate(http_requests_total[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is {{ $value | humanizePercentage }}"

      - alert: SlowResponse
        expr: |
          histogram_quantile(0.95,
            rate(http_request_duration_seconds_bucket[5m])
          ) > 3
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Slow API response"
          description: "p95 latency is {{ $value }}s"

7. 個人情報とログの取り扱い

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

| 種類 | 具体例 | 対策 | |------|--------|------| | 認証情報 | パスワード、API キー | マスキング | | 個人情報 | メールアドレス、電話番号 | ユーザー ID に置き換え | | 決済情報 | クレジットカード番号 | PCI DSS 準拠 | | 機密データ | 社内情報、顧客データ | 暗号化または除外 |

7.2 マスキング実装例

function maskSensitiveData(data: any): any {
  if (typeof data !== 'object' || data === null) return data;

  const sensitiveKeys = ['password', 'token', 'apiKey', 'creditCard'];
  const masked = { ...data };

  for (const key in masked) {
    if (sensitiveKeys.some(k => key.toLowerCase().includes(k))) {
      masked[key] = '***MASKED***';
    } else if (typeof masked[key] === 'object') {
      masked[key] = maskSensitiveData(masked[key]);
    }
  }

  return masked;
}

// 使用例
logger.info('User data', maskSensitiveData({
  userId: 123,
  email: 'user@example.com', // そのまま
  password: 'secret123',     // マスク
  apiKey: 'abc123',          // マスク
}));

8. 実装チェックリスト

8.1 開発時

- [ ] 構造化ログを採用(JSON 形式)
- [ ] ログレベルを適切に使い分け
- [ ] リクエスト ID を全ログに含める
- [ ] エラー時にスタックトレースを記録
- [ ] 個人情報をマスキング
- [ ] 環境変数でログレベルを制御
- [ ] ローカルでログを確認できる

8.2 本番リリース前

- [ ] ログ収集基盤を構築(Loki / Datadog など)
- [ ] 分散トレーシングを設定(OpenTelemetry)
- [ ] メトリクス収集エンドポイントを公開
- [ ] 主要アラートを設定(エラー率、レスポンスタイム)
- [ ] ログ保持期間を決定(30 日〜6 ヶ月)
- [ ] ログローテーション設定
- [ ] オンコール体制を整備

8.3 運用時

- [ ] 週次でログを確認し、異常パターンを検出
- [ ] 月次でアラート閾値を見直し
- [ ] 四半期でログ設計をレビュー
- [ ] インシデント後にログを改善
- [ ] 新機能追加時に必要なログを追加

まとめ

本記事では、障害調査を効率化するログ設計とオブザーバビリティの実装方法を解説しました。

重要ポイント:

  1. 構造化ログで検索性・集計性を確保
  2. リクエスト ID で処理を追跡
  3. 分散トレーシングでマイクロサービス間を可視化
  4. メトリクス収集でシステム状態を監視
  5. 適切なアラートで異常を早期検知
  6. 個人情報のマスキングでコンプライアンス遵守

ログとオブザーバビリティは「後から追加する」では遅く、設計段階から組み込むことが重要です。本記事のチェックリストを活用し、障害に強いシステムを構築してください。


Yureate へのお問い合わせ

Yureate では、受託開発・技術顧問を通じて、ログ基盤の構築支援やオブザーバビリティ導入のコンサルティングを提供しています。「どこから手をつければいいか分からない」「既存システムに後付けしたい」といったご相談もお気軽にどうぞ。

  • Web: https://yureate.com
  • Email: contact@yureate.com
この内容について相談する他の記事を見る