- 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, Zabbix | Prometheus, Grafana, Jaeger |
オブザーバビリティの3本柱
- ログ(Logs):個別イベントの詳細記録
- メトリクス(Metrics):時系列数値データ
- トレース(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"]

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(初週):構造化ログの導入とヘルスチェック
- フェーズ2(2〜3週目):Prometheusメトリクスの計装
- フェーズ3(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で検索してみてください。