Skip to content

観測可能性の設計

障害は「再現」ではなく「観測」で解決する。観測可能性を高める設計を詳しく解説します。

各処理にトレースID(request_id)を通し、入出力境界ごとにJSON構造化ログを吐く。

// ✅ 良い例: 構造化ログとトレースID
class 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などのコンテキストを自動的に含める

成功率・レイテンシ・リトライ回数・プール使用率を定常監視する。

// ✅ 良い例: 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

外部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を埋め、分散トレース可能にする

これらの設計により、障害を「再現」ではなく「観測」で解決できます。