Skip to content

feat(metrics): count provider calls and record latency by endpoint and outcome - #342

Merged
Miracle656 merged 1 commit into
Miracle656:mainfrom
kosisochukwu1234:feat/linq-call-metrics
Oct 26, 2026
Merged

Miracle656 merged 1 commit into
Miracle656:mainfrom
kosisochukwu1234:feat/linq-call-metrics

Conversation

@kosisochukwu1234

Copy link
Copy Markdown
Contributor

Context

Every naira operation across the rails (offramp, onramp, and bills) passes through src/linq/http.ts as the single transport to Linq. Previously, provider call success/failure counts were not tracked, and provider outages were invisible until user-reported.

Changes

  • provider_calls_total: Added Prometheus counter labeled by bounded endpoint and outcome (success, 4xx, 5xx, 429, timeout).
  • Single transport instrumentation: Increments provider_calls_total directly inside linqCall's finally block so no rail can omit it.
  • Outcome classification: Distinguishes client 4xx, provider 5xx, rate-limiting (429), timeouts, and successes.
  • Bounded cardinality: Paths are stripped of query parameters (e.g., /b2b/status?id=... -> /b2b/status) ensuring clean, low-cardinality label sets.
  • Latency observation on failures: Latency histograms record duration across all outcome classifications.
  • Tests: Added unit tests in src/__tests__/linq/providerCalls.test.ts asserting counter increments across all outcome categories against the fake provider.

Closes #269

…d outcome

Counts payment provider calls in linqCall by endpoint path and outcome
so no rail can forget to record them. Distinguishes success, 4xx, 5xx,
429, and timeout outcomes. Preserves bounded cardinality by normalizing
endpoint paths and stripping query parameters. Observes latency across
all failure outcomes as well as successes.
@Miracle656
Miracle656 merged commit e3b71fe into Miracle656:main Oct 26, 2026
4 checks passed
@Miracle656

Copy link
Copy Markdown
Owner

Merged into main (1980eb9).

The outcome classification is the valuable part. The existing provider_request_duration_seconds labels outcome as only ok / error / timeout, so a malformed request we sent, a provider outage, and a rate limit all landed in one error bucket. Your classifyFailureOutcome splits them:

outcome whose problem
4xx ours — we sent something the provider rejected
5xx theirs — the provider failed
429 backpressure — we are at the shared key's ceiling
timeout the network, or them, too slowly to tell

Those are four different pages at 3am and they were previously indistinguishable. Worth having.

Stripping the query string before labelling is also the right instinct, and more important than it looks: customerRef and order ids travel as query parameters on these calls, so leaving the query in would have put caller-identifying values into /metrics, which is both unbounded and a disclosure.

One change on merge. normalizeEndpointPath's comment said it produced "a bounded endpoint path label", but it only removed the query — path segments passed through untouched. That is true-by-accident today, because every Linq path is a static literal (/b2b/onramp, /b2b/bills/status, /onramprate). It stops being true the first time someone adds /b2b/onramp/status/${orderId}, and it fails silently: one series per order, growing until the scrape times out. An unbounded label does not skew metrics, it takes monitoring down.

So it now collapses identifier-shaped segments to :id:

/^\d+$/ | /^[0-9a-f-]{16,}$/i | segment.length > 24  →  ":id"

No existing series changes — I added a test asserting each of the current static paths is returned untouched, plus cases for a numeric id, a hex id and a long reference being collapsed, and one confirming the query is still stripped.

Worth knowing for next time: a Prometheus histogram already exposes _count, so provider_request_duration_seconds_count was already a call count by provider/operation/outcome. The reason your counter earns its place is the richer outcome vocabulary and the endpoint-path view, not the counting. If the histogram's outcome label ever gains the same four values, this counter becomes redundant and should go.

tsc clean, 1,341 tests pass.

https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nothing measures whether a Linq call succeeded

2 participants