first commit
This commit is contained in:
32
docs/networking/events/events-metrics/README.md
Normal file
32
docs/networking/events/events-metrics/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# @suaveplan/events-metrics - OTel metrics companion for @suaveplan/events: dispatch counters, latency histograms, queue-depth gauges, and active-subscriber counts via interceptor API.
|
||||
|
||||
## Summary
|
||||
|
||||
OTel metrics companion for `@suaveplan/events`. Attaches via the public interceptor API to record dispatch totals, listener error counts, and latency histograms per event name.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [attach-event-metrics](./src/core/attach-event-metrics/attach-event-metrics.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [attach-event-metrics](./src/core/attach-event-metrics/attach-event-metrics.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [metric-names](./src/metrics/metric-names/metric-names.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [metric-names](./src/metrics/metric-names/metric-names.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
<!-- end:auto-docs --> - [attach-event-metrics](./src/core/attach-event-metrics/attach-event-metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [metric-names](./src/metrics/metric-names/metric-names.md) — <!-- begin:auto-docs --> _(no submodules)_- [metric-names](./src/metrics/metric-names/metric-names.md) — <!-- begin:auto-docs --> _(no submodules)_- [attach-event-metrics](./src/core/attach-event-metrics/attach-event-metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [metric-names](./src/metrics/metric-names/metric-names.md) — <!-- begin:auto-docs --> _(no submodules)_- [metric-names](./src/metrics/metric-names/metric-names.md) — <!-- begin:auto-docs --> _(no submodules)_- [attach-event-metrics](./src/core/attach-event-metrics/attach-event-metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [metric-names](./src/metrics/metric-names/metric-names.md) — <!-- begin:auto-docs --> _(no submodules)_- [metric-names](./src/metrics/metric-names/metric-names.md) — <!-- begin:auto-docs --> _(no submodules)_- [attach-event-metrics](./src/core/attach-event-metrics/attach-event-metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [metric-names](./src/metrics/metric-names/metric-names.md) — <!-- begin:auto-docs --> _(no submodules)_ - [metric-names](./src/metrics/metric-names/metric-names.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Quick Start
|
||||
|
||||
```ts
|
||||
import { EventBus } from "@suaveplan/events";
|
||||
import { attachEventMetrics } from "@suaveplan/events-metrics";
|
||||
|
||||
const bus = new EventBus();
|
||||
const handle = attachEventMetrics(bus);
|
||||
|
||||
await bus.emitAsync("user.created", { id: "u-1" });
|
||||
console.log(handle.snapshot().dispatchTotal["user.created"]); // 1
|
||||
handle.detach();
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
- [`src/core/attach-event-metrics/attach-event-metrics`](src/core/attach-event-metrics/attach-event-metrics.md) — `attachEventMetrics`, `EventMetricsHandle`, `MetricsSnapshot`
|
||||
- [`src/metrics/metric-names/metric-names`](src/metrics/metric-names/metric-names.md) — `METRIC_NAMES`, `ATTRIBUTE_KEYS`
|
||||
@@ -0,0 +1,98 @@
|
||||
# attach-event-metrics
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Attaches OTel metrics instrumentation to an `EventBus` via the public interceptor API from `@suaveplan/events`. It records event dispatch totals, listener error counts, and latency histograms per event name without modifying the bus or its listeners. The attachment is transparent to publishers and subscribers — they do not need to know that metrics are being collected.
|
||||
|
||||
The implementation prioritises zero per-dispatch allocation on the hot path for previously seen event names by pre-caching the OTel counter/histogram references and using object property access rather than Map lookups.
|
||||
|
||||
## Features
|
||||
|
||||
- `attachEventMetrics(bus, options?)` — installs a metrics interceptor and returns a `EventMetricsHandle`
|
||||
- Dispatch total counter (`suaveplan.events.dispatch.total`) per event name
|
||||
- Listener error counter (`suaveplan.events.listener.error.total`) per event name
|
||||
- Dispatch latency histogram (`suaveplan.events.dispatch.latency_ms`) per event name
|
||||
- `EventMetricsHandle.snapshot()` returns in-process totals without needing an OTel exporter
|
||||
- `EventMetricsHandle.detach()` removes the interceptor
|
||||
- Injectable `meter`, `clock`, `logger` for testability
|
||||
- `eventFilter` option to limit recording to a subset of event names
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { EventBus } from "@suaveplan/events";
|
||||
import { attachEventMetrics } from "@suaveplan/events-metrics";
|
||||
|
||||
const bus = new EventBus<{ "user.created": { id: string } }>();
|
||||
|
||||
const handle = attachEventMetrics(bus);
|
||||
|
||||
bus.on("user.created", (payload) => console.log(payload));
|
||||
await bus.emitAsync("user.created", { id: "u-1" });
|
||||
|
||||
const snap = handle.snapshot();
|
||||
console.log(snap.dispatchTotal["user.created"]); // 1
|
||||
handle.detach();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { EventBus } from "@suaveplan/events";
|
||||
import { attachEventMetrics } from "@suaveplan/events-metrics";
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
|
||||
const bus = new EventBus<{ click: { x: number }; submit: { formId: string } }>();
|
||||
|
||||
const handle = attachEventMetrics(bus, {
|
||||
meter: metrics.getMeter("my-app"),
|
||||
eventFilter: (name) => name.startsWith("click"),
|
||||
clock: { now: () => performance.now() },
|
||||
});
|
||||
|
||||
// Only "click" events will be instrumented; "submit" events pass through uninstrumented
|
||||
await bus.emitAsync("click", { x: 42 });
|
||||
await bus.emitAsync("submit", { formId: "f-1" });
|
||||
|
||||
const snap = handle.snapshot();
|
||||
console.log(snap.dispatchTotal["click"]); // 1
|
||||
console.log(snap.dispatchTotal["submit"]); // undefined (filtered out)
|
||||
handle.detach();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `attachEventMetrics<TMap>(bus, options?): EventMetricsHandle`
|
||||
|
||||
Installs a metrics interceptor on `bus`.
|
||||
|
||||
| Param | Type | Description |
|
||||
|---|---|---|
|
||||
| `bus` | `EventBus<TMap>` | The event bus to instrument |
|
||||
| `options` | `EventMetricsOptions?` | Meter, filter, clock, logger |
|
||||
|
||||
**Returns**: `EventMetricsHandle`
|
||||
|
||||
### `EventMetricsHandle`
|
||||
|
||||
| Method | Returns | Description |
|
||||
|---|---|---|
|
||||
| `detach()` | `void` | Remove the interceptor |
|
||||
| `snapshot()` | `MetricsSnapshot` | In-process metric values |
|
||||
|
||||
### `MetricsSnapshot`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `dispatchTotal` | `Record<string, number>` | Total dispatches per event name |
|
||||
| `listenerErrorTotal` | `Record<string, number>` | Total listener errors per event name |
|
||||
| `avgDispatchLatencyMs` | `Record<string, number>` | Average latency per event name |
|
||||
| `queueDepth` | `number` | Current queue depth (0 for synchronous bus) |
|
||||
| `activeSubscribers` | `number` | Active subscriber count |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The metrics interceptor uses `bus.use()` which is the `@suaveplan/events` public interceptor API. It wraps `next()` with a timer, increments counters, and records the histogram. OTel instruments are lazily created on first use to avoid startup cost when the bus is never dispatched. The `queueDepth` and `activeSubscribers` fields in `snapshot()` are reported as 0 because the synchronous `EventBus` does not expose a queue depth API; applications using `BoundedQueue` would need a separate gauge observer.
|
||||
@@ -0,0 +1,61 @@
|
||||
# metric-names
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Exports the stable constant objects `METRIC_NAMES` and `ATTRIBUTE_KEYS` that define every OTel metric name and span attribute key used across `@suaveplan/events-metrics`. Centralising these names prevents typos, enables IDE completion, and makes it easy to audit the full observability surface of the package.
|
||||
|
||||
These constants are the single source of truth consumed by both the instrumentation code and the tests that assert metric names are correct.
|
||||
|
||||
## Features
|
||||
|
||||
- `METRIC_NAMES` — all five OTel metric names: dispatch total, listener error total, dispatch latency histogram, queue depth gauge, active subscribers gauge
|
||||
- `ATTRIBUTE_KEYS` — all four OTel attribute keys: event name, event category, listener ID, bus ID
|
||||
- Both objects use `as const` for full literal-type narrowing
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { METRIC_NAMES, ATTRIBUTE_KEYS } from "@suaveplan/events-metrics";
|
||||
|
||||
console.log(METRIC_NAMES.DISPATCH_TOTAL); // "suaveplan.events.dispatch.total"
|
||||
console.log(ATTRIBUTE_KEYS.EVENT_NAME); // "event.name"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { METRIC_NAMES, ATTRIBUTE_KEYS } from "@suaveplan/events-metrics";
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
|
||||
const meter = metrics.getMeter("my-app");
|
||||
const counter = meter.createCounter(METRIC_NAMES.DISPATCH_TOTAL);
|
||||
counter.add(1, { [ATTRIBUTE_KEYS.EVENT_NAME]: "user.created" });
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `METRIC_NAMES`
|
||||
|
||||
| Key | Value |
|
||||
|---|---|
|
||||
| `DISPATCH_TOTAL` | `"suaveplan.events.dispatch.total"` |
|
||||
| `LISTENER_ERROR_TOTAL` | `"suaveplan.events.listener.error.total"` |
|
||||
| `DISPATCH_LATENCY_MS` | `"suaveplan.events.dispatch.latency_ms"` |
|
||||
| `QUEUE_DEPTH` | `"suaveplan.events.queue.depth"` |
|
||||
| `ACTIVE_SUBSCRIBERS` | `"suaveplan.events.subscribers.active"` |
|
||||
|
||||
### `ATTRIBUTE_KEYS`
|
||||
|
||||
| Key | Value |
|
||||
|---|---|
|
||||
| `EVENT_NAME` | `"event.name"` |
|
||||
| `EVENT_CATEGORY` | `"event.category"` |
|
||||
| `LISTENER_ID` | `"listener.id"` |
|
||||
| `BUS_ID` | `"bus.id"` |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The constants are defined in `src/constants/index.ts` and re-exported from this module to follow the package structure convention. Consumers should import from `@suaveplan/events-metrics` (the root barrel) rather than the internal path to benefit from tree-shaking and future path changes.
|
||||
Reference in New Issue
Block a user