Initial Commit
This commit is contained in:
75
packages/communication/emails/emailer/src/otel/metrics.md
Normal file
75
packages/communication/emails/emailer/src/otel/metrics.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# `metrics.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Single source of truth for every OpenTelemetry instrument emitted by
|
||||
`@suaveplan/emailer`. Every production path that touches the wire — send
|
||||
attempts, send failures, latency, retries, queue enqueues — records exactly
|
||||
one observation through one of the helpers exported here. Centralising the
|
||||
emission lets the spec-conformance test grep one file to assert that every
|
||||
declared instrument is wired into a production call.
|
||||
|
||||
## Features
|
||||
|
||||
- `recordSend(transport, outcome, latencyMs)` — counter + histogram pair
|
||||
for every send attempt.
|
||||
- `recordSendError(transport, code)` — counter keyed on the typed
|
||||
`EmailerErrorCode` so dashboards filter by code path, not message.
|
||||
- `recordRetry(transport)` — counter on every retry attempt.
|
||||
- `recordQueue(transport)` — counter on every enqueue.
|
||||
- `refreshEmailerMeters()` — re-binds every instrument against the current
|
||||
global `MeterProvider`; tests call this after installing an in-memory
|
||||
provider so subsequent emissions land on the test exporter.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { recordSend } from "@suaveplan/emailer/otel/metrics";
|
||||
|
||||
const startedAt = Date.now();
|
||||
// ... actual send ...
|
||||
recordSend("dummy", "ok", Date.now() - startedAt);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { MeterProvider } from "@opentelemetry/sdk-metrics";
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
import { refreshEmailerMeters, recordSend } from "@suaveplan/emailer/otel/metrics";
|
||||
|
||||
const provider = new MeterProvider();
|
||||
metrics.setGlobalMeterProvider(provider);
|
||||
refreshEmailerMeters();
|
||||
|
||||
recordSend("dummy", "ok", 12);
|
||||
// `provider.collect()` now contains an `emailer.send.total` observation
|
||||
// with attributes `{ transport: "dummy", outcome: "ok" }`.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `refreshEmailerMeters(): void`
|
||||
- `recordSend(transport: string, outcome: "ok" | "error", latencyMs: number): void`
|
||||
- `recordSendError(transport: string, code: string): void`
|
||||
- `recordRetry(transport: string): void`
|
||||
- `recordQueue(transport: string): void`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The five instrument names — `emailer.send.total`,
|
||||
`emailer.send.errors`, `emailer.send.latency_ms`, `emailer.retry.total`,
|
||||
`emailer.queue.depth` — are part of the public surface. Renaming them
|
||||
silently breaks every downstream dashboard, so any change must move
|
||||
through OpenSpec.
|
||||
- `refreshEmailerMeters` is required because the OpenTelemetry SDK does
|
||||
not propagate provider changes to existing instruments. Tests that swap
|
||||
the global provider must call it before invoking the production path,
|
||||
otherwise observations land on the module-load meter and the test sees
|
||||
zero data.
|
||||
- All instruments are universally available — the helpers carry no IO and
|
||||
are safe to call from synchronous hot paths.
|
||||
Reference in New Issue
Block a user