はじめに
前回
シリーズ最終回は プロダクション運用での観測性 (Observability) を総仕上げする。
開発フェーズでは型安全にロギング/メトリクスを定義できても、集約・検索・ダッシュボードまで一貫して整合性を保つのは難しい。本稿では TypeScript の型システムを活用して コード → 収集 → 可視化 をエンドツーエンドで統合する手法を解説する。
1. ゴール設定
- 構造化ログ と メトリクス を同一スキーマで生成
- CI でスキーマ破壊を検知し、本番ダッシュボードを壊さない
- OpenTelemetry (OTel) を基盤に、Grafana / Prometheus / Loki へ安全に流し込む
2. アーキテクチャ全体像
-
型安全 Logger / Meter: シリーズ第16回で定義した
log()
meter
を再利用。 - Collector: OTLP 受信 → Prometheus / Loki / Elastic にエクスポート。
3. スキーマレジストリをコードで定義
import { z } from "zod";
export const LogSchemas = {
USER_LOGIN: z.object({ userId: z.number(), method: z.enum(["oauth", "password"]) }),
API_LATENCY: z.object({ path: z.string(), ms: z.number() }),
} as const;
export type LogKey = keyof typeof LogSchemas;
- 単一ファイルで log schema を集中管理 し、Collector とダッシュボードと共有。
3.1 Grafana Dashboard JSON 型生成
import fs from "fs";
function toGrafanaField(key: LogKey) {
const fields = Object.entries(LogSchemas[key].shape);
return fields.map(([name, schema]) => ({
name,
type: schema._def.typeName, // "ZodNumber" など
}));
}
fs.writeFileSync(
"dashboards/fields.json",
JSON.stringify(toGrafanaField("API_LATENCY"), null, 2)
);
- 型からダッシュボード設定ファイルを自動生成し、ヒューマンエラーを排除。
4. メトリクス Enum と Counter 定義
export const MetricKeys = {
API_LATENCY_MS: "api_latency_ms",
QUEUE_DEPTH: "queue_depth",
} as const;
type MetricKey = typeof MetricKeys[keyof typeof MetricKeys];
const meter = metrics.getMeter("ts-oni");
export const counters: Record<MetricKey, ReturnType<typeof meter.createHistogram>> = {
api_latency_ms: meter.createHistogram("api_latency_ms", {
description: "API latency in milliseconds",
unit: "ms",
}),
queue_depth: meter.createHistogram("queue_depth"),
};
- Enum → Prometheus metric 名 を固定し、スペルミスをコンパイル時に検出。
5. CI でスキーマ破壊を検知
# .github/workflows/validate-log-schema.yml
name: validate-log-schema
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run generate-dashboard
- uses: grafana/verify-dashboards-action@v1
with:
dashboards_path: dashboards
- スキーマ更新で自動生成された dashboard JSON を差分検証し、破壊的変更を防ぐ。
6. 収集・クエリ時の型保証
6.1 Loki LogQL テンプレート
type ApiLatencyLog = z.infer<typeof LogSchemas["API_LATENCY"]> & { ts: string };
function queryLatency(path: string) {
const q = `{event="API_LATENCY", path="${path}"}`;
return client.range<ApiLatencyLog>(q, "5m");
}
- LogQL クエリ文字列を テンプレート文字列タグ関数 で型安全に生成。
7. 落とし穴と対策
落とし穴 | 原因 | 対策 |
---|---|---|
ログキー乱立 | チーム間で定義散逸 | スキーマ単一ファイル + ESLint で import 統一 |
メトリクス名後から変更 | ダッシュボード壊れる | Enum 固定 + GitHub Action で破壊検知 |
Collector 設定肥大化 | Manual YAML | Helm chart に値を注入し CI でテンプレート化 |
まとめ
- スキーマレジストリを TypeScript 型で宣言 → ログ・メトリクス・ダッシュボードを自動生成。
- OpenTelemetry Collector で コードと運用基盤を接続、型安全にクエリ可能。
- CI でスキーマ破壊を検知し、運用フェーズでの事故を最小化。
シリーズ完結編として、ここまで解説した型安全パターンを組み合わせれば 開発・テスト・運用のすべてを TypeScript 型でガード できる。
長編連載をお読みいただきありがとうございました。さらなる深掘りが必要であれば、続編として書いていきたいなと思ってます。