Advanced

Designing Logging and Monitoring Infrastructure with Claude Code

Learn about designing logging and monitoring infrastructure using Claude Code. Includes practical code examples.

モニタリング基盤の構築にClaude Codeを活用する

本番環境のトラブルシューティングには、適切なログとモニタリングが不可欠です。Claude Codeは構造化ログ、分散トレーシング、メトリクス収集の基盤を一括で構築でき、可観測性(Observability)の高いシステムを効率的に実現します。

構造化ログの実装

> pinoを使った構造化ログのセットアップを作成して。
> リクエストID、ユーザーID、処理時間を含めて。
> 環境ごとにログレベルを切り替えられるようにして。
// src/lib/logger.ts
import pino from 'pino';

export const logger = pino({
  level: process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'info' : 'debug'),
  transport: process.env.NODE_ENV !== 'production'
    ? { target: 'pino-pretty', options: { colorize: true } }
    : undefined,
  formatters: {
    level(label) {
      return { level: label };
    },
  },
  base: {
    service: process.env.SERVICE_NAME || 'my-app',
    env: process.env.NODE_ENV,
  },
  timestamp: pino.stdTimeFunctions.isoTime,
  redact: ['password', 'token', 'authorization', 'cookie'],
});

// リクエストスコープのロガー
export function createRequestLogger(requestId: string, userId?: string) {
  return logger.child({
    requestId,
    userId,
  });
}

リクエストログミドルウェア

// src/middleware/request-logger.ts
import { Request, Response, NextFunction } from 'express';
import { createRequestLogger } from '@/lib/logger';
import { randomUUID } from 'crypto';

export function requestLogger(req: Request, res: Response, next: NextFunction) {
  const requestId = (req.headers['x-request-id'] as string) || randomUUID();
  const start = Date.now();

  const log = createRequestLogger(requestId, req.user?.id);
  req.log = log;

  res.setHeader('X-Request-Id', requestId);

  log.info({
    method: req.method,
    url: req.url,
    userAgent: req.headers['user-agent'],
  }, 'Request started');

  res.on('finish', () => {
    const duration = Date.now() - start;
    const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'info';

    log[level]({
      method: req.method,
      url: req.url,
      statusCode: res.statusCode,
      duration,
    }, 'Request completed');
  });

  next();
}

OpenTelemetryによる分散トレーシング

> OpenTelemetryを使った分散トレーシングを設定して。
> HTTP、DB、Redis呼び出しの自動計装を有効にして。
// src/instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { Resource } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';

const sdk = new NodeSDK({
  resource: new Resource({
    [ATTR_SERVICE_NAME]: process.env.SERVICE_NAME || 'my-app',
    [ATTR_SERVICE_VERSION]: process.env.APP_VERSION || '1.0.0',
  }),
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces',
  }),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter({
      url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/metrics',
    }),
    exportIntervalMillis: 30000,
  }),
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-http': { enabled: true },
      '@opentelemetry/instrumentation-express': { enabled: true },
      '@opentelemetry/instrumentation-pg': { enabled: true },
      '@opentelemetry/instrumentation-redis': { enabled: true },
    }),
  ],
});

sdk.start();

process.on('SIGTERM', () => {
  sdk.shutdown().then(() => process.exit(0));
});

カスタムメトリクスの収集

// src/lib/metrics.ts
import { metrics } from '@opentelemetry/api';

const meter = metrics.getMeter('my-app');

// リクエストカウンター
export const requestCounter = meter.createCounter('http_requests_total', {
  description: 'Total HTTP requests',
});

// レスポンスタイムのヒストグラム
export const responseTime = meter.createHistogram('http_response_time_ms', {
  description: 'HTTP response time in milliseconds',
  unit: 'ms',
});

// アクティブユーザー数のゲージ
export const activeUsers = meter.createUpDownCounter('active_users', {
  description: 'Number of active users',
});

// ビジネスメトリクス
export const orderCounter = meter.createCounter('orders_total', {
  description: 'Total orders placed',
});

// Usage example
export function recordRequest(method: string, path: string, statusCode: number, duration: number) {
  requestCounter.add(1, { method, path, statusCode: String(statusCode) });
  responseTime.record(duration, { method, path });
}

ヘルスチェックエンドポイント

// src/app/api/health/route.ts
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/db';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL!);

export async function GET() {
  const checks = {
    status: 'ok' as 'ok' | 'degraded' | 'error',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    checks: {} as Record<string, { status: string; latency?: number }>,
  };

  // DB接続チェック
  try {
    const start = Date.now();
    await prisma.$queryRaw`SELECT 1`;
    checks.checks.database = { status: 'ok', latency: Date.now() - start };
  } catch {
    checks.checks.database = { status: 'error' };
    checks.status = 'degraded';
  }

  // Redis接続チェック
  try {
    const start = Date.now();
    await redis.ping();
    checks.checks.redis = { status: 'ok', latency: Date.now() - start };
  } catch {
    checks.checks.redis = { status: 'error' };
    checks.status = 'degraded';
  }

  const statusCode = checks.status === 'ok' ? 200 : 503;
  return NextResponse.json(checks, { status: statusCode });
}

エラートラッキング

// src/lib/error-tracker.ts
import { logger } from './logger';

export function trackError(error: Error, context?: Record<string, unknown>) {
  logger.error({
    err: {
      name: error.name,
      message: error.message,
      stack: error.stack,
    },
    ...context,
  }, 'Unhandled error');

  // Sentryなどの外部サービスへ送信
  if (process.env.SENTRY_DSN) {
    // Sentry.captureException(error, { extra: context });
  }
}

// グローバルエラーハンドラー
process.on('uncaughtException', (error) => {
  trackError(error, { type: 'uncaughtException' });
  process.exit(1);
});

process.on('unhandledRejection', (reason) => {
  trackError(reason instanceof Error ? reason : new Error(String(reason)), {
    type: 'unhandledRejection',
  });
});

Zusammenfassung

Claude Codeを使えば、構造化ログ、分散トレーシング、メトリクス収集、ヘルスチェックを含むモニタリング基盤を効率的に構築できます。本番運用に必要な可観測性を最初から組み込むことで、トラブルシューティングの時間を大幅に短縮できます。開発環境の設定はCLAUDE.mdに記述しておくと、Claude Codeが一貫した構成を生成します。マイクロサービスでのモニタリングはマイクロサービス設計も参考にしてください。

Claude Codeの詳細はAnthropic公式ドキュメントをご覧ください。OpenTelemetryの詳細はOpenTelemetry公式サイトを参照してください。

#Claude Code #ログ #モニタリング #可観測性 #OpenTelemetry