feat(core-shared): IMetrics interface + NoopMetrics impl

This commit is contained in:
2026-05-11 11:57:56 +02:00
parent 2cf1c00f93
commit e11fd7c897
3 changed files with 86 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
export type MetricAttributeValue = string | number | boolean;
/**
* Vendor-neutral metrics signal interface. Mirrors the pattern of ITracer / ILogger.
* Three impls: NoopMetrics (noop), OtelMetrics (OTel API), RecordingMetrics (core-testing).
*
* gauge() limitation: uses UpDownCounter under the hood, which accumulates deltas.
* True "set to absolute value" semantics require ObservableGauge with a callback —
* deferred to a v2 interface when the first true-gauge use case lands.
*/
export interface IMetrics {
/** Monotonic counter. Use for event counts (signups, errors, requests). */
counter(
name: string,
value?: number,
attributes?: Record<string, MetricAttributeValue>,
): void;
/** Distribution. Use for measured quantities (latency, payload size). */
histogram(
name: string,
value: number,
attributes?: Record<string, MetricAttributeValue>,
): void;
/**
* Point-in-time value. UpDownCounter under the hood — true gauge semantics
* (set to absolute value) require ObservableGauge with an async callback;
* that is deferred to a future v2 spec when the first true-gauge use case arrives.
*/
gauge(
name: string,
value: number,
attributes?: Record<string, MetricAttributeValue>,
): void;
}

View File

@@ -0,0 +1,29 @@
import { describe, it, expect } from "vitest";
import { NoopMetrics } from "./noop-metrics";
describe("NoopMetrics", () => {
it("counter() returns undefined without throwing", () => {
const metrics = new NoopMetrics();
expect(() => metrics.counter("my.counter")).not.toThrow();
expect(() => metrics.counter("my.counter", 5)).not.toThrow();
expect(() =>
metrics.counter("my.counter", 1, { feature: "auth", success: true }),
).not.toThrow();
});
it("histogram() returns undefined without throwing", () => {
const metrics = new NoopMetrics();
expect(() => metrics.histogram("my.latency", 42)).not.toThrow();
expect(() =>
metrics.histogram("my.latency", 100, { route: "/api/me" }),
).not.toThrow();
});
it("gauge() returns undefined without throwing", () => {
const metrics = new NoopMetrics();
expect(() => metrics.gauge("queue.depth", 7)).not.toThrow();
expect(() =>
metrics.gauge("queue.depth", 3, { queue: "emails" }),
).not.toThrow();
});
});

View File

@@ -0,0 +1,21 @@
import type { IMetrics, MetricAttributeValue } from "./metrics.interface";
export class NoopMetrics implements IMetrics {
counter(
_name: string,
_value?: number,
_attributes?: Record<string, MetricAttributeValue>,
): void {}
histogram(
_name: string,
_value: number,
_attributes?: Record<string, MetricAttributeValue>,
): void {}
gauge(
_name: string,
_value: number,
_attributes?: Record<string, MetricAttributeValue>,
): void {}
}