𝕏 f B! L
案件・求人数 12,345
案件を探す(準備中) エージェントを探す(準備中) お役立ち情報 ログイン
案件・求人数 12,345
OpenAI Codex CLI × オブザーバビリティ・監視設計ガイド|ログ・メトリクス・トレースの自動構築

OpenAI Codex CLI × オブザーバビリティ・監視設計ガイド|ログ・メトリクス・トレースの自動構築

OpenAI Codex CLIオブザーバビリティ監視設計PrometheusGrafana
目次
⚡ 3秒でわかる!この記事のポイント
  • Codex CLIでオブザーバビリティの3本柱(ログ・メトリクス・トレース)を自動構築できる
  • Prometheus + Grafanaのダッシュボードやアラートルールをコード生成で効率的に構築可能
  • SES案件でオブザーバビリティ設計スキルは月額80〜110万円クラスの高単価案件に直結する

「アプリケーションの障害原因がわからず、調査に何時間もかかった…」「監視設定が属人化していて、引き継ぎが大変…」——SES現場でこうした経験をしたことはありませんか。

結論から言うと、OpenAI Codex CLIを活用すれば、オブザーバビリティ基盤の設計・実装を大幅に効率化できます。ログ収集・メトリクス取得・分散トレーシングの3本柱を、コード生成の力で素早く構築可能です。

この記事はOpenAI Codex CLI完全攻略シリーズとして、オブザーバビリティ・監視設計の実践手法を解説します。

この記事でわかること
  • オブザーバビリティの3本柱とその設計原則
  • Codex CLIでPrometheus/Grafana環境を自動構築する方法
  • 構造化ログとOpenTelemetryトレーシングの実装
  • アラートルールとインシデント対応の自動化
  • SES現場での監視設計スキルの市場価値

オブザーバビリティの基本概念

監視(Monitoring)とオブザーバビリティの違い

従来の「監視」はサーバーのCPU使用率やメモリ使用量を閾値で見張るアプローチです。一方、**オブザーバビリティ(Observability)**はシステムの内部状態を外部出力から理解する能力を指します。

観点従来の監視オブザーバビリティ
アプローチ既知の問題を検出未知の問題を調査
データメトリクスのみログ・メトリクス・トレース
分析方法閾値ベースのアラートクエリベースの探索的分析
スケーラビリティホスト単位サービス単位・リクエスト単位
ツール例Nagios, ZabbixPrometheus, Grafana, Jaeger

オブザーバビリティの3本柱

  1. ログ(Logs):個別イベントの詳細記録
  2. メトリクス(Metrics):時系列数値データ
  3. トレース(Traces):リクエストの分散処理経路

Codex CLIは、これら3つすべての実装を効率的に支援します。

Codex CLIでのログ設計と実装

構造化ログの自動生成

Codex CLIに「構造化ログを実装して」と指示すると、JSON形式のログ出力を自動生成します。

このNode.jsアプリケーションに構造化ログを導入してください。
要件:
- JSON形式の出力
- リクエストID、ユーザーID、処理時間を含む
- ログレベル(debug/info/warn/error)の切り替え
- 本番環境ではdebugを抑制

Codex CLIが生成するログユーティリティの例です。

// src/lib/logger.ts
import pino from 'pino';

const isProduction = process.env.NODE_ENV === 'production';

export const logger = pino({
  level: process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'),
  formatters: {
    level(label) {
      return { level: label };
    },
  },
  timestamp: pino.stdTimeFunctions.isoTime,
  serializers: {
    err: pino.stdSerializers.err,
    req: pino.stdSerializers.req,
    res: pino.stdSerializers.res,
  },
  ...(isProduction
    ? {}
    : {
        transport: {
          target: 'pino-pretty',
          options: { colorize: true },
        },
      }),
});

export function createRequestLogger(requestId: string, userId?: string) {
  return logger.child({
    requestId,
    userId: userId || 'anonymous',
    service: process.env.SERVICE_NAME || 'app',
  });
}

ログミドルウェアの実装

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

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

  const log = createRequestLogger(requestId, userId);

  // リクエストヘッダーにIDを伝播
  res.setHeader('x-request-id', requestId);

  log.info({
    msg: 'Request received',
    method: req.method,
    path: req.path,
    query: req.query,
    userAgent: req.headers['user-agent'],
  });

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

    log[logLevel]({
      msg: 'Request completed',
      method: req.method,
      path: req.path,
      statusCode: res.statusCode,
      duration,
      contentLength: res.getHeader('content-length'),
    });
  });

  // リクエストコンテキストにloggerを注入
  (req as any).log = log;
  (req as any).requestId = requestId;

  next();
}

Fluentdでのログ集約

Codex CLIでFluentdの設定ファイルも自動生成できます。

<!-- fluent.conf -->
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<filter app.**>
  @type parser
  key_name log
  reserve_data true
  <parse>
    @type json
  </parse>
</filter>

<filter app.**>
  @type record_transformer
  <record>
    cluster ${CLUSTER_NAME}
    namespace ${NAMESPACE}
    hostname "#{Socket.gethostname}"
  </record>
</filter>

<match app.**>
  @type elasticsearch
  host elasticsearch
  port 9200
  index_name app-logs
  type_name _doc
  logstash_format true
  logstash_prefix app-logs
  <buffer>
    @type file
    path /var/log/fluentd/buffer
    flush_interval 5s
    chunk_limit_size 5M
    retry_max_times 3
  </buffer>
</match>

Prometheusメトリクスの実装

アプリケーションメトリクスの計装

Codex CLIでPrometheusメトリクスの計装コードを自動生成します。

Prometheusメトリクスをこのアプリケーションに追加してください。
計測対象:
- HTTPリクエスト数(メソッド・パス・ステータスコード別)
- レスポンスタイム(ヒストグラム)
- アクティブ接続数
- ビジネスメトリクス(注文数、エラー率)
// src/lib/metrics.ts
import { Registry, Counter, Histogram, Gauge, collectDefaultMetrics } from 'prom-client';

const register = new Registry();

// デフォルトメトリクス(CPU、メモリ、GCなど)
collectDefaultMetrics({ register });

// HTTPリクエストカウンター
export const httpRequestsTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'path', 'status_code'] as const,
  registers: [register],
});

// レスポンスタイムヒストグラム
export const httpRequestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration in seconds',
  labelNames: ['method', 'path', 'status_code'] as const,
  buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
  registers: [register],
});

// アクティブ接続数
export const activeConnections = new Gauge({
  name: 'active_connections',
  help: 'Number of active connections',
  registers: [register],
});

// ビジネスメトリクス
export const ordersTotal = new Counter({
  name: 'orders_total',
  help: 'Total number of orders',
  labelNames: ['status', 'payment_method'] as const,
  registers: [register],
});

export const orderAmount = new Histogram({
  name: 'order_amount_yen',
  help: 'Order amount in JPY',
  buckets: [1000, 5000, 10000, 50000, 100000, 500000],
  registers: [register],
});

export { register };

メトリクスミドルウェア

// src/middleware/metrics.ts
import { Request, Response, NextFunction } from 'express';
import {
  httpRequestsTotal,
  httpRequestDuration,
  activeConnections,
  register,
} from '../lib/metrics';

export function metricsMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
) {
  activeConnections.inc();
  const end = httpRequestDuration.startTimer();

  res.on('finish', () => {
    activeConnections.dec();
    const labels = {
      method: req.method,
      path: normalizePath(req.route?.path || req.path),
      status_code: String(res.statusCode),
    };
    httpRequestsTotal.inc(labels);
    end(labels);
  });

  next();
}

// パスパラメータを正規化(カーディナリティ爆発防止)
function normalizePath(path: string): string {
  return path
    .replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g, '/:id')
    .replace(/\/\d+/g, '/:id');
}

// /metrics エンドポイント
export async function metricsHandler(_req: Request, res: Response) {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
}

Prometheus設定の自動生成

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "rules/*.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["alertmanager:9093"]

scrape_configs:
  - job_name: "app"
    metrics_path: "/metrics"
    static_configs:
      - targets: ["app:3000"]
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance

  - job_name: "node-exporter"
    static_configs:
      - targets: ["node-exporter:9100"]

  - job_name: "postgres-exporter"
    static_configs:
      - targets: ["postgres-exporter:9187"]

Codex CLI × オブザーバビリティ設計の構成図解

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

トレーシングの基本設定

Codex CLIで分散トレーシングの実装を自動生成します。

// src/lib/tracing.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import {
  ATTR_SERVICE_NAME,
  ATTR_SERVICE_VERSION,
  ATTR_DEPLOYMENT_ENVIRONMENT_NAME,
} from '@opentelemetry/semantic-conventions';

const sdk = new NodeSDK({
  resource: new Resource({
    [ATTR_SERVICE_NAME]: process.env.SERVICE_NAME || 'app',
    [ATTR_SERVICE_VERSION]: process.env.APP_VERSION || '1.0.0',
    [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: process.env.NODE_ENV || 'development',
  }),
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://jaeger:4318/v1/traces',
  }),
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-http': {
        ignoreIncomingPaths: ['/health', '/metrics'],
      },
      '@opentelemetry/instrumentation-express': {},
      '@opentelemetry/instrumentation-pg': {},
    }),
  ],
});

sdk.start();

process.on('SIGTERM', () => {
  sdk.shutdown().then(
    () => console.log('OpenTelemetry SDK shut down'),
    (err) => console.error('Error shutting down OpenTelemetry SDK', err)
  );
});

export { sdk };

カスタムスパンの作成

// src/services/order-service.ts
import { trace, SpanStatusCode } from '@opentelemetry/api';

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

export async function createOrder(orderData: OrderInput): Promise<Order> {
  return tracer.startActiveSpan('createOrder', async (span) => {
    try {
      span.setAttribute('order.customer_id', orderData.customerId);
      span.setAttribute('order.items_count', orderData.items.length);

      // 在庫確認
      const stockResult = await tracer.startActiveSpan('checkStock', async (stockSpan) => {
        const result = await inventoryService.checkAvailability(orderData.items);
        stockSpan.setAttribute('stock.available', result.available);
        stockSpan.end();
        return result;
      });

      if (!stockResult.available) {
        span.setStatus({ code: SpanStatusCode.ERROR, message: 'Out of stock' });
        throw new Error('在庫不足です');
      }

      // 決済処理
      const payment = await tracer.startActiveSpan('processPayment', async (paymentSpan) => {
        paymentSpan.setAttribute('payment.method', orderData.paymentMethod);
        paymentSpan.setAttribute('payment.amount', orderData.totalAmount);
        const result = await paymentService.charge(orderData);
        paymentSpan.end();
        return result;
      });

      // 注文確定
      const order = await orderRepository.create({
        ...orderData,
        paymentId: payment.id,
        status: 'confirmed',
      });

      span.setAttribute('order.id', order.id);
      span.setAttribute('order.total_amount', order.totalAmount);
      span.setStatus({ code: SpanStatusCode.OK });

      return order;
    } catch (error) {
      span.recordException(error as Error);
      span.setStatus({ code: SpanStatusCode.ERROR });
      throw error;
    } finally {
      span.end();
    }
  });
}

Grafanaダッシュボードの自動生成

ダッシュボードJSON の生成

Codex CLIに「Grafanaダッシュボードを作成して」と指示すると、JSON形式のダッシュボード定義を生成します。

以下のメトリクスを表示するGrafanaダッシュボードを作成してください。
- リクエストレート(RPS)
- レスポンスタイムの分布(p50/p95/p99)
- エラーレート(5xx率)
- アクティブ接続数
- CPU/メモリ使用率
{
  "dashboard": {
    "title": "Application Overview",
    "tags": ["app", "production"],
    "timezone": "Asia/Tokyo",
    "panels": [
      {
        "title": "Request Rate (RPS)",
        "type": "timeseries",
        "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
        "targets": [
          {
            "expr": "sum(rate(http_requests_total[5m]))",
            "legendFormat": "Total RPS"
          },
          {
            "expr": "sum(rate(http_requests_total{status_code=~\"5..\"}[5m]))",
            "legendFormat": "5xx RPS"
          }
        ]
      },
      {
        "title": "Response Time Percentiles",
        "type": "timeseries",
        "gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
        "targets": [
          {
            "expr": "histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m]))",
            "legendFormat": "p50"
          },
          {
            "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))",
            "legendFormat": "p95"
          },
          {
            "expr": "histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))",
            "legendFormat": "p99"
          }
        ]
      },
      {
        "title": "Error Rate (%)",
        "type": "gauge",
        "gridPos": { "h": 8, "w": 6, "x": 0, "y": 8 },
        "targets": [
          {
            "expr": "sum(rate(http_requests_total{status_code=~\"5..\"}[5m])) / sum(rate(http_requests_total[5m])) * 100"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "thresholds": {
              "steps": [
                { "color": "green", "value": 0 },
                { "color": "yellow", "value": 1 },
                { "color": "red", "value": 5 }
              ]
            },
            "unit": "percent"
          }
        }
      }
    ]
  }
}

アラートルールの自動生成

# rules/app-alerts.yml
groups:
  - name: app_alerts
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status_code=~"5.."}[5m]))
          / sum(rate(http_requests_total[5m])) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is {{ $value | humanizePercentage }} (threshold: 5%)"
          runbook_url: "https://wiki.example.com/runbooks/high-error-rate"

      - alert: HighLatency
        expr: |
          histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 2
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "High p95 latency detected"
          description: "p95 latency is {{ $value }}s (threshold: 2s)"

      - alert: PodHighMemoryUsage
        expr: |
          container_memory_usage_bytes / container_spec_memory_limit_bytes > 0.85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Pod memory usage is high"
          description: "Pod {{ $labels.pod }} memory usage is {{ $value | humanizePercentage }}"

      - alert: DatabaseConnectionPoolExhausted
        expr: |
          pg_stat_activity_count / pg_settings_max_connections > 0.8
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Database connection pool near exhaustion"
          description: "{{ $value | humanizePercentage }} of connections used"

Docker Composeによるオブザーバビリティスタックの構築

Codex CLIでローカル開発用のオブザーバビリティスタック全体を構築できます。

# docker-compose.observability.yml
services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./monitoring/rules:/etc/prometheus/rules
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3001:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
      GF_USERS_ALLOW_SIGN_UP: "false"
    volumes:
      - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources
      - grafana-data:/var/lib/grafana

  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"
      - "4318:4318"
    environment:
      COLLECTOR_OTLP_ENABLED: "true"

  alertmanager:
    image: prom/alertmanager:latest
    ports:
      - "9093:9093"
    volumes:
      - ./monitoring/alertmanager.yml:/etc/alertmanager/alertmanager.yml

  node-exporter:
    image: prom/node-exporter:latest
    ports:
      - "9100:9100"

volumes:
  prometheus-data:
  grafana-data:

SLI/SLO の定義と監視

SLI(Service Level Indicator)の設計

Codex CLIで「このAPIのSLI/SLOを定義して」と指示すると、適切な指標と目標値を提案します。

// src/lib/sli.ts
import { Counter, Histogram } from 'prom-client';

// 可用性SLI:成功リクエスト / 全リクエスト
export const sliAvailability = {
  total: new Counter({
    name: 'sli_requests_total',
    help: 'Total SLI-eligible requests',
    labelNames: ['endpoint'] as const,
  }),
  success: new Counter({
    name: 'sli_requests_success_total',
    help: 'Successful SLI-eligible requests',
    labelNames: ['endpoint'] as const,
  }),
};

// レイテンシSLI:閾値内のリクエスト割合
export const sliLatency = new Histogram({
  name: 'sli_request_duration_seconds',
  help: 'SLI request duration',
  labelNames: ['endpoint'] as const,
  buckets: [0.1, 0.25, 0.5, 1.0, 2.5],
});

SLOダッシュボード

# SLO定義
slos:
  - name: API Availability
    target: 99.9%
    window: 30d
    sli_query: |
      sum(rate(sli_requests_success_total[30d]))
      / sum(rate(sli_requests_total[30d]))

  - name: API Latency (p99 < 500ms)
    target: 99.0%
    window: 30d
    sli_query: |
      sum(rate(sli_request_duration_seconds_bucket{le="0.5"}[30d]))
      / sum(rate(sli_request_duration_seconds_count[30d]))

インシデント対応の自動化

Alertmanagerの通知設定

# alertmanager.yml
route:
  group_by: ['alertname', 'severity']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: 'slack-default'

  routes:
    - match:
        severity: critical
      receiver: 'slack-critical'
      repeat_interval: 1h
    - match:
        severity: warning
      receiver: 'slack-warning'

receivers:
  - name: 'slack-default'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/xxx'
        channel: '#alerts'
        title: '{{ .GroupLabels.alertname }}'
        text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'

  - name: 'slack-critical'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/xxx'
        channel: '#alerts-critical'
        title: '🚨 {{ .GroupLabels.alertname }}'
        text: '{{ range .Alerts }}{{ .Annotations.description }}\nRunbook: {{ .Annotations.runbook_url }}{{ end }}'
    pagerduty_configs:
      - routing_key: 'xxx'
        severity: 'critical'

ランブックの自動生成

Codex CLIにインシデント対応のランブックも生成させることができます。エラーハンドリングの詳細は「Codex CLI エラーハンドリングガイド」を参照してください。

SES現場での監視設計のベストプラクティス

段階的な導入アプローチ

新規SES案件で監視を導入する際は、以下の順序が推奨されます。

  1. フェーズ1(初週):構造化ログの導入とヘルスチェック
  2. フェーズ2(2〜3週目):Prometheusメトリクスの計装
  3. フェーズ3(4週目〜):分散トレーシングとダッシュボード
  4. フェーズ4(運用安定後):SLI/SLO定義とアラート最適化

コスト効率の高い監視設計

Codex CLIでのコスト最適化については「Codex CLI コスト最適化ガイド」も参考にしてください。

  • メトリクスのカーディナリティ管理:ラベル値の爆発を防ぐ
  • ログのサンプリング:debugログは本番で10%サンプリング
  • トレースのサンプリング:正常リクエストは1%、エラーは100%記録
  • データ保持期間:メトリクス90日、ログ30日、トレース14日

よくある失敗パターン

失敗パターン原因Codex CLIでの対策
アラート疲れ閾値が厳しすぎる段階的な閾値設定の自動生成
メトリクス爆発カーディナリティ管理不足パス正規化の自動実装
ログ検索不能構造化されていないJSON構造化ログの自動導入
原因特定が遅いトレーシングなしOpenTelemetryの自動計装

SES案件での単価への影響

オブザーバビリティ設計スキルはSES市場で高い需要があります。

スキルレベル想定月額単価内容
基本60〜70万円ログ収集・基本的なメトリクス監視
中級75〜90万円Prometheus/Grafana構築・アラート設計
上級90〜110万円分散トレーシング・SLI/SLO設計
エキスパート110万円〜全社オブザーバビリティ基盤設計

CI/CDとの統合スキルも求められます。詳しくは「Codex CLI × CI/CD自動化ガイド」をご覧ください。

まとめ:Codex CLIでオブザーバビリティを加速する

この記事のポイントをまとめます。

  • オブザーバビリティの3本柱(ログ・メトリクス・トレース)をCodex CLIで一括構築できる
  • 構造化ログはpino + JSON形式で実装し、リクエストIDで追跡可能にする
  • Prometheusメトリクスはカーディナリティ管理を意識して計装する
  • OpenTelemetryで分散トレーシングを実装し、サービス間のボトルネックを可視化
  • GrafanaダッシュボードとアラートルールもCodex CLIで自動生成可能
  • SLI/SLOを定義してサービス品質を定量的に管理する

Codex CLIを活用してオブザーバビリティスキルを身につけ、SES市場での競争力を高めましょう。

SES BASEで監視・SRE案件を探す

オブザーバビリティ・SRE・監視設計の経験を活かせるSES案件をSES BASEで検索してみてください。

SES案件をお探しですか?

SES記事をもっと読む →
🏗️

SES BASE 編集長

SES業界歴10年以上のメンバーが在籍する編集チーム。SES企業での営業・エンジニア経験、フリーランス独立経験を持つメンバーが、業界のリアルな情報をお届けします。

📊 業界データに基づく記事制作 🔍 IPA・経済産業省データ参照 💼 SES実務経験者が執筆・監修