metrics emits nothing, and a provider whose capabilities().metrics is false stores nothing.
For the guide, see Metrics.
AlfizClientOptions.metrics
MetricsObserver | MetricsObserver[]
required
(observation: CheckObservation) => void, called synchronously from the
check path. An array fans out, isolating failures per sink. Do cheap work
only, because a slow observer is latency on your request path.number | { gate, visibility }
default:"1"
The probability an evaluated check is observed. A number applies to every
shape; the object form samples gates (
can, require) and visibility
traffic (canAny, requireAny, holds, heldKeys) separately. Evaluated
with one Math.random() inside the call, before any observation is built.
Values are clamped to [0, 1].() => number
default:"Math.random"
The randomness source for sampling. Injectable because
Math.random cannot
be seeded and a sampled pipeline still has to be testable."type" | "instance" | ScopeType[]
default:"\"type\""
Cardinality policy for the scope dimension.
"type" folds every instance
into its scope type. An array opts the listed scope types into raw instance
counting. "instance" opts in everything.(error: unknown) => void
Receives errors thrown by an observer. Unset means silently ignored.
right default for a lossy counter, the wrong one for debugging it.
CheckObservation
boolean
Present and
true when this check named a permission admitted by
externalPermissions: "warn" or "allow", meaning one no catalog declares. This
is how you count what still needs an imports declaration, from the same
stream that counts everything else.boolean
true for can and require, the shapes that authorize an action.
false for the visibility shapes, which drive conditional UI and vastly
outnumber gates. Keep the two apart in any counter you build.string | null
The concrete key that decided the check (the allowing key on an allow, the
first key otherwise), the pattern for
canAny / requireAny, or null
for heldKeys, which asks about the whole catalog at once.string
The scope type checked, or
"*" for the global scope. Always bounded.string
The scope instance, present only for scope types opted into instance
counting via
scopeInstances.string | null
Set when exactly one grant row allowed the check. That row was the sole
matcher: revoking it would have flipped this check to deny.
null when
several rows allowed, or when the check was denied.boolean
The allow came from ancestor implication rather than a direct match. The
implying grants are still reported in
matchedGrantIds.number
The probability that kept this observation. Multiply by
1 / sampleRate to
estimate real volume.CheckOptions
Every check shape accepts an optional trailing { observe?: boolean }, and client.snapshot() accepts observe in its options. Setting it false evaluates normally and records nothing.
otelMetricsObserver(options)
@opentelemetry/api is not a dependency of Alfiz. OtelMeter is the structural subset the adapter uses: createCounter(name, options) returning something with add(value, attributes), which a real Meter satisfies without a cast.
Emits alfiz.checks, and with attribution on, alfiz.grant.matched, alfiz.grant.sole_match, alfiz.revoke.matched, and alfiz.role.matched.
With extrapolate on (the default), each measurement adds 1 / sampleRate so a sampled counter still reads as real traffic. At the default sampleRate: 1 the two are identical.
createMetricsAggregator(options?)
A pure, bounded, windowed fold over the observation stream.
Windows roll lazily, on the first observation past the boundary, so a quiet process schedules nothing.
startMetricsFlusher(aggregator, { intervalMs }) adds a timer when a window must close on time rather than on traffic; it returns a stop() function and never keeps a process alive.
MetricsBatch
observed (what was seen) and estimated (corrected for sampling) side by side.
createProviderMetricsSink(provider, options?)
Aggregates locally, then batches to a provider that stores usage. Accepts every createMetricsAggregator option plus intervalMs and maxPendingBatches (default 4).
maxPendingBatches are queued behind a slow store, new batches are dropped and counted on sink.droppedBatches rather than queued. The sink checks capabilities().metrics once; a provider that does not accept metrics is simply never sent any, and the sink degrades to a local aggregator.
Usage reads
Available on any provider whosecapabilities().metrics is true, meaning the Application with metrics: {}, and hosted providers pointed at one.
query is { ids?: string[]; since?: number; until?: number }. ids filters to specific rows or keys, since is inclusive and until exclusive, both epoch ms, defaulting to the provider’s full retention window.
buckets (0.5.1) is the same per-bucket series RowUsage carries: the totals are exactly that list summed, so one read gives you both the headline number and the shape of it over time. Buckets with no traffic are absent rather than zero-filled. You know the window and the granularity, so a dense axis is yours to fill.
revocationSafeguard(usage, options?)
soleMatch, never on matched, and never claims that an unused grant is safe to remove.
Application options
recordMetrics, readMetrics, and pruneMetrics, which the memory and Prisma drivers do. Construction throws if they are missing, rather than accepting batches that go nowhere. The Prisma schema fragment adds one model, AlfizMetric; retention compaction runs opportunistically and off the write path.