monitoring-observability について
サービスのテレメトリー設定や運用チェックを支援する、エンジニア向けの監視・可観測性スキルです。OpenTelemetry、Prometheusメトリクス、構造化ログ、アラートルール、よくある問題を扱います。
主な機能
- 01OpenTelemetryの設定と自動計装
- 02カスタムスパンとメトリクスの追加
- 03Prometheusメトリクスの収集
- 04秘匿情報を除外する構造化ログ
- 05アラートルールとSLOの確認
- 06可観測性のアンチパターン一覧
活用シーン
- 01サービスへのトレースとメトリクスの追加
- 02Prometheusメトリクスの公開と収集
- 03アプリケーションログの機密情報の除外
- 04サービスのエラー率やレイテンシに基づくアラート作成
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ rohitg00/ awesome-claude-code-toolkit --skill monitoring-observabilityインストール後、Claude Code で/monitoring-observabilityを入力して読み込まれたか確認します。
詳細
監視と可観測性
OpenTelemetry のセットアップ
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import { PgInstrumentation } from "@opentelemetry/instrumentation-pg";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
const sdk = new NodeSDK({
serviceName: "order-service",
traceExporter: new OTLPTraceExporter({
url: "http://otel-collector:4318/v1/traces",
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: "http://otel-collector:4318/v1/metrics",
}),
exportIntervalMillis: 15000,
}),
instrumentations: [
new HttpInstrumentation(),
new PgInstrumentation(),
],
});
sdk.start();
process.on("SIGTERM", () => sdk.shutdown());
カスタムスパンとメトリクス
import { trace, metrics, SpanStatusCode } from "@opentelemetry/api";
const tracer = trace.getTracer("order-service");
const meter = metrics.getMeter("order-service");
const orderCounter = meter.createCounter("orders.created", {
description: "Number of orders created",
});
const orderDuration = meter.createHistogram("orders.processing_duration_ms", {
description: "Order processing duration in milliseconds",
unit: "ms",
});
async function createOrder(input: CreateOrderInput) {
return tracer.startActiveSpan("createOrder", async (span) => {
try {
span.setAttributes({
"order.customer_id": input.customerId,
"order.item_count": input.items.length,
});
const start = performance.now();
const order = await db.order.create({ data: input });
orderCounter.add(1, { status: "success" });
orderDuration.record(performance.now() - start);
span.setStatus({ code: SpanStatusCode.OK });
return order;
} catch (error) {
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
orderCounter.add(1, { status: "error" });
throw error;
} finally {
span.end();
}
});
}
Prometheus メトリクス
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: "api-servers"
static_configs:
- targets: ["api-1:9090", "api-2:9090"]
metrics_path: /metrics
- job_name: "node-exporter"
static_configs:
- targets: ["node-exporter:9100"]
import { collectDefaultMetrics, Counter, Histogram, Registry } from "prom-client";
const registry = new Registry();
collectDefaultMetrics({ register: registry });
const httpRequestDuration = new Histogram({
name: "http_request_duration_seconds",
help: "HTTP request duration in seconds",
labelNames: ["method", "route", "status"],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
registers: [registry],
});
app.use((req, res, next) => {
const end = httpRequestDuration.startTimer();
res.on("finish", () => {
end({ method: req.method, route: req.route?.path ?? req.path, status: res.statusCode });
});
next();
});
app.get("/metrics", async (req, res) => {
res.set("Content-Type", registry.contentType);
res.end(await registry.metrics());
});
構造化ログ
import pino from "pino";
const logger = pino({
level: process.env.LOG_LEVEL ?? "info",
formatters: {
level: (label) => ({ level: label }),
},
redact: ["req.headers.authorization", "password", "token"],
});
function requestLogger(req, res, next) {
const start = Date.now();
res.on("finish", () => {
logger.info({
method: req.method,
url: req.url,
status: res.statusCode,
duration_ms: Date.now() - start,
trace_id: req.headers["x-trace-id"],
});
});
next();
}
アラートルール
groups:
- name: api-alerts
rules:
- alert: HighErrorRate
expr: rate(http_request_duration_seconds_count{status=~"5.."}[5m]) / rate(http_request_duration_seconds_count[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "Error rate above 5% for {{ $labels.route }}"
- alert: HighLatency
expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 2
for: 10m
labels:
severity: warning
アンチパターン
- マスキングせずに機密データ(パスワード、トークン、PII)をログに記録する
- 構造化フィールドを使わず、ログメッセージに文字列補間を使う
- メトリクスのラベルのカーディナリティを無制限にする(例:ラベルとしてユーザー ID を使う)
- 共通のトレース ID でログとトレースを関連付けない
- 根本原因を把握せず、症状(CPU 使用率の高さ)に対してアラートを設定する
- ダッシュボードを作成する前に SLO を定義しない
チェックリスト
- OpenTelemetry SDK を初期化し、HTTP、DB、メッセージングの自動計装を設定する
- ビジネス上重要な処理にカスタムスパンを追加する
- メトリクスのラベルのカーディナリティを制限する
- JSON 出力とシークレットのマスキングを設定した構造化ログを使用する
- サービス境界をまたいでトレースコンテキストを伝播させる
- SLO(エラー率、レイテンシのパーセンタイル)に基づくアラートルールを設定する
- サービスごとに RED メトリクス(レート、エラー、継続時間)をダッシュボードに表示する
- ログの保持ポリシーとローテーションポリシーを設定する
コメント
Markdown 対応