1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TSの鬼 第20回(最終回)運用を極める型安全ロギング & メトリクス実践

Posted at

はじめに

前回

シリーズ最終回は プロダクション運用での観測性 (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 型でガード できる。

長編連載をお読みいただきありがとうございました。さらなる深掘りが必要であれば、続編として書いていきたいなと思ってます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?