← Recipes

阅读中文版本

Export Prometheus Metrics with a Default Registry

Register a counter and a histogram with the default registry, instrument the request path, and expose /metrics in the text format Prometheus scrapes.

~25 minprometheusmetricsobservability

Task

Expose a /metrics endpoint that Prometheus can scrape, with a request counter and a latency histogram that have stable labels.

When to use this

The team wants a request rate and a latency percentile on the dashboard, but the app has no metrics endpoint. They need one that Prometheus scrapes without extra glue.

Prerequisites

  • Node.js 20 LTS with prom-client installed
  • An HTTP server that can add a /metrics route
  • A Prometheus scrape config pointing at the app port

Steps

1. Register a counter for requests and a histogram for latency with method and route labels.

const requests = new Counter({ name: "http_requests_total", help: "Total requests", labelNames: ["method", "route"] });
const latency = new Histogram({ name: "http_request_duration_seconds", help: "Latency", labelNames: ["method", "route"], buckets: [0.01, 0.1, 1] });

Expected output: The metrics appear in the default registry with the declared names and labels.

2. Instrument the request path to observe the counter and the histogram on every request.

const end = latency.startTimer();
requests.inc({ method, route });
res.on("finish", () => end());

Expected output: A request increments the counter and records a latency sample in the histogram bucket.

3. Expose /metrics in the text format on the app port.

app.get("/metrics", async (req, res) => res.set("Content-Type", register.contentType).end(await register.metrics()));

Expected output: A GET /metrics returns the Prometheus text format with the counter and histogram series.

Troubleshooting

Problem

Cardinality explodes because route includes the id

Solution

Use a low-cardinality route label like /users/:id, not the raw path; a unique label per request makes Prometheus unusable.

Problem

The scrape times out

Solution

Collecting metrics is single-threaded; if the registry is huge, move expensive collectors to a separate registry and scrape it less often.

Limitations

  • The default registry is process-global; a long-running process accumulates series that must be reset on restart.
  • Histogram buckets are fixed at registration; changing them requires a redeploy.

Register, instrument, expose

Register a counter and a histogram on the default registry, instrument the request path, and expose /metrics in the text format. Prometheus scrapes the endpoint on its interval.

const requests = new Counter({ name: 'http_requests_total', help: 'Total requests', labelNames: ['method', 'route'] });
const latency = new Histogram({ name: 'http_request_duration_seconds', help: 'Latency', labelNames: ['method', 'route'], buckets: [0.01, 0.1, 1] });

Keep labels low-cardinality

Use a templated route like /users/:id as the label, not the raw path. A unique label per request makes the series count explode and exhausts Prometheus memory.

app.get('/metrics', async (req, res) => res.set('Content-Type', register.contentType).end(await register.metrics()));

Expected outcome

A GET /metrics returns the text format with the counter and histogram series, the dashboard shows a request rate and a latency percentile, and the label cardinality stays bounded. Scrape the endpoint on a fixed interval and alert on the rate and the percentile.