暴露应用指标与 RED 方法
以 Prometheus 格式发布速率、错误和耗时指标,并定义有意义的服务级指标。
暴露应用指标与 RED 方法 是 CoddyKit 上的免费 Node.js Backend Development Bootcamp 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Node.js Backend Development Bootcamp 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Node.js Backend Development Bootcamp 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
Why Metrics Matter
Logs tell you what happened in a single request. Metrics tell you how your whole service is behaving right now and over time. They are cheap to store, aggregate well, and power dashboards and alerts.
A metric is a numeric measurement sampled over time. In a Node.js backend you typically export metrics in Prometheus text format over an HTTP endpoint (conventionally /metrics). Prometheus scrapes that endpoint every few seconds and stores the time series.
- Counter — only goes up (total requests, total errors).
- Gauge — goes up and down (active connections, queue depth).
- Histogram — buckets observations (request durations) to compute quantiles.
The Prometheus Text Format
Before wiring up libraries, it helps to see what the /metrics endpoint actually returns. Each line is metric_name{label="value"} number. The # HELP and # TYPE comment lines describe each metric.
Labels let one metric name carry many dimensions (per route, per status code). Below is a tiny generator that builds this exposition format by hand so you can see exactly what Prometheus parses.
function renderMetrics(samples) {
const lines = [
'# HELP http_requests_total Total HTTP requests',
'# TYPE http_requests_total counter',
];
for (const s of samples) {
const labels = Object.entries(s.labels)
.map(([k, v]) => k + '="' + v + '"')
.join(',');
lines.push('http_requests_total{' + labels + '} ' + s.value);
}
return lines.join('\n');
}
const out = renderMetrics([
{ labels: { method: 'GET', route: '/users', status: '200' }, value: 1024 },
{ labels: { method: 'GET', route: '/users', status: '500' }, value: 7 },
]);
console.log(out);Introducing the RED Method
The RED method is a focused recipe for monitoring request-driven services. For every service you track three signals:
- R — Rate: requests per second the service is handling.
- E — Errors: rate of failed requests (typically HTTP 5xx, sometimes 4xx).
- D — Duration: distribution of how long requests take (latency).
RED is the request-side complement to USE (Utilization, Saturation, Errors) which targets resources like CPU and disk. For an HTTP API, RED maps almost perfectly onto user-facing experience: throughput, failure rate, and latency.
The elegant part: all three derive from instrumenting just one thing — the request lifecycle.
Installing prom-client
In Node.js the de-facto library is prom-client. It manages a registry of metrics and renders them in Prometheus format. It also ships default metrics (event loop lag, heap usage, GC) that you enable with one call.
Create a single shared registry so every part of your app registers into the same place.
const client = require('prom-client');
const register = new client.Registry();
register.setDefaultLabels({ app: 'orders-api' });
// Node.js runtime metrics: event loop lag, heap, GC, fd count...
client.collectDefaultMetrics({ register });
module.exports = { client, register };R — A Counter for Request Rate
Rate is not stored directly. You store a monotonic counter of total requests, and Prometheus computes the per-second rate at query time with rate(http_requests_total[1m]).
Give the counter labels for method, route, and status_code. Crucially, use the route template (/users/:id) and not the raw URL (/users/42) — otherwise every id becomes a new time series and your cardinality explodes.
const client = require('prom-client');
const httpRequestsTotal = new client.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code'],
});
// On each finished request:
httpRequestsTotal.inc({
method: 'GET',
route: '/users/:id', // template, NOT /users/42
status_code: '200',
});D — A Histogram for Duration
Duration needs a histogram so you can compute percentiles (p50, p95, p99). A histogram counts observations into predefined buckets (upper bounds in seconds). Choose buckets that span your expected latency range.
A histogram automatically exposes three series per label set: _bucket, _sum, and _count. From _sum and _count you also get the average; from _bucket you estimate quantiles with histogram_quantile().
const client = require('prom-client');
const httpRequestDuration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration in seconds',
labelNames: ['method', 'route', 'status_code'],
// Tuned for a typical web API (5ms .. 5s)
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
});
// Observe a completed request that took 0.137s
httpRequestDuration.observe(
{ method: 'GET', route: '/users/:id', status_code: '200' },
0.137
);One Middleware to Capture R, E, and D
The whole point of RED is that a single piece of instrumentation feeds all three signals. An Express-style middleware starts a timer, lets the request run, then on res.finish records the count and duration with the status code as a label.
Errors need no separate metric — they are just the counter filtered by status_code=~"5.." at query time. This is framework code (Express), so treat it as a reference pattern rather than a standalone program.
function metricsMiddleware(req, res, next) {
const end = httpRequestDuration.startTimer({ method: req.method });
res.on('finish', () => {
// req.route?.path gives the template, e.g. '/users/:id'
const route = (req.route && req.route.path) || req.path;
const labels = {
method: req.method,
route,
status_code: String(res.statusCode),
};
httpRequestsTotal.inc(labels);
end(labels); // stops timer and records duration with these labels
});
next();
}
app.use(metricsMiddleware);Exposing the /metrics Endpoint
Prometheus pulls metrics, so you must expose them over HTTP. Add a GET /metrics route that returns register.metrics() with the correct content type. This is the endpoint you list as a scrape target in prometheus.yml.
Keep this endpoint internal (bind to a private interface or protect it) — it can leak operational detail and is a tempting DoS target.
const { register } = require('./metrics');
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
// prometheus.yml scrape config:
// scrape_configs:
// - job_name: 'orders-api'
// scrape_interval: 15s
// static_configs:
// - targets: ['orders-api:3000']Querying RED with PromQL
Once data is flowing, you express each RED signal as a PromQL query for dashboards and alerts:
- Rate:
sum(rate(http_requests_total[5m])) by (route) - Errors (ratio):
sum(rate(http_requests_total{status_code=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) - Duration (p95):
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
Note the error ratio is a fraction of total requests, not a raw count. An error rate of 0.02 means 2% of requests failed — far more meaningful than "40 errors" with no denominator.
From Metrics to SLIs and SLOs
A Service Level Indicator (SLI) is a precisely defined measure of service health, usually expressed as good events / valid events. RED metrics are exactly the raw material for SLIs:
- Availability SLI: non-5xx responses / total responses.
- Latency SLI: requests faster than 300ms / total requests.
A Service Level Objective (SLO) is a target for an SLI over a window, e.g. "99.9% of requests succeed over 30 days." The complement of the SLO is your error budget (0.1%), which tells you how much failure you can tolerate before you must stop shipping features and fix reliability.
Computing a Latency SLI From Buckets
A latency SLI like "fraction of requests under 300ms" comes straight from histogram bucket counts: take the cumulative count in the le="0.3" bucket and divide by the total count. Here is a standalone simulation of that computation over two scrape samples.
// Cumulative bucket counts from a histogram, le = upper bound (seconds)
const sample = {
total: 10000,
buckets: {
'0.1': 6000,
'0.3': 9700, // 9700 requests finished within 300ms
'1.0': 9990,
'+Inf': 10000,
},
};
function latencySLI(s, thresholdLabel) {
const good = s.buckets[thresholdLabel];
return good / s.total;
}
const sli = latencySLI(sample, '0.3');
console.log('Latency SLI (<300ms): ' + (sli * 100).toFixed(2) + '%');
console.log('Meets 99% SLO? ' + (sli >= 0.99));Quick Check
Test your understanding of cardinality and the RED method.
Recap
You can now expose meaningful application metrics in Prometheus format and reason about them with the RED method.
- Counter, Gauge, Histogram are the core metric types;
prom-clientmanages a registry and renders the exposition format. - RED = Rate, Errors, Duration — all three fall out of instrumenting the request lifecycle once, in a single middleware.
- Rate comes from a counter via
rate(); Errors are that counter filtered bystatus_code=~"5.."; Duration comes from a histogram viahistogram_quantile(). - Always label by route template, never raw URLs, to control cardinality.
- Expose metrics at
/metricsand let Prometheus scrape it; keep the endpoint internal. - Turn RED metrics into SLIs (good/valid events), set SLOs, and track your error budget.
常见问题解答
「暴露应用指标与 RED 方法」课时是免费的吗?
是的 — 「暴露应用指标与 RED 方法」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Node.js Backend Development Bootcamp 课程的其余内容,请升级到 CoddyKit PRO。 Node.js Backend Development Bootcamp 课程共包含 4 节课。
「暴露应用指标与 RED 方法」这节课中我会学到什么?
以 Prometheus 格式发布速率、错误和耗时指标,并定义有意义的服务级指标。 你通过在浏览器中直接运行的动手代码来练习 Node.js Backend Development Bootcamp,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Node.js Backend Development Bootcamp 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Node.js Backend Development Bootcamp 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「暴露应用指标与 RED 方法」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Node.js Backend Development Bootcamp 课中编写并运行代码吗?
能。每节 Node.js Backend Development Bootcamp 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。