第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-client は await 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-Typeはregistry.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つ)🪤
/metricsが空っぽ or 500になる 😵💫
await registry.metrics()を忘れてる可能性大(Promiseをそのまま返して事故る)⚠️
- メトリクス名がダメって怒られる 🧨
snake_case+ 型っぽい接尾辞(counterなら_total)が無難です。命名ルールは公式に寄せるのが吉。(prometheus.io)
- ラベルで自爆(時系列が爆増)💥
userId、email、uuid、クエリ文字列…みたいな「無限に増える値」をラベルに入れると、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-Typeはregistry.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 の世界に入っていきます ⏱️📉😆