Background
Lens already has real observability: src/metrics.ts exports seven Prometheus metrics via a public /metrics route, and /status reports lastProcessedAt. That part works and is not in question.
What is missing is the HTTP layer. Nothing anywhere in Lens exports:
- request rate
- error rate — nothing counts 5xx at all
- p95 latency (
db_query_duration_seconds covers the database, not the request)
price_requests_total counts price calls specifically, not requests overall, so it cannot answer "is the API healthy".
This is called out as the largest gap in veil's docs/MONITORING.md, which is an SCF #46 tranche-2 deliverable. Right now that document has to mark error rate and p95 as ❌ nowhere — because listing a metric nothing emits would be worse than admitting the hole.
The wraith half is tracked separately in wraith#39. This issue is Lens only.
Why it is not a five-line middleware
The naive version — one histogram labelled by req.url — produces unbounded label cardinality (every address, every pair, every cursor becomes its own time series) and will eventually take down the Prometheus scraping it. Getting the label set right is the work.
What to build
A Fastify hook exporting standard HTTP metrics on the existing registry.
Key files
src/metrics.ts — the registry and existing metrics
src/index.ts — plugin registration order and the /metrics route
src/api/rest.ts — routes, and where price_requests_total is incremented today
Suggested execution
- Add
http_requests_total{method, route, status_class} and http_request_duration_seconds{method, route}.
- Label by route template (
/price/:assetA/:assetB), never the resolved URL. Fastify exposes the matched route — use it, and fall back to a literal "unmatched" rather than the raw path for 404s.
- Keep
status_class (2xx/4xx/5xx) rather than the exact code, to bound cardinality; add exact status only if a concrete need appears.
- Choose histogram buckets deliberately for this service and say why in a comment — the defaults are tuned for fast in-process calls, and these handlers make network and DB round-trips.
- Make sure the hook runs for error responses too. A 500 that escapes the counter is the exact case this exists to catch.
- Leave
price_requests_total alone — it means something different, and dashboards may use it.
Acceptance criteria
Notes
- Registration order matters: the hook must sit outside the x402 and auth plugins, or payment-rejected and unauthorised requests go uncounted.
/metrics itself should not be counted, or the scraper inflates its own numbers.
Drips Wave · Complexity: Advanced · 200 points
Required: Before submitting, join the contributor Telegram so your work can be tracked and counted toward the Stellar Wave: https://t.me/+fxHXq8f1SwlkZDBk
Background
Lens already has real observability:
src/metrics.tsexports seven Prometheus metrics via a public/metricsroute, and/statusreportslastProcessedAt. That part works and is not in question.What is missing is the HTTP layer. Nothing anywhere in Lens exports:
db_query_duration_secondscovers the database, not the request)price_requests_totalcounts price calls specifically, not requests overall, so it cannot answer "is the API healthy".This is called out as the largest gap in veil's
docs/MONITORING.md, which is an SCF #46 tranche-2 deliverable. Right now that document has to mark error rate and p95 as ❌ nowhere — because listing a metric nothing emits would be worse than admitting the hole.The wraith half is tracked separately in wraith#39. This issue is Lens only.
Why it is not a five-line middleware
The naive version — one histogram labelled by
req.url— produces unbounded label cardinality (every address, every pair, every cursor becomes its own time series) and will eventually take down the Prometheus scraping it. Getting the label set right is the work.What to build
A Fastify hook exporting standard HTTP metrics on the existing registry.
Key files
src/metrics.ts— the registry and existing metricssrc/index.ts— plugin registration order and the/metricsroutesrc/api/rest.ts— routes, and whereprice_requests_totalis incremented todaySuggested execution
http_requests_total{method, route, status_class}andhttp_request_duration_seconds{method, route}./price/:assetA/:assetB), never the resolved URL. Fastify exposes the matched route — use it, and fall back to a literal"unmatched"rather than the raw path for 404s.status_class(2xx/4xx/5xx) rather than the exact code, to bound cardinality; add exact status only if a concrete need appears.price_requests_totalalone — it means something different, and dashboards may use it.Acceptance criteria
http_requests_totalandhttp_request_duration_secondsare exported on/metricsdocs/) lists what is exported and what an alert on it should look like, soMONITORING.mdcan be updated to name real metricsNotes
/metricsitself should not be counted, or the scraper inflates its own numbers.Required: Before submitting, join the contributor Telegram so your work can be tracked and counted toward the Stellar Wave: https://t.me/+fxHXq8f1SwlkZDBk