オブザーバビリティ完全ガイド
オブザーバビリティ完全ガイド
Section titled “オブザーバビリティ完全ガイド”オブザーバビリティの実践的な実装方法を、実務で使える実装例とベストプラクティスとともに詳しく解説します。
1. オブザーバビリティの3本柱
Section titled “1. オブザーバビリティの3本柱”ログ(Logs)
Section titled “ログ(Logs)”// 構造化ログの実装import winston from 'winston';
const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }) ]});
// 構造化ログの使用logger.info('User logged in', { userId: user.id, ipAddress: req.ip, timestamp: new Date().toISOString()});メトリクス(Metrics)
Section titled “メトリクス(Metrics)”// Prometheusメトリクスの実装import { register, Counter, Histogram, Gauge } from 'prom-client';
// カウンターconst httpRequestsTotal = new Counter({ name: 'http_requests_total', help: 'Total number of HTTP requests', labelNames: ['method', 'route', 'status']});
// ヒストグラムconst httpRequestDuration = new Histogram({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'route'], buckets: [0.1, 0.5, 1, 2, 5]});
// ゲージconst activeConnections = new Gauge({ name: 'active_connections', help: 'Number of active connections'});
// 使用例app.use((req, res, next) => { const start = Date.now();
res.on('finish', () => { const duration = (Date.now() - start) / 1000; httpRequestsTotal.inc({ method: req.method, route: req.route?.path || req.path, status: res.statusCode }); httpRequestDuration.observe( { method: req.method, route: req.route?.path || req.path }, duration ); });
next();});トレース(Traces)
Section titled “トレース(Traces)”// OpenTelemetryトレーシングの実装import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('order-service');
async function processOrder(orderData: OrderData) { const span = tracer.startSpan('processOrder');
try { span.setAttribute('order.id', orderData.id); span.setAttribute('order.amount', orderData.amount);
// 子スパンの作成 const paymentSpan = tracer.startSpan('processPayment', { parent: span });
await processPayment(orderData); paymentSpan.end();
span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR }); span.recordException(error); throw error; } finally { span.end(); }}2. ログ管理
Section titled “2. ログ管理”// 構造化ログの実装interface LogContext { userId?: string; requestId?: string; ipAddress?: string; userAgent?: string;}
class StructuredLogger { private context: LogContext = {};
setContext(context: LogContext): void { this.context = { ...this.context, ...context }; }
info(message: string, data?: any): void { logger.info(message, { ...this.context, ...data, timestamp: new Date().toISOString() }); }
error(message: string, error: Error, data?: any): void { logger.error(message, { ...this.context, ...data, error: { message: error.message, stack: error.stack, name: error.name }, timestamp: new Date().toISOString() }); }}// ログレベルの使い分けlogger.debug('Detailed information for debugging'); // 開発時のみlogger.info('General information'); // 通常の情報logger.warn('Warning message'); // 警告logger.error('Error message'); // エラーlogger.fatal('Fatal error'); // 致命的なエラー3. メトリクス収集
Section titled “3. メトリクス収集”カスタムメトリクス
Section titled “カスタムメトリクス”// ビジネスメトリクスの実装const ordersProcessed = new Counter({ name: 'orders_processed_total', help: 'Total number of orders processed', labelNames: ['status', 'payment_method']});
const orderValue = new Histogram({ name: 'order_value', help: 'Order value distribution', buckets: [0, 100, 500, 1000, 5000, 10000]});
async function processOrder(order: Order) { try { await processPayment(order); ordersProcessed.inc({ status: 'success', payment_method: order.paymentMethod }); orderValue.observe(order.amount); } catch (error) { ordersProcessed.inc({ status: 'failed', payment_method: order.paymentMethod }); throw error; }}4. 分散トレーシング
Section titled “4. 分散トレーシング”トレースコンテキストの伝播
Section titled “トレースコンテキストの伝播”// トレースコンテキストの伝播import { context, propagation } from '@opentelemetry/api';
async function makeHttpRequest(url: string, data: any) { const span = tracer.startSpan('http_request'); const ctx = trace.setSpan(context.active(), span);
// トレースコンテキストをHTTPヘッダーに注入 const headers: Record<string, string> = {}; propagation.inject(ctx, headers);
const response = await fetch(url, { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
span.end(); return response;}5. アラート設定
Section titled “5. アラート設定”アラートルール
Section titled “アラートルール”# Prometheusアラートルールgroups: - name: application_alerts rules: - alert: HighErrorRate expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05 for: 5m annotations: summary: "High error rate detected" description: "Error rate is {{ $value }} errors per second"
- alert: HighLatency expr: histogram_quantile(0.99, http_request_duration_seconds) > 1 for: 5m annotations: summary: "High latency detected" description: "99th percentile latency is {{ $value }} seconds"6. 実践的なベストプラクティス
Section titled “6. 実践的なベストプラクティス”// 相関IDの実装import { v4 as uuidv4 } from 'uuid';
app.use((req, res, next) => { const correlationId = req.headers['x-correlation-id'] || uuidv4(); req.correlationId = correlationId; res.setHeader('X-Correlation-ID', correlationId);
// ログコンテキストに設定 logger.setContext({ correlationId });
next();});// ELK Stackへの送信import { createLogger, transports } from 'winston';import { ElasticsearchTransport } from 'winston-elasticsearch';
const esTransport = new ElasticsearchTransport({ level: 'info', clientOpts: { node: 'http://elasticsearch:9200' }});
const logger = createLogger({ transports: [esTransport]});オブザーバビリティ完全ガイドのポイント:
- 3本柱: ログ、メトリクス、トレース
- 構造化ログ: 検索可能なログ
- メトリクス収集: Prometheus、カスタムメトリクス
- 分散トレーシング: OpenTelemetry、トレースコンテキスト
- アラート: 問題の早期発見
- ベストプラクティス: 相関ID、ログ集約
適切なオブザーバビリティにより、システムの状態を把握できます。