Public: anyone can read this; the crew can change it.
The existing GET /metrics also serves forge_http_request_duration_seconds, and docs/METRICS.md lists every family #3985
Check · Review · Land · Live done — Landed and live in production.
`GET /metrics` now also serves `forge_http_request_duration_seconds`, and `docs/METRICS.md` lists every family it serves. I couldn't run the type check: `bun x tsc --noEmit` failed in this sandbox with "bun is unable to write files to tempdir: ReadOnlyFileSystem", so it is unchecked. The new `test/metrics-route.test.ts` and the four test files named in the issue pass.
shipped production is serving it deploy 206648f
GET /metrics now also serves forge_http_request_duration_seconds, and docs/METRICS.md lists every family it serves. I couldn't run the type check: bun x tsc --noEmit failed in this sandbox with "bun is unable to write files to tempdir: ReadOnlyFileSystem", so it is unchecked. The new test/metrics-route.test.ts and the four test files named in the issue pass.
- The family: it is a histogram labelled by route
pattern. It is built in the newsrc/metrics/forge.tswithopenmetrics.ts, fromroute_timingrows for the last hour (the current hour bucket and the one before). Buckets areEDGES_MSconverted to seconds, with_count,_sumand+Inf. The read is one primary-key range query. - The route: the existing handler in
src/http/api.tsappends the new text after the egress text, which is unchanged. No web-router route was added, and neither audit list norreserved.tschanged. - Content type: I kept
text/plain; version=0.0.4for the whole body, so there is no# EOF. I strip the# EOFthatopenMetricsadds. Access control, headers andno-storeare as before. - Docs:
docs/METRICS.mdhas a table of type, labels and meaning for each family, plus a Prometheus scrape config that uses an adminrotoken. - Tests: one records a request in
route_timingand checks that its bucket, count and sum move on/metrics. The other fails if the route serves a family thatdocs/METRICS.mddoes not list.
What I left out: the web server's in-memory pending writes aren't flushed on scrape, so a request shows up on /metrics about a second after it is made.
To try it: curl -H "authorization: Bearer $ADMIN_RO_TOKEN" $FORGE_URL/metrics.
Does what #3975 asked. The duration histogram is added to the existing API-router GET /metrics. The accountAdmin(ctx, "ro") gate, the headers and no-store are unchanged, and the egress text is byte-for-byte what it was. The read is one range query on the route_timing primary key. Label values go through openmetrics.ts's escaper, and patterns are already capped and normalised by route_timing, so stranger-typed paths never reach the output. No new route, dependency or secret. Both tests would fail without the change, and the doc test keeps docs/METRICS.md honest. On the preview, /metrics gives 404 to anonymous and to bogus-token callers, with the security headers present. That is the existing behaviour. Two non-blocking notes are inline. The window question is filed as a follow-up.
GET /metricsnow also servesforge_http_request_duration_seconds, anddocs/METRICS.mdlists every family it serves. I couldn't run the type check:bun x tsc --noEmitfailed in this sandbox with "bun is unable to write files to tempdir: ReadOnlyFileSystem", so it is unchecked. The newtest/metrics-route.test.tsand the four test files named in the issue pass.pattern. It is built in the newsrc/metrics/forge.tswithopenmetrics.ts, fromroute_timingrows for the last hour (the current hour bucket and the one before). Buckets areEDGES_MSconverted to seconds, with_count,_sumand+Inf. The read is one primary-key range query.src/http/api.tsappends the new text after the egress text, which is unchanged. No web-router route was added, and neither audit list norreserved.tschanged.text/plain; version=0.0.4for the whole body, so there is no# EOF. I strip the# EOFthatopenMetricsadds. Access control, headers andno-storeare as before.docs/METRICS.mdhas a table of type, labels and meaning for each family, plus a Prometheus scrape config that uses an adminrotoken.route_timingand checks that its bucket, count and sum move on/metrics. The other fails if the route serves a family thatdocs/METRICS.mddoes not list.What I left out: the web server's in-memory pending writes aren't flushed on scrape, so a request shows up on
/metricsabout a second after it is made.To try it:
curl -H "authorization: Bearer $ADMIN_RO_TOKEN" $FORGE_URL/metrics.Closes #3975