Initial Commit
This commit is contained in:
69
packages/communication/emails/dmarc/src/otel/dmarc-otel.md
Normal file
69
packages/communication/emails/dmarc/src/otel/dmarc-otel.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# dmarc-otel
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides OpenTelemetry metric instruments for `@suaveplan/dmarc`. It gives operators visibility into parse throughput and latency without coupling the parse logic itself to observability concerns. All instruments are created lazily by the OTel SDK's no-op implementation, so importing this module in an environment without an OTel SDK installed is always safe — the calls become no-ops.
|
||||
|
||||
## Features
|
||||
|
||||
- `dmarcParseTotal` — counter tracking total parse invocations by `kind` and `status`
|
||||
- `dmarcParseDurationMs` — histogram recording parse latency in milliseconds by `kind`
|
||||
- `recordParse()` — convenience wrapper that updates both instruments in one call
|
||||
- Zero runtime dependencies beyond `@opentelemetry/api`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { recordParse } from "@suaveplan/dmarc/otel/dmarc-otel";
|
||||
|
||||
const start = performance.now();
|
||||
try {
|
||||
// ... parse logic
|
||||
recordParse("aggregate", "success", performance.now() - start);
|
||||
} catch {
|
||||
recordParse("aggregate", "error", performance.now() - start);
|
||||
throw;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { dmarcParseTotal, dmarcParseDurationMs, recordParse } from "@suaveplan/dmarc/otel/dmarc-otel";
|
||||
|
||||
// Direct instrument access for custom attribute sets
|
||||
dmarcParseTotal.add(1, { kind: "aggregate", status: "success", source: "s3" });
|
||||
dmarcParseDurationMs.record(42, { kind: "forensic", source: "email" });
|
||||
|
||||
// Or use the convenience wrapper for standard usage
|
||||
recordParse("forensic", "success", 38);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `dmarcParseTotal`
|
||||
|
||||
`Counter` — incremented once per parse call. Attributes: `kind` (`"aggregate" | "forensic"`), `status` (`"success" | "error"`).
|
||||
|
||||
### `dmarcParseDurationMs`
|
||||
|
||||
`Histogram` — records elapsed parse time in milliseconds. Attributes: `kind` (`"aggregate" | "forensic"`).
|
||||
|
||||
### `recordParse(kind, status, durationMs): void`
|
||||
|
||||
Convenience function that increments `dmarcParseTotal` and records `durationMs` in `dmarcParseDurationMs` in a single call.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `kind` — `"aggregate"` or `"forensic"`
|
||||
- `status` — `"success"` or `"error"`
|
||||
- `durationMs` — elapsed time in milliseconds
|
||||
|
||||
**Returns:** `void`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The meter name is `"@suaveplan/dmarc"` which matches the npm package name, making it trivial to filter metrics by package in dashboards. Histogram bucket boundaries are left to the OTel SDK/exporter configuration rather than hardcoded here, which is the recommended practice per the OTel specification. Importing this module in test environments without an SDK causes zero issues because the OTel API ships a built-in no-op implementation.
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
dmarcParseDurationMs,
|
||||
dmarcParseTotal,
|
||||
recordParse,
|
||||
} from "./dmarc-otel.js";
|
||||
|
||||
describe("dmarc-otel", () => {
|
||||
it("exports dmarcParseTotal counter", { timeout: 5000 }, () => {
|
||||
expect(dmarcParseTotal).toBeDefined();
|
||||
expect(typeof dmarcParseTotal.add).toBe("function");
|
||||
});
|
||||
|
||||
it("exports dmarcParseDurationMs histogram", { timeout: 5000 }, () => {
|
||||
expect(dmarcParseDurationMs).toBeDefined();
|
||||
expect(typeof dmarcParseDurationMs.record).toBe("function");
|
||||
});
|
||||
|
||||
it("recordParse does not throw for aggregate success", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(() => recordParse("aggregate", "success", 10)).not.toThrow();
|
||||
});
|
||||
|
||||
it("recordParse does not throw for aggregate error", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(() => recordParse("aggregate", "error", 5)).not.toThrow();
|
||||
});
|
||||
|
||||
it("recordParse does not throw for forensic success", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(() => recordParse("forensic", "success", 20)).not.toThrow();
|
||||
});
|
||||
|
||||
it("recordParse does not throw for forensic error", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(() => recordParse("forensic", "error", 3)).not.toThrow();
|
||||
});
|
||||
});
|
||||
41
packages/communication/emails/dmarc/src/otel/dmarc-otel.ts
Normal file
41
packages/communication/emails/dmarc/src/otel/dmarc-otel.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
|
||||
const meter = metrics.getMeter("@suaveplan/dmarc");
|
||||
|
||||
/**
|
||||
* Counter tracking the total number of DMARC parse operations, labelled by
|
||||
* `kind` (`aggregate` or `forensic`) and `status` (`success` or `error`).
|
||||
*/
|
||||
export const dmarcParseTotal = meter.createCounter("dmarc.parse.total", {
|
||||
description: "Total number of DMARC report parse operations",
|
||||
unit: "{reports}",
|
||||
});
|
||||
|
||||
/**
|
||||
* Histogram recording the wall-clock duration of each parse operation in
|
||||
* milliseconds, labelled by `kind` (`aggregate` or `forensic`).
|
||||
*/
|
||||
export const dmarcParseDurationMs = meter.createHistogram(
|
||||
"dmarc.parse.duration_ms",
|
||||
{
|
||||
description:
|
||||
"Duration of DMARC report parse operations in milliseconds",
|
||||
unit: "ms",
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Records a completed parse observation.
|
||||
*
|
||||
* @param kind - `"aggregate"` or `"forensic"`.
|
||||
* @param status - `"success"` or `"error"`.
|
||||
* @param durationMs - Elapsed milliseconds for the parse call.
|
||||
*/
|
||||
export function recordParse(
|
||||
kind: "aggregate" | "forensic",
|
||||
status: "success" | "error",
|
||||
durationMs: number
|
||||
): void {
|
||||
dmarcParseTotal.add(1, { kind, status });
|
||||
dmarcParseDurationMs.record(durationMs, { kind });
|
||||
}
|
||||
5
packages/communication/emails/dmarc/src/otel/index.ts
Normal file
5
packages/communication/emails/dmarc/src/otel/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export {
|
||||
dmarcParseDurationMs,
|
||||
dmarcParseTotal,
|
||||
recordParse,
|
||||
} from "./dmarc-otel";
|
||||
Reference in New Issue
Block a user