first commit
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# `metrics`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
OpenTelemetry instrumentation surface for the JWS verify and sign hot paths. Every production code path records a histogram for wall-time latency and increments outcome-tagged counters; the error registry's `recordError` also fires once per typed throw so dashboards can pivot on `code` cardinality. The `outcome` attribute is bounded to a 4-value enum (`success | sig_invalid | alg_not_allowed | header_invalid`) so production observability backends do not blow up under attacker-controlled token streams.
|
||||
|
||||
## Features
|
||||
|
||||
- `recordVerify(alg, outcome, durationMs)` — histogram + outcome counter.
|
||||
- `recordSign(alg, "success" | "error", durationMs)` — sign-side mirror.
|
||||
- `recordError(code)` — typed-error counter pivoted by code.
|
||||
- `resetMetricsForTesting()` — rebinds the cached instrument bag after a test installs a fresh `MeterProvider`.
|
||||
- `getInstrumentsForTesting()` — exposes the bag for assertion-time introspection.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
import { resetMetricsForTesting, compactVerify } from "@suaveplan/jws";
|
||||
|
||||
metrics.setGlobalMeterProvider(myInMemoryProvider);
|
||||
resetMetricsForTesting();
|
||||
await compactVerify(token, key, { algorithms: ["HS256"] });
|
||||
// `jws.verify_duration_ms{alg=HS256, outcome=success}` has 1 sample.
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { getInstrumentsForTesting, recordVerify, recordError, JWS_CODES } from "@suaveplan/jws";
|
||||
|
||||
const bag = getInstrumentsForTesting();
|
||||
// bag.verifyDurationMs / bag.signDurationMs (Histogram)
|
||||
// bag.verifyTotal / bag.signTotal (Counter)
|
||||
// bag.errorTotal (Counter)
|
||||
|
||||
// Bypass the pipeline for an ad-hoc observation:
|
||||
recordVerify("RS256", "sig_invalid", 4.2);
|
||||
recordError(JWS_CODES.SIG_INVALID);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `recordVerify(alg: string, outcome: JwsVerifyOutcome, durationMs: number): void`
|
||||
- `recordSign(alg: string, outcome: "success" | "error", durationMs: number): void`
|
||||
- `recordError(code: string): void`
|
||||
- `nowMs(): number`
|
||||
- `resetMetricsForTesting(): void`
|
||||
- `getInstrumentsForTesting(): InstrumentBag`
|
||||
- `type JwsVerifyOutcome = "success" | "sig_invalid" | "alg_not_allowed" | "header_invalid"`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The instrument bag is cached in module scope so repeated invocations do not call `metrics.getMeter` on every request — that call has non-trivial overhead under the OTel SDK because it walks the provider's registry. Tests that install a fresh `MeterProvider` MUST call `resetMetricsForTesting()` in `beforeEach`; otherwise the cached bag stays bound to the original (no-op) provider and the test asserts against an empty histogram. The `outcome` enum's bounded cardinality is deliberate: an unbounded enum would let an adversarial token stream blow up the metrics backend's series count, and most OTel sinks bill by series.
|
||||
Reference in New Issue
Block a user