# `otel` _(no submodules)_ ## Purpose `otel.ts` wires the `@suaveplan/drive-cache` meter and tracer used by every middleware intercept in `@suaveplan/drive-plugin-cache`. It exposes lazily-built `getInstruments()`, a `setDriveCacheMeterProvider` override (so tests can inject a stub provider), `resetInstruments()` for inter-test isolation, and five `recordX` helpers that pair a counter increment with a single short-lived span carrying the canonical `drive.cache.*` attributes. The instruments are cached after the first call and rebuilt only when the provider override changes or `resetInstruments()` is called. This avoids re-creating instrument handles on the hot read path while still letting tests drive their own provider. ## Features - `drive_cache.hits_total{namespace}` / `misses_total{namespace}` / `populations_total{namespace}` / `invalidations_total{namespace}` counters. - `drive_cache.errors_total{namespace, op}` counter — `op ∈ {"get", "set", "delete"}`. - `drive_cache.entry_bytes{namespace}` histogram observed on every population. - One-span-per-event spans with `drive.cache.namespace`, `drive.cache.path`, and `drive.cache.outcome ∈ {"hit", "miss", "populated", "invalidated", "error"}` attributes. - `setDriveCacheMeterProvider(provider)` resets the cached instruments so tests can swap providers between cases. ## Usage: Basic ```ts import { recordHit, recordMiss, recordPopulate } from "@suaveplan/drive-plugin-cache/otel"; recordMiss("drive", "clip.mov"); recordPopulate("drive", "clip.mov", 4096); recordHit("drive", "clip.mov"); ``` ## Usage: Advanced ```ts import { setDriveCacheMeterProvider, resetInstruments, recordError, } from "@suaveplan/drive-plugin-cache/otel"; import { MeterProvider } from "@opentelemetry/sdk-metrics"; const provider = new MeterProvider(); setDriveCacheMeterProvider(provider); recordError("drive", "set", "clip.mov"); // run assertions on provider's metric readers setDriveCacheMeterProvider(undefined); resetInstruments(); ``` ## API Reference | Export | Shape | Description | |---|---|---| | `getInstruments(force?)` | `() => DriveCacheInstruments` | Lazy accessor; pass `true` to force-rebuild. | | `resetInstruments()` | `() => void` | Clear the cached bag (test-only). | | `setDriveCacheMeterProvider(provider)` | `(p?: MeterProvider) => void` | Swap the meter provider; auto-resets the cache. | | `recordHit(namespace, path)` | `(string, string) => void` | Increment hits + emit `outcome="hit"` span. | | `recordMiss(namespace, path)` | `(string, string) => void` | Increment misses + emit `outcome="miss"` span. | | `recordPopulate(namespace, path, byteLength)` | `(string, string, number) => void` | Increment populations + observe entry size + emit `outcome="populated"` span. | | `recordInvalidate(namespace, path)` | `(string, string) => void` | Increment invalidations + emit `outcome="invalidated"` span. | | `recordError(namespace, op, path)` | `(string, "get" \| "set" \| "delete", string) => void` | Increment errors + emit `outcome="error"` span with `op` attribute. | ## Implementation Notes Spans are created via `tracer.startSpan(...).end()` rather than `tracer.startActiveSpan(...)`. The middleware does its own work inside the calling async flow; the only purpose of these spans is to carry the outcome attribute for trace-backed analysis. Long-lived active spans would force every middleware intercept to thread an `OpenTelemetry.Context`, which is overkill for a cache event. Counter attribute bags are constructed inline as plain objects — V8 inlines this allocation on the hot path so the impact is undetectable.