first commit
This commit is contained in:
134
docs/security-stack/security/vault-sync/src/otel/instruments.md
Normal file
134
docs/security-stack/security/vault-sync/src/otel/instruments.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# otel instruments
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`instruments.ts` exposes the safe-by-construction OTel instruments emitted
|
||||
from every `@suaveplan/vault-sync` production path. The instrument set is
|
||||
deliberately narrow: only durations, outcome counters, and a queue-depth
|
||||
gauge — no vault ids, no item ids, no key material, no vector-clock
|
||||
values. Counters carry only a `status` attribute drawn from a fixed
|
||||
allowlist (`success | conflict | rejected`, `encrypted | decrypted |
|
||||
tampered`). The lazy singleton ensures the meter is acquired exactly once
|
||||
per process; tests override the provider via `setVaultSyncMeterProvider`
|
||||
to capture instrument calls deterministically.
|
||||
|
||||
## Features
|
||||
|
||||
- `getVaultSyncInstruments()` — lazy singleton accessor for the meter set
|
||||
- `setVaultSyncMeterProvider(provider)` — inject test providers
|
||||
- `resetVaultSyncInstruments()` — clear cache between test suites
|
||||
- `getVaultSyncTracer()` / `setVaultSyncTracerProvider()` — span tracer access
|
||||
- `resetVaultSyncTracer()` — clear tracer cache between suites
|
||||
- Histogram trio for `sync`, `merge`, `patch.apply`, `payload.encrypt`,
|
||||
`queue.flush` durations
|
||||
- Observable gauge for queue depth (count only, no item identifiers)
|
||||
- Outcome counters with bounded-string labels (no PII)
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { getVaultSyncInstruments } from "@suaveplan/vault-sync/otel";
|
||||
|
||||
const i = getVaultSyncInstruments();
|
||||
const start = Date.now();
|
||||
// ... work ...
|
||||
i.syncDurationMs.record(Date.now() - start);
|
||||
i.mergeOutcomeTotal.add(1, { status: "success" });
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
getVaultSyncInstruments,
|
||||
resetVaultSyncInstruments,
|
||||
setVaultSyncMeterProvider,
|
||||
} from "@suaveplan/vault-sync/otel";
|
||||
import type { MeterProvider } from "@opentelemetry/api";
|
||||
|
||||
interface Capture {
|
||||
histograms: Map<string, number[]>;
|
||||
counters: Map<string, Array<[number, Record<string, string> | undefined]>>;
|
||||
}
|
||||
|
||||
function makeProvider(): { provider: MeterProvider; capture: Capture } {
|
||||
const capture: Capture = {
|
||||
histograms: new Map(),
|
||||
counters: new Map(),
|
||||
};
|
||||
const meter = {
|
||||
createHistogram: (name: string) => {
|
||||
capture.histograms.set(name, []);
|
||||
return { record: (v: number) => capture.histograms.get(name)?.push(v) };
|
||||
},
|
||||
createCounter: (name: string) => {
|
||||
capture.counters.set(name, []);
|
||||
return {
|
||||
add: (v: number, attrs?: Record<string, string>) => {
|
||||
capture.counters.get(name)?.push([v, attrs]);
|
||||
},
|
||||
};
|
||||
},
|
||||
createObservableGauge: (name: string) => ({
|
||||
addCallback: () => {
|
||||
capture.histograms.set(name, []);
|
||||
},
|
||||
}),
|
||||
};
|
||||
return {
|
||||
provider: {
|
||||
getMeter: () => meter as unknown as ReturnType<MeterProvider["getMeter"]>,
|
||||
} as unknown as MeterProvider,
|
||||
capture,
|
||||
};
|
||||
}
|
||||
|
||||
const { provider, capture } = makeProvider();
|
||||
setVaultSyncMeterProvider(provider);
|
||||
const i = getVaultSyncInstruments();
|
||||
i.syncDurationMs.record(42);
|
||||
console.log(capture.histograms.get("vault_sync.sync.duration_ms")); // [42]
|
||||
|
||||
setVaultSyncMeterProvider(undefined);
|
||||
resetVaultSyncInstruments();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `getVaultSyncInstruments(): VaultSyncInstruments` — lazily resolves the
|
||||
meter and caches the instrument bundle. Returns the cached value on
|
||||
subsequent calls.
|
||||
- `setVaultSyncMeterProvider(provider: MeterProvider | undefined): void` —
|
||||
rebuilds the cache against the provided meter; pass `undefined` to fall
|
||||
back to the global default.
|
||||
- `resetVaultSyncInstruments(): void` — clears the cached bundle so the
|
||||
next `get` call rebinds to the (possibly new) global meter.
|
||||
- `getVaultSyncTracer(): Tracer` — lazy tracer singleton.
|
||||
- `setVaultSyncTracerProvider(provider): void` — inject test tracer.
|
||||
- `resetVaultSyncTracer(): void` — clear tracer cache.
|
||||
|
||||
Instrument names:
|
||||
|
||||
- `vault_sync.sync.duration_ms` (Histogram, ms)
|
||||
- `vault_sync.merge.duration_ms` (Histogram, ms)
|
||||
- `vault_sync.patch.apply.duration_ms` (Histogram, ms)
|
||||
- `vault_sync.payload.encrypt.duration_ms` (Histogram, ms)
|
||||
- `vault_sync.queue.flush.duration_ms` (Histogram, ms)
|
||||
- `vault_sync.queue.depth` (ObservableGauge, count)
|
||||
- `vault_sync.merge.outcome.total` (Counter, status ∈ {success, conflict, rejected})
|
||||
- `vault_sync.payload.outcome.total` (Counter, status ∈ {encrypted, decrypted, tampered})
|
||||
|
||||
Span names emitted by the consuming modules: `sync.pull`, `sync.merge`,
|
||||
`sync.push`, `queue.flush`, `payload.encrypt`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The lazy singleton lets the package be imported even when no OTel SDK is
|
||||
registered — `metrics.getMeter` returns a no-op meter in that case, and
|
||||
recording through it is a fast inline call. Counters never carry vault or
|
||||
item identifiers; the only label is `status` drawn from a documented
|
||||
allowlist. This is enforced by code review — adding a new label requires
|
||||
an OpenSpec amendment to the observability plan in the package proposal.
|
||||
Reference in New Issue
Block a user