first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
# otel/metrics
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module declares the six OpenTelemetry counter instruments emitted by
`@suaveplan/broadcast-channel`. Every counter is registered against the meter
`@suaveplan/broadcast-channel@0.1.0` so a single global `MeterProvider`
configuration in the host application picks all of them up. The instruments
close the observability gap the enterprise audit flagged ("No structured
logging / OTel counters"): each lifecycle event a channel observes is
counted, attributed by channel name and either message type or drop reason.
The `refreshBroadcastChannelMeters()` function re-resolves every instrument
against the current global `MeterProvider`. Tests call it after wiring the
in-memory exporter from `tests/setup/polyfills.ts` so subsequent record calls
land on the exporter rather than the noop meter resolved at module load.
## Features
- Six counters: `channel.message.{sent,received,dropped}`, `channel.tab.{joined,left}`, `channel.leader.elected`.
- Stable attribute shape: `{ channel }` plus `{ type }` or `{ reason }`.
- `refreshBroadcastChannelMeters()` for test rebinding.
- No span / tracer surface — counters only, by design.
- Module-load-safe under the noop meter when no `MeterProvider` is configured.
## Usage: Basic
```ts
import { metrics } from "@opentelemetry/api";
import { MeterProvider } from "@opentelemetry/sdk-metrics";
import {
refreshBroadcastChannelMeters,
TypedBroadcastChannel,
nativeChannelFactory,
} from "@suaveplan/broadcast-channel";
metrics.setGlobalMeterProvider(new MeterProvider({ readers: [] }));
refreshBroadcastChannelMeters();
const channel = new TypedBroadcastChannel({
config: { name: "app-bus" },
factory: nativeChannelFactory,
});
channel.open();
channel.send("greet", "hi"); // increments channel.message.sent
```
## Usage: Advanced
```ts
import { metrics } from "@opentelemetry/api";
import {
AggregationTemporality,
InMemoryMetricExporter,
MeterProvider,
PeriodicExportingMetricReader,
} from "@opentelemetry/sdk-metrics";
import { refreshBroadcastChannelMeters } from "@suaveplan/broadcast-channel";
const exporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
const reader = new PeriodicExportingMetricReader({
exporter,
exportIntervalMillis: 60_000,
});
metrics.setGlobalMeterProvider(new MeterProvider({ readers: [reader] }));
refreshBroadcastChannelMeters();
// Later, in a test assertion:
const collected = exporter.getMetrics();
const names = collected.flatMap((rm) =>
rm.scopeMetrics.flatMap((sm) => sm.metrics.map((m) => m.descriptor.name))
);
expect(names).toContain("channel.message.sent");
```
## API Reference
- `refreshBroadcastChannelMeters(): void` — re-resolve every instrument against the current global `MeterProvider`.
- `recordMessageSent(type: string, channelName: string): void`
- `recordMessageReceived(type: string, channelName: string): void`
- `recordMessageDropped(reason: "malformed", channelName: string): void`
- `recordTabJoined(channelName: string): void`
- `recordTabLeft(channelName: string): void`
- `recordLeaderElected(channelName: string): void`
Each `record*` helper calls `.add(1, attrs)` on the corresponding counter.
## Implementation Notes
The instruments are stored in module-local `let` bindings so
`refreshBroadcastChannelMeters` can rebind them without forcing callers to
hold a stable reference. This mirrors the pattern used by
`@suaveplan/slack-client`: at module load the meter is whatever the global
provider returns (typically the noop provider, in which case `.add()` is a
no-op), and the explicit refresh after `setGlobalMeterProvider` swaps in
real instruments. The attribute keys (`channel`, `type`, `reason`) are kept
short to minimise cardinality blow-up; consumer dashboards group on
`channel` first to slice per logical bus. No histograms are exposed — the
package has no meaningful latency axis (`send` is synchronous, dispatch is
in-process) so a histogram would be empty noise.