Skip to content

P1: Expose Prometheus metrics at /metrics (#173) - #191

Open
dkijania wants to merge 1 commit into
mainfrom
feat/metrics
Open

P1: Expose Prometheus metrics at /metrics (#173)#191
dkijania wants to merge 1 commit into
mainfrom
feat/metrics

Conversation

@dkijania

Copy link
Copy Markdown
Contributor

What & why

Part of the production-readiness epic (#163). Refs #173.

There was no metrics endpoint — only optional Jaeger tracing — so request rate, errors, latency, and process health were invisible in production.

Changes

  • New /metrics endpoint (prom-client) via a Yoga plugin.
  • RED HTTP metrics: http_requests_total{method,route,status}, http_request_duration_seconds histogram, http_requests_in_flight gauge.
  • Node process metrics: CPU, memory, event loop lag, GC.
  • Route labels normalised to a known set (/, /healthcheck, /readiness, /metrics; anything else → other) to bound label cardinality; the /metrics scrape is not self-counted.

New dependency: prom-client.

On DB pool saturation

The issue also lists DB pool-saturation gauges. postgres.js exposes no pool-introspection API, so those need a query-instrumentation pass rather than a simple read of pool state. I scoped that out of this PR and left #173 open for it (this PR is Refs, not Closes) — happy to follow up with an instrumentation approach if you want it.

Testing

  • npm run build — clean
  • npm run test:unit — all pass; tests cover the exposition format, per-route/status counting (asserts a request count of 2), and exclusion of the scrape endpoint
  • npm run lint / npx prettier --debug-check . — clean

🤖 Generated with Claude Code

There was no metrics endpoint — only optional Jaeger tracing — so request rate,
errors, latency, and process health were invisible in production.

Add a prom-client registry served at `/metrics`:

- RED HTTP metrics via a Yoga plugin: `http_requests_total{method,route,status}`,
  `http_request_duration_seconds` histogram, and `http_requests_in_flight`.
- Standard Node process metrics (CPU, memory, event loop, GC).
- Route labels are normalised to a known set (`other` otherwise) to bound
  cardinality; the `/metrics` scrape is not self-counted.

DB pool-saturation gauges are intentionally out of scope here: postgres.js
exposes no pool-introspection API, so that needs a query-instrumentation pass —
tracked as a follow-up.

Unit tests cover the exposition format, per-route/status counting, and that the
scrape endpoint is excluded.

Refs #173.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSuak9smCHbp4N17xjjLF6
@dkijania dkijania added production-readiness Work toward making the API production-ready / publicly available P1 Strongly recommended before GA labels Jun 28, 2026
@SanabriaRusso

Copy link
Copy Markdown
Collaborator

Thanks for this — a /metrics endpoint with route normalized to a known set and the scrape self-excluded is exactly the right shape, and bounding label cardinality up front is great to see. Two small, non-blocking operational notes:

1. /metrics is always-on and unauthenticated on the public port. plugins.push(useMetrics()) is unconditional, so /metrics is reachable by anyone who can hit the GraphQL endpoint on :8080 — which is public (CORS defaults to * for the mina-explorer browser client). The RED metrics are safe aggregates, but collectDefaultMetrics also publishes nodejs_version_info, process start time, and memory/GC shape. Every other optional surface here is env-gated (ENABLE_GRAPHIQL, ENABLE_INTROSPECTION, ENABLE_LOGGING), so it'd be consistent to gate this too and/or document the boundary (#173 also asks to "document scrape config"):

if (process.env.ENABLE_METRICS === 'true') plugins.push(useMetrics());

Docs note: /metrics shares the API port and is unauthenticated — restrict it to your Prometheus scrapers at the ingress/LB.

2. Heads-up for #185 (rate limiting): its limiter currently exempts only /healthcheck. Since Prometheus scrapes /metrics on a fixed interval, it's worth adding /metrics to that exemption list. Note both PRs insert their plugin at the top of buildPlugins(), so expect a conflict there — resolve it so the plugin that short-circuits first is intentional.

And +1 on leaving #173 open for the DB-pool gauges — postgres.js has no pool introspection, so that genuinely needs a separate instrumentation pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P1 Strongly recommended before GA production-readiness Work toward making the API production-ready / publicly available

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants