観測可能性の設計
観測可能性の設計
Section titled “観測可能性の設計”障害は「再現」ではなく「観測」で解決する。観測可能性を高める設計を詳しく解説します。
各処理にトレースID(request_id)を通し、入出力境界ごとにJSON構造化ログを吐く。
// ✅ 良い例: 構造化ログとトレースIDclass TraceIdMiddleware{ public function handle(Request $request, Closure $next) { // トレースIDを生成(またはリクエストヘッダーから取得) $traceId = $request->header('X-Trace-Id') ?: Str::uuid(); $request->attributes->set('trace_id', $traceId);
// すべてのログにトレースIDを自動的に含める Log::withContext([ 'trace_id' => $traceId, 'user_id' => auth()->id(), ]);
$response = $next($request); $response->header('X-Trace-Id', $traceId);
return $response; }}
class OrderController extends Controller{ public function create(Request $request) { $traceId = $request->attributes->get('trace_id');
// 入出力境界でログを出力 Log::info('Order creation started', [ 'order_data' => $request->all(), 'timestamp' => now()->toIso8601String(), ]);
$order = OrderService::createOrder($request->all(), $traceId);
Log::info('Order creation completed', [ 'order_id' => $order->id, 'status' => $order->status, 'duration' => now()->diffInMilliseconds($request->server('REQUEST_TIME_FLOAT')), ]);
return response()->json($order, 201); }}
// config/logging.php'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['single'], ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'info'), 'formatter' => \Monolog\Formatter\JsonFormatter::class, ],],ログの出力例:
{ "timestamp": "2024-01-01T10:00:00.000Z", "level": "INFO", "message": "Order creation started", "trace_id": "abc123", "user_id": "user456", "order_data": { "amount": 10000, "items": [...] }}なぜ重要か:
- トレーサビリティ: トレースIDでリクエスト全体を追跡可能
- 構造化ログ: JSON形式で検索・分析が容易
- コンテキスト: ユーザーIDなどのコンテキストを自動的に含める
メトリクス設計
Section titled “メトリクス設計”成功率・レイテンシ・リトライ回数・プール使用率を定常監視する。
// ✅ 良い例: Prometheusを使用したメトリクス収集use Prometheus\CollectorRegistry;use Prometheus\Storage\InMemory;
$registry = new CollectorRegistry(new InMemory());
// カウンター: 注文作成回数$orderCreationCounter = $registry->getOrRegisterCounter( 'orders', 'orders_created_total', 'Total number of orders created', ['status']);
// ヒストグラム: 注文作成のレイテンシ$orderCreationDuration = $registry->getOrRegisterHistogram( 'orders', 'orders_creation_duration_seconds', 'Order creation duration in seconds', [], [0.1, 0.5, 1, 2, 5]);
// ゲージ: 接続プールの使用率$connectionPoolGauge = $registry->getOrRegisterGauge( 'db', 'connection_pool_active', 'Active database connections');
class OrderController extends Controller{ public function create(Request $request) { $timer = $orderCreationDuration->startTimer();
try { $order = OrderService::createOrder($request->all(), $request->attributes->get('trace_id'));
// 成功時のメトリクス $orderCreationCounter->inc(['success']);
return response()->json($order, 201); } catch (\Exception $e) { // 失敗時のメトリクス $orderCreationCounter->inc(['failure']);
throw $e; } finally { $timer->observeDuration(); } }}
// メトリクスエンドポイントRoute::get('/metrics', function () use ($registry) { $renderer = new \Prometheus\RenderTextFormat(); return response($renderer->render($registry->getMetricFamilySamples())) ->header('Content-Type', 'text/plain');});監視すべきメトリクス:
- 成功率:
orders_created_total{status="success"}/orders_created_total{status="failure"} - レイテンシ:
orders_creation_duration_seconds(p50, p95, p99) - リトライ回数:
orders_retry_total - プール使用率:
db_connection_pool_active/db_connection_pool_max
トレース設計
Section titled “トレース設計”外部API・DBアクセス単位にSpanを埋め、分散トレース可能にする。
// ✅ 良い例: OpenTelemetryを使用した分散トレースuse OpenTelemetry\API\Trace\TracerInterface;use OpenTelemetry\SDK\Trace\TracerProvider;
$tracerProvider = new TracerProvider();$tracer = $tracerProvider->getTracer('order-service');
class OrderService{ public function createOrder($orderData, $traceId) { $span = $tracer->spanBuilder('create-order') ->setAttribute('order.amount', $orderData['amount']) ->setAttribute('trace.id', $traceId) ->startSpan();
try { // DB操作のSpan $dbSpan = $tracer->spanBuilder('db.save-order') ->setParent($span->getContext()) ->startSpan();
$order = Order::create($orderData);
$dbSpan->setAttribute('order.id', $order->id); $dbSpan->end();
return $order; } finally { $span->end(); } }}トレースの出力例:
Trace: abc123├─ Span: create-order (duration: 150ms)│ ├─ Span: db.save-order (duration: 50ms)│ └─ Span: payment-api.charge (duration: 100ms)なぜ重要か:
- 分散トレース: 複数サービス間のリクエストフローを追跡可能
- ボトルネック特定: どの処理が遅いかを特定可能
- エラー追跡: エラーが発生した箇所を特定可能
観測可能性の設計のポイント:
- ログ: トレースIDを通し、構造化ログを出力
- メトリクス: 成功率・レイテンシ・リトライ回数・プール使用率を監視
- トレース: 外部API・DBアクセス単位にSpanを埋め、分散トレース可能にする
これらの設計により、障害を「再現」ではなく「観測」で解決できます。