メインコンテンツまでスキップ

第16章:/metrics を生やす:最小のprom-client 🌱📏

① 今日のゴール 🎯

  • GET /metrics にアクセスすると Prometheus形式のテキストが返るようになる 📄✨
  • まずは最小でOK:「HTTPリクエスト数」カウンタを1つだけ作って増やす 🔢⬆️
  • 「メトリクスは 押し込む んじゃなくて、取りに来てもらう(pull)」の感覚をつかむ 🧲👣 Prometheus系は「アプリがHTTPエンドポイントでメトリクスを公開する」スタイルが基本です。(prometheus.io)

② 図(1枚)🖼️

      (ブラウザ / curl)                (後でPrometheusがやること)
│ GET /metrics │ 定期的にGET /metrics
▼ ▼
┌────────────────┐ ┌────────────────┐
│ Node API │ │ Prometheus │
│ + prom-client │<── scrape ───── │ (時系列DB) │
└────────────────┘ └────────────────┘

└─ "text/plain; version=0.0.4" みたいな形式で返す 📄

prom-clientawait registry.metrics() の結果を返せばOK、という方針です。(GitHub) また、Prometheusは Accept ヘッダでフォーマットをやり取りする前提があるので、Content-Typeを正しく返すのが大事です。(prometheus.io)


③ 手を動かす(手順 5〜10個)🛠️

ステップ0:今回使うライブラリの“最新”メモ 📝

  • prom-client の最新リリースは **v15.1.3(2024-06-27)**として案内されています(npm/GitHub)。(GitHub)
  • Node.jsは本日時点で、v24 がActive LTS、v25 がCurrentの並びです(例:v24.13.1 / v25.6.1)。(nodejs.org)

バージョンを暗記するより、「公式のやり方どおりに /metrics を返せる」ことが勝ちです 💪✨


ステップ1:prom-client を追加する 📦

PowerShell(またはVS Codeターミナル)で:

npm i prom-client

ステップ2:メトリクス専用ファイルを作る(分けるのがコツ)🧩

ファイル構成イメージ:

.
├─ src
│ ├─ server.ts
│ └─ metrics.ts ← 追加!
└─ package.json

src/metrics.ts(最小の “カウンタ1個” ):

import { Counter, Registry } from "prom-client";

// ここでは「専用のRegistry」を使います(グローバル汚さない作戦✨)
export const registry = new Registry();

// リクエスト総数カウンタ(Counterは増えるだけ⬆️)
export const httpRequestsTotal = new Counter({
name: "http_requests_total",
help: "Total number of HTTP requests",
labelNames: ["method", "route", "status"],
registers: [registry],
});
  • Counterは「総数」なので _total を付けるのが定番です 👍(命名の話は公式の推奨が強いです)(prometheus.io)
  • ラベルは便利だけど、増やしすぎると地獄になるので注意(例:userId を入れるのは危険)⚠️🔥(prometheus.io)

ステップ3:/metrics エンドポイントを生やす 🌱

src/server.ts に追記(Express想定の最小例):

import express from "express";
import { httpRequestsTotal, registry } from "./metrics";

const app = express();

app.get("/ping", (req, res) => {
res.status(200).json({ ok: true });

httpRequestsTotal.inc({
method: req.method,
route: "/ping",
status: String(res.statusCode),
});
});

app.get("/slow", async (req, res) => {
await new Promise((r) => setTimeout(r, 800));
res.status(200).json({ ok: true, slow: true });

httpRequestsTotal.inc({
method: req.method,
route: "/slow",
status: String(res.statusCode),
});
});

// ★ここが本題:Prometheusが見に来る出口
app.get("/metrics", async (_req, res) => {
res.setHeader("Content-Type", registry.contentType);
res.end(await registry.metrics());
});

app.listen(3000, () => {
console.log("Listening on http://localhost:3000");
});

ポイントはここ👇

  • registry.metrics()await して返す(今どきはasync前提で書くのが安全)(GitHub)
  • Content-Typeregistry.contentType を使って正しく返す(交渉の前提がある)(prometheus.io)

ステップ4:Dockerで動かして /metrics を見に行く 🐳👀

(すでにComposeで起動できる前提で)いつも通り:

docker compose up --build

別ターミナルで確認:

curl.exe http://localhost:3000/metrics

ステップ5:カウンタが増えるのを確認する 🔢⬆️

まず /ping を何回か叩く:

curl.exe http://localhost:3000/ping
curl.exe http://localhost:3000/ping
curl.exe http://localhost:3000/slow

それから /metrics を絞って見る(PowerShellで便利):

curl.exe http://localhost:3000/metrics | Select-String http_requests_total

出力イメージ(だいたいこんな感じ):

## HELP http_requests_total Total number of HTTP requests
## TYPE http_requests_total counter
http_requests_total{method="GET",route="/ping",status="200"} 2
http_requests_total{method="GET",route="/slow",status="200"} 1

④ つまづきポイント(3つ)🪤

  1. /metrics が空っぽ or 500になる 😵‍💫
  • await registry.metrics() を忘れてる可能性大(Promiseをそのまま返して事故る)⚠️
  1. メトリクス名がダメって怒られる 🧨
  • snake_case + 型っぽい接尾辞(counterなら _total)が無難です。命名ルールは公式に寄せるのが吉。(prometheus.io)
  1. ラベルで自爆(時系列が爆増)💥
  • userIdemailuuid、クエリ文字列…みたいな「無限に増える値」をラベルに入れると、Prometheusがつらいです。(prometheus.io) まずは method/route/status くらいで十分!🙆‍♂️

⑤ ミニ課題(15分)⏳✨

「/metrics は“計測対象”に含めない」版を作ってみよう 😈➡️😇

  • 目的:Prometheusが定期的に /metrics を叩くと、そのアクセスまでカウントされて紛らわしい…を防ぐ
  • やること:/metrics では httpRequestsTotal.inc(...) しない(今のコードはすでにそうなってます🙆‍♂️)
  • 追加課題:/boom があるなら status="500" が増えるのも確認(次章の「エラー率」に繋がる)🔥

⑥ AIに投げるプロンプト例(コピペOK)🤖📋

  • 「Express + TypeScript で、prom-client を使って /metrics を実装して。Registry を分離して、Content-Typeregistry.contentType を返すこと。さらに http_requests_total{method,route,status}/ping/slow で増やして。」
  • 「Prometheusのラベルの“高カーディナリティ問題”を、初心者向けに具体例つきで説明して。ダメな例とOKな例を3つずつ。」(prometheus.io)
  • 「次章でヒストグラムを入れたい。今回の metrics.ts を、後で request_duration_seconds を追加しやすい形にリファクタ案を出して。」

✅ 章のチェック(合格ライン)🎓

  • /metrics にアクセスすると テキスト形式のメトリクスが返る
  • /ping/slow を叩くと http_requests_totalちゃんと増える
  • なんとなく「Prometheusが取りに来るための出口を作った」感覚がある 🧲✨

次の第17章では、この出口に “レスポンス時間(ヒストグラム)” を流し込んで、p95/p99 の世界に入っていきます ⏱️📉😆