# OpenTelemetry wiring _(no submodules)_ ## Purpose `otel.ts` exposes the tracer, meter, and instrument bag that every read path in `@suaveplan/storage-uri-plugin-bundle` records against. It mirrors the convention used by `@suaveplan/storage-uri/otel` and `@suaveplan/storage-plugin-cache/otel`: lazy instrument acquisition, test-injectable `MeterProvider`, and a single `recordOp` entry point that emits the histogram, the success/error counter, and the per-op span in one call so the call site never forgets one of the three. The module also carries the small helpers that the rest of the package relies on for span-attribute hygiene: `sanitiseRelPathAttr` strips query strings and truncates to 256 chars, and `classifyRootKind` maps a `URL` to the `file | http | data | blob | other` label used on every span and counter. ## Features - `bundle_uri.op_duration_ms{op, root_kind, outcome}` histogram. - `bundle_uri.reads_total{root_kind, outcome}` counter. - `bundle_uri.errors_total{root_kind, code}` counter. - `bundle_uri.range_fallback_total{root_kind}` counter. - `bundle_uri.cache_hits_total{root_kind}` and `bundle_uri.cache_misses_total{root_kind}` counters. - Per-op span on the `@suaveplan/storage-uri-plugin-bundle` tracer with `bundle.package`, `bundle.rel_path`, `bundle.root_kind`, `bundle.outcome` attributes. - `setBundleUriMeterProvider(provider)` + `resetInstruments()` for deterministic test injection. ## Usage: Basic ```ts import { recordOp } from "@suaveplan/storage-uri-plugin-bundle"; recordOp({ op: "get", rootKind: "http", outcome: "success", durationMs: 12, packageSlug: "my-pkg", relPath: "assets/x.wav", }); ``` ## Usage: Advanced ```ts import { setBundleUriMeterProvider, resetInstruments, } from "@suaveplan/storage-uri-plugin-bundle"; import { MeterProvider } from "@opentelemetry/sdk-metrics"; const provider = new MeterProvider({}); setBundleUriMeterProvider(provider); resetInstruments(); // …run instrumented code, then drain `provider` to read observations. ``` ## API Reference - `getInstruments(force?)` — lazy-build the bag; `force=true` rebuilds. - `setBundleUriMeterProvider(provider | undefined)` — test injection. - `resetInstruments()` — drop the cached bag. - `recordOp(input)` — one-call emission of histogram + counter + span. - `recordRangeFallback(rootKind)` — increment fallback counter. - `recordCacheHit(rootKind)` / `recordCacheMiss(rootKind)` — cache-path counters. - `sanitiseRelPathAttr(relPath)` — strip query + truncate to 256 chars. - `classifyRootKind(rootUrl)` — `URL` → `BundleUriRootKind`. - `BundleUriOp`, `BundleUriOutcome`, `BundleUriInstruments`, `RecordOpInput` — supporting types. ## Implementation Notes The instrument bag is held in a module-private `let` so the same instruments persist across calls. `resetInstruments` is the test-only escape hatch that drops the cache so the next `getInstruments` rebinds against the freshly-installed `MeterProvider`. The 256-char clip is applied *after* the query strip — query-string lengths are not counted against the attribute size budget. `classifyRootKind` is a `switch` over `URL.protocol` (which always ends with `:`); the default case covers bundler-virtual schemes (`virtual:`, `~asset:`, `\0…`) so coverage includes the `"other"` branch.