@zudojs/observability 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +260 -13
- package/dist/errors/index.d.ts +5 -0
- package/dist/errors/index.js +5 -0
- package/dist/errors/observabilityError.core.d.ts +32 -0
- package/dist/errors/observabilityError.core.js +56 -0
- package/dist/exporter/exporter.console.d.ts +51 -3
- package/dist/exporter/exporter.console.js +110 -23
- package/dist/exporter/index.d.ts +2 -2
- package/dist/exporter/index.js +2 -2
- package/dist/index.d.ts +17 -11
- package/dist/index.js +20 -12
- package/dist/internal/ids.core.d.ts +26 -0
- package/dist/internal/ids.core.js +51 -0
- package/dist/internal/index.d.ts +5 -0
- package/dist/internal/index.js +5 -0
- package/dist/logLevel/index.d.ts +1 -1
- package/dist/logLevel/index.js +1 -1
- package/dist/logLevel/logLevel.type.d.ts +8 -2
- package/dist/logLevel/logLevel.type.js +27 -20
- package/dist/logRecord/index.d.ts +1 -1
- package/dist/logRecord/index.js +1 -1
- package/dist/logRecord/logRecord.core.d.ts +15 -3
- package/dist/logRecord/logRecord.core.js +48 -26
- package/dist/logger/logger.core.d.ts +16 -8
- package/dist/logger/logger.core.js +54 -23
- package/dist/metrics/counter/counter.core.d.ts +4 -1
- package/dist/metrics/counter/counter.core.js +11 -3
- package/dist/metrics/gauge/gauge.core.d.ts +1 -0
- package/dist/metrics/gauge/gauge.core.js +9 -0
- package/dist/metrics/histogram/histogram.core.d.ts +22 -11
- package/dist/metrics/histogram/histogram.core.js +102 -7
- package/dist/metrics/histogram/index.d.ts +1 -1
- package/dist/metrics/histogram/index.js +1 -1
- package/dist/metrics/index.d.ts +4 -3
- package/dist/metrics/index.js +4 -3
- package/dist/metrics/metrics.reader.d.ts +39 -0
- package/dist/metrics/metrics.reader.js +81 -0
- package/dist/metrics/metrics.registry.d.ts +55 -4
- package/dist/metrics/metrics.registry.js +136 -49
- package/dist/noop/index.d.ts +1 -1
- package/dist/noop/index.js +1 -1
- package/dist/noop/noopObservability.core.d.ts +14 -2
- package/dist/noop/noopObservability.core.js +48 -11
- package/dist/observability/observability.core.d.ts +57 -3
- package/dist/observability/observability.core.js +206 -50
- package/dist/processor/index.d.ts +4 -3
- package/dist/processor/index.js +4 -3
- package/dist/processor/processor.batch.d.ts +66 -11
- package/dist/processor/processor.batch.js +130 -29
- package/dist/processor/processor.log.d.ts +56 -0
- package/dist/processor/processor.log.js +119 -0
- package/dist/propagation/index.d.ts +1 -1
- package/dist/propagation/index.js +1 -1
- package/dist/propagation/propagation.core.d.ts +26 -6
- package/dist/propagation/propagation.core.js +35 -19
- package/dist/redaction/index.d.ts +1 -1
- package/dist/redaction/index.js +1 -1
- package/dist/redaction/redaction.core.d.ts +37 -5
- package/dist/redaction/redaction.core.js +180 -36
- package/dist/sampling/index.d.ts +1 -1
- package/dist/sampling/index.js +1 -1
- package/dist/sampling/sampler.type.d.ts +38 -6
- package/dist/sampling/sampler.type.js +64 -23
- package/dist/tracing/index.d.ts +2 -2
- package/dist/tracing/index.js +2 -2
- package/dist/tracing/span/index.d.ts +1 -1
- package/dist/tracing/span/index.js +1 -1
- package/dist/tracing/span/span.core.d.ts +51 -8
- package/dist/tracing/span/span.core.js +90 -10
- package/dist/tracing/span/spanContext.type.d.ts +12 -3
- package/dist/tracing/span/spanContext.type.js +19 -16
- package/dist/tracing/tracer/index.d.ts +2 -2
- package/dist/tracing/tracer/index.js +2 -2
- package/dist/tracing/tracer/tracer.core.d.ts +44 -14
- package/dist/tracing/tracer/tracer.core.js +81 -25
- package/dist/types/config.types.d.ts +138 -0
- package/dist/types/config.types.js +5 -0
- package/dist/types/logging.types.d.ts +94 -0
- package/dist/types/logging.types.js +20 -0
- package/dist/types/metrics.types.d.ts +93 -0
- package/dist/types/metrics.types.js +5 -0
- package/dist/types/tracing.types.d.ts +115 -0
- package/dist/types/tracing.types.js +28 -0
- package/package.json +17 -8
- package/dist/exporter/exporter.console.d.ts.map +0 -1
- package/dist/exporter/exporter.console.js.map +0 -1
- package/dist/exporter/index.d.ts.map +0 -1
- package/dist/exporter/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logLevel/index.d.ts.map +0 -1
- package/dist/logLevel/index.js.map +0 -1
- package/dist/logLevel/logLevel.type.d.ts.map +0 -1
- package/dist/logLevel/logLevel.type.js.map +0 -1
- package/dist/logRecord/index.d.ts.map +0 -1
- package/dist/logRecord/index.js.map +0 -1
- package/dist/logRecord/logRecord.core.d.ts.map +0 -1
- package/dist/logRecord/logRecord.core.js.map +0 -1
- package/dist/logger/index.d.ts.map +0 -1
- package/dist/logger/index.js.map +0 -1
- package/dist/logger/logger.core.d.ts.map +0 -1
- package/dist/logger/logger.core.js.map +0 -1
- package/dist/metrics/counter/counter.core.d.ts.map +0 -1
- package/dist/metrics/counter/counter.core.js.map +0 -1
- package/dist/metrics/counter/index.d.ts.map +0 -1
- package/dist/metrics/counter/index.js.map +0 -1
- package/dist/metrics/gauge/gauge.core.d.ts.map +0 -1
- package/dist/metrics/gauge/gauge.core.js.map +0 -1
- package/dist/metrics/gauge/index.d.ts.map +0 -1
- package/dist/metrics/gauge/index.js.map +0 -1
- package/dist/metrics/histogram/histogram.core.d.ts.map +0 -1
- package/dist/metrics/histogram/histogram.core.js.map +0 -1
- package/dist/metrics/histogram/index.d.ts.map +0 -1
- package/dist/metrics/histogram/index.js.map +0 -1
- package/dist/metrics/index.d.ts.map +0 -1
- package/dist/metrics/index.js.map +0 -1
- package/dist/metrics/metrics.registry.d.ts.map +0 -1
- package/dist/metrics/metrics.registry.js.map +0 -1
- package/dist/noop/index.d.ts.map +0 -1
- package/dist/noop/index.js.map +0 -1
- package/dist/noop/noopObservability.core.d.ts.map +0 -1
- package/dist/noop/noopObservability.core.js.map +0 -1
- package/dist/observability/index.d.ts.map +0 -1
- package/dist/observability/index.js.map +0 -1
- package/dist/observability/observability.core.d.ts.map +0 -1
- package/dist/observability/observability.core.js.map +0 -1
- package/dist/processor/index.d.ts.map +0 -1
- package/dist/processor/index.js.map +0 -1
- package/dist/processor/processor.batch.d.ts.map +0 -1
- package/dist/processor/processor.batch.js.map +0 -1
- package/dist/propagation/index.d.ts.map +0 -1
- package/dist/propagation/index.js.map +0 -1
- package/dist/propagation/propagation.core.d.ts.map +0 -1
- package/dist/propagation/propagation.core.js.map +0 -1
- package/dist/redaction/index.d.ts.map +0 -1
- package/dist/redaction/index.js.map +0 -1
- package/dist/redaction/redaction.core.d.ts.map +0 -1
- package/dist/redaction/redaction.core.js.map +0 -1
- package/dist/sampling/index.d.ts.map +0 -1
- package/dist/sampling/index.js.map +0 -1
- package/dist/sampling/sampler.type.d.ts.map +0 -1
- package/dist/sampling/sampler.type.js.map +0 -1
- package/dist/tracing/index.d.ts.map +0 -1
- package/dist/tracing/index.js.map +0 -1
- package/dist/tracing/span/index.d.ts.map +0 -1
- package/dist/tracing/span/index.js.map +0 -1
- package/dist/tracing/span/span.core.d.ts.map +0 -1
- package/dist/tracing/span/span.core.js.map +0 -1
- package/dist/tracing/span/spanContext.type.d.ts.map +0 -1
- package/dist/tracing/span/spanContext.type.js.map +0 -1
- package/dist/tracing/tracer/index.d.ts.map +0 -1
- package/dist/tracing/tracer/index.js.map +0 -1
- package/dist/tracing/tracer/tracer.core.d.ts.map +0 -1
- package/dist/tracing/tracer/tracer.core.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
import type { Counter } from "../../types.js";
|
|
7
7
|
/**
|
|
8
8
|
* In-memory counter. Increments monotonically.
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
|
+
* Invalid increments throw rather than being dropped: a counter that quietly
|
|
11
|
+
* ignored a negative delta would report a total nobody can reconcile with the
|
|
12
|
+
* code that produced it.
|
|
10
13
|
*/
|
|
11
14
|
export declare class DefaultCounter implements Counter {
|
|
12
15
|
readonly name: string;
|
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Monotonically increasing counter for tracking event counts.
|
|
5
5
|
*/
|
|
6
|
+
import { MetricValueError } from "../../errors/index.js";
|
|
6
7
|
/**
|
|
7
8
|
* In-memory counter. Increments monotonically.
|
|
8
|
-
*
|
|
9
|
+
*
|
|
10
|
+
* Invalid increments throw rather than being dropped: a counter that quietly
|
|
11
|
+
* ignored a negative delta would report a total nobody can reconcile with the
|
|
12
|
+
* code that produced it.
|
|
9
13
|
*/
|
|
10
14
|
export class DefaultCounter {
|
|
11
15
|
name;
|
|
@@ -16,8 +20,12 @@ export class DefaultCounter {
|
|
|
16
20
|
this.labels = labels;
|
|
17
21
|
}
|
|
18
22
|
increment(value = 1) {
|
|
19
|
-
if (value
|
|
20
|
-
|
|
23
|
+
if (!Number.isFinite(value)) {
|
|
24
|
+
throw new MetricValueError(this.name, value, "must be finite");
|
|
25
|
+
}
|
|
26
|
+
if (value < 0) {
|
|
27
|
+
throw new MetricValueError(this.name, value, "a counter cannot decrease; use a gauge");
|
|
28
|
+
}
|
|
21
29
|
this.value += value;
|
|
22
30
|
}
|
|
23
31
|
getValue() {
|
|
@@ -12,6 +12,7 @@ export declare class DefaultGauge implements Gauge {
|
|
|
12
12
|
readonly labels?: Record<string, string>;
|
|
13
13
|
private value;
|
|
14
14
|
constructor(name: string, labels?: Record<string, string>);
|
|
15
|
+
private assertFinite;
|
|
15
16
|
setValue(value: number): void;
|
|
16
17
|
increment(value?: number): void;
|
|
17
18
|
decrement(value?: number): void;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A value that can go up and down, for tracking current state.
|
|
5
5
|
*/
|
|
6
|
+
import { MetricValueError } from "../../errors/index.js";
|
|
6
7
|
/**
|
|
7
8
|
* In-memory gauge. Tracks a value that can be set, incremented, or decremented.
|
|
8
9
|
*/
|
|
@@ -14,13 +15,21 @@ export class DefaultGauge {
|
|
|
14
15
|
this.name = name;
|
|
15
16
|
this.labels = labels;
|
|
16
17
|
}
|
|
18
|
+
assertFinite(value) {
|
|
19
|
+
if (!Number.isFinite(value)) {
|
|
20
|
+
throw new MetricValueError(this.name, value, "must be finite");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
17
23
|
setValue(value) {
|
|
24
|
+
this.assertFinite(value);
|
|
18
25
|
this.value = value;
|
|
19
26
|
}
|
|
20
27
|
increment(value = 1) {
|
|
28
|
+
this.assertFinite(value);
|
|
21
29
|
this.value += value;
|
|
22
30
|
}
|
|
23
31
|
decrement(value = 1) {
|
|
32
|
+
this.assertFinite(value);
|
|
24
33
|
this.value -= value;
|
|
25
34
|
}
|
|
26
35
|
getValue() {
|
|
@@ -2,29 +2,40 @@
|
|
|
2
2
|
* @zudojs/observability — Histogram
|
|
3
3
|
*
|
|
4
4
|
* Distribution of observed values for tracking latencies, sizes, etc.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Memory is bounded by fixed bucket boundaries rather than by keeping the
|
|
7
|
+
* observations: count, sum, min and max alone cannot answer "what is the p95",
|
|
8
|
+
* which is the question a latency histogram exists to answer.
|
|
9
|
+
*/
|
|
10
|
+
import type { Histogram, HistogramValue } from "../../types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Default boundaries, in milliseconds, covering sub-millisecond calls through
|
|
13
|
+
* ten-second ones. Pass your own when the unit is not latency.
|
|
6
14
|
*/
|
|
7
|
-
|
|
15
|
+
export declare const DEFAULT_BUCKET_BOUNDARIES: readonly number[];
|
|
8
16
|
/**
|
|
9
|
-
* In-memory histogram
|
|
17
|
+
* In-memory histogram with cumulative buckets and interpolated quantiles.
|
|
10
18
|
*/
|
|
11
19
|
export declare class DefaultHistogram implements Histogram {
|
|
12
20
|
readonly name: string;
|
|
13
21
|
readonly labels?: Record<string, string>;
|
|
22
|
+
private readonly boundaries;
|
|
23
|
+
/** Counts per bucket; one slot longer than `boundaries` for the overflow. */
|
|
24
|
+
private counts;
|
|
14
25
|
private count;
|
|
15
26
|
private sum;
|
|
16
27
|
private min;
|
|
17
28
|
private max;
|
|
18
|
-
constructor(name: string, labels?: Record<string, string
|
|
29
|
+
constructor(name: string, labels?: Record<string, string>, boundaries?: readonly number[]);
|
|
19
30
|
record(value: number): void;
|
|
20
|
-
getValue():
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
getValue(): HistogramValue;
|
|
32
|
+
/**
|
|
33
|
+
* Estimates a quantile by linear interpolation inside the bucket the
|
|
34
|
+
* quantile falls in, clamped to the observed min and max.
|
|
35
|
+
*/
|
|
36
|
+
percentile(q: number): number;
|
|
26
37
|
reset(): void;
|
|
27
38
|
}
|
|
28
39
|
/** Creates a histogram. */
|
|
29
|
-
export declare function createHistogram(name: string, labels?: Record<string, string
|
|
40
|
+
export declare function createHistogram(name: string, labels?: Record<string, string>, boundaries?: readonly number[]): DefaultHistogram;
|
|
30
41
|
//# sourceMappingURL=histogram.core.d.ts.map
|
|
@@ -2,47 +2,142 @@
|
|
|
2
2
|
* @zudojs/observability — Histogram
|
|
3
3
|
*
|
|
4
4
|
* Distribution of observed values for tracking latencies, sizes, etc.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Memory is bounded by fixed bucket boundaries rather than by keeping the
|
|
7
|
+
* observations: count, sum, min and max alone cannot answer "what is the p95",
|
|
8
|
+
* which is the question a latency histogram exists to answer.
|
|
9
|
+
*/
|
|
10
|
+
import { MetricValueError } from "../../errors/index.js";
|
|
11
|
+
/**
|
|
12
|
+
* Default boundaries, in milliseconds, covering sub-millisecond calls through
|
|
13
|
+
* ten-second ones. Pass your own when the unit is not latency.
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_BUCKET_BOUNDARIES = [
|
|
16
|
+
1, 2, 5, 10, 25, 50, 100, 250, 500, 1_000, 2_500, 5_000, 10_000,
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Validates and canonicalises bucket boundaries.
|
|
20
|
+
*
|
|
21
|
+
* A `NaN` boundary makes `sort` leave the list in an arbitrary order, and
|
|
22
|
+
* every quantile computed from it comes back `NaN` — a latency histogram that
|
|
23
|
+
* reports nothing while looking like it works. Duplicates create zero-width
|
|
24
|
+
* buckets that skew interpolation, and an empty list leaves a single overflow
|
|
25
|
+
* bucket that cannot answer a percentile at all.
|
|
6
26
|
*/
|
|
27
|
+
function normalizeBoundaries(name, boundaries) {
|
|
28
|
+
for (const boundary of boundaries) {
|
|
29
|
+
if (!Number.isFinite(boundary)) {
|
|
30
|
+
throw new MetricValueError(name, boundary, "histogram bucket boundaries must all be finite numbers");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const sorted = [...new Set(boundaries)].sort((a, b) => a - b);
|
|
34
|
+
if (sorted.length === 0) {
|
|
35
|
+
throw new MetricValueError(name, 0, "a histogram needs at least one bucket boundary");
|
|
36
|
+
}
|
|
37
|
+
return sorted;
|
|
38
|
+
}
|
|
7
39
|
/**
|
|
8
|
-
* In-memory histogram
|
|
40
|
+
* In-memory histogram with cumulative buckets and interpolated quantiles.
|
|
9
41
|
*/
|
|
10
42
|
export class DefaultHistogram {
|
|
11
43
|
name;
|
|
12
44
|
labels;
|
|
45
|
+
boundaries;
|
|
46
|
+
/** Counts per bucket; one slot longer than `boundaries` for the overflow. */
|
|
47
|
+
counts;
|
|
13
48
|
count = 0;
|
|
14
49
|
sum = 0;
|
|
15
50
|
min = Infinity;
|
|
16
51
|
max = -Infinity;
|
|
17
|
-
constructor(name, labels) {
|
|
52
|
+
constructor(name, labels, boundaries = DEFAULT_BUCKET_BOUNDARIES) {
|
|
18
53
|
this.name = name;
|
|
19
54
|
this.labels = labels;
|
|
55
|
+
this.boundaries = normalizeBoundaries(name, boundaries);
|
|
56
|
+
this.counts = new Array(this.boundaries.length + 1).fill(0);
|
|
20
57
|
}
|
|
21
58
|
record(value) {
|
|
59
|
+
if (!Number.isFinite(value)) {
|
|
60
|
+
throw new MetricValueError(this.name, value, "must be finite");
|
|
61
|
+
}
|
|
22
62
|
this.count++;
|
|
23
63
|
this.sum += value;
|
|
24
64
|
if (value < this.min)
|
|
25
65
|
this.min = value;
|
|
26
66
|
if (value > this.max)
|
|
27
67
|
this.max = value;
|
|
68
|
+
let bucket = this.boundaries.length;
|
|
69
|
+
for (let i = 0; i < this.boundaries.length; i++) {
|
|
70
|
+
if (value <= this.boundaries[i]) {
|
|
71
|
+
bucket = i;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
this.counts[bucket] += 1;
|
|
28
76
|
}
|
|
29
77
|
getValue() {
|
|
78
|
+
const empty = this.count === 0;
|
|
79
|
+
let cumulative = 0;
|
|
80
|
+
const buckets = this.boundaries.map((le, i) => {
|
|
81
|
+
cumulative += this.counts[i];
|
|
82
|
+
return { le, count: cumulative };
|
|
83
|
+
});
|
|
30
84
|
return {
|
|
31
85
|
count: this.count,
|
|
32
86
|
sum: this.sum,
|
|
33
|
-
min:
|
|
34
|
-
max:
|
|
87
|
+
min: empty ? 0 : this.min,
|
|
88
|
+
max: empty ? 0 : this.max,
|
|
89
|
+
mean: empty ? 0 : this.sum / this.count,
|
|
90
|
+
buckets,
|
|
91
|
+
p50: this.percentile(0.5),
|
|
92
|
+
p90: this.percentile(0.9),
|
|
93
|
+
p95: this.percentile(0.95),
|
|
94
|
+
p99: this.percentile(0.99),
|
|
35
95
|
};
|
|
36
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Estimates a quantile by linear interpolation inside the bucket the
|
|
99
|
+
* quantile falls in, clamped to the observed min and max.
|
|
100
|
+
*/
|
|
101
|
+
percentile(q) {
|
|
102
|
+
if (!Number.isFinite(q) || q < 0 || q > 1) {
|
|
103
|
+
throw new MetricValueError(this.name, q, "quantile must be in [0, 1]");
|
|
104
|
+
}
|
|
105
|
+
if (this.count === 0)
|
|
106
|
+
return 0;
|
|
107
|
+
const target = q * this.count;
|
|
108
|
+
let cumulative = 0;
|
|
109
|
+
let lowerBound = this.min;
|
|
110
|
+
for (let i = 0; i < this.counts.length; i++) {
|
|
111
|
+
const inBucket = this.counts[i];
|
|
112
|
+
if (inBucket === 0) {
|
|
113
|
+
if (i < this.boundaries.length)
|
|
114
|
+
lowerBound = this.boundaries[i];
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const upperBound = i < this.boundaries.length ? this.boundaries[i] : this.max;
|
|
118
|
+
if (cumulative + inBucket >= target) {
|
|
119
|
+
const within = (target - cumulative) / inBucket;
|
|
120
|
+
const low = Math.max(lowerBound, this.min);
|
|
121
|
+
const high = Math.min(upperBound, this.max);
|
|
122
|
+
if (high <= low)
|
|
123
|
+
return high;
|
|
124
|
+
return low + within * (high - low);
|
|
125
|
+
}
|
|
126
|
+
cumulative += inBucket;
|
|
127
|
+
lowerBound = upperBound;
|
|
128
|
+
}
|
|
129
|
+
return this.max;
|
|
130
|
+
}
|
|
37
131
|
reset() {
|
|
38
132
|
this.count = 0;
|
|
39
133
|
this.sum = 0;
|
|
40
134
|
this.min = Infinity;
|
|
41
135
|
this.max = -Infinity;
|
|
136
|
+
this.counts = new Array(this.boundaries.length + 1).fill(0);
|
|
42
137
|
}
|
|
43
138
|
}
|
|
44
139
|
/** Creates a histogram. */
|
|
45
|
-
export function createHistogram(name, labels) {
|
|
46
|
-
return new DefaultHistogram(name, labels);
|
|
140
|
+
export function createHistogram(name, labels, boundaries) {
|
|
141
|
+
return new DefaultHistogram(name, labels, boundaries);
|
|
47
142
|
}
|
|
48
143
|
//# sourceMappingURL=histogram.core.js.map
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Distribution of observed values for latencies and sizes.
|
|
5
5
|
*/
|
|
6
|
-
export { DefaultHistogram, createHistogram } from "./histogram.core.js";
|
|
6
|
+
export { DefaultHistogram, createHistogram, DEFAULT_BUCKET_BOUNDARIES, } from "./histogram.core.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Distribution of observed values for latencies and sizes.
|
|
5
5
|
*/
|
|
6
|
-
export { DefaultHistogram, createHistogram } from "./histogram.core.js";
|
|
6
|
+
export { DefaultHistogram, createHistogram, DEFAULT_BUCKET_BOUNDARIES, } from "./histogram.core.js";
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/metrics/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zudojs/observability — Metrics
|
|
3
3
|
*
|
|
4
|
-
* Counters, gauges, histograms, and the
|
|
4
|
+
* Counters, gauges, histograms, the registry, and the periodic reader.
|
|
5
5
|
*/
|
|
6
6
|
export { DefaultCounter, createCounter } from "./counter/index.js";
|
|
7
7
|
export { DefaultGauge, createGauge } from "./gauge/index.js";
|
|
8
|
-
export { DefaultHistogram, createHistogram } from "./histogram/index.js";
|
|
9
|
-
export { DefaultMetricsRegistry, createMetricsRegistry, } from "./metrics.registry.js";
|
|
8
|
+
export { DefaultHistogram, createHistogram, DEFAULT_BUCKET_BOUNDARIES, } from "./histogram/index.js";
|
|
9
|
+
export { DefaultMetricsRegistry, createMetricsRegistry, metricKey, type MetricsRegistryOptions, } from "./metrics.registry.js";
|
|
10
|
+
export { PeriodicMetricReader, createPeriodicMetricReader, type PeriodicMetricReaderOptions, } from "./metrics.reader.js";
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/metrics/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zudojs/observability — Metrics
|
|
3
3
|
*
|
|
4
|
-
* Counters, gauges, histograms, and the
|
|
4
|
+
* Counters, gauges, histograms, the registry, and the periodic reader.
|
|
5
5
|
*/
|
|
6
6
|
export { DefaultCounter, createCounter } from "./counter/index.js";
|
|
7
7
|
export { DefaultGauge, createGauge } from "./gauge/index.js";
|
|
8
|
-
export { DefaultHistogram, createHistogram } from "./histogram/index.js";
|
|
9
|
-
export { DefaultMetricsRegistry, createMetricsRegistry, } from "./metrics.registry.js";
|
|
8
|
+
export { DefaultHistogram, createHistogram, DEFAULT_BUCKET_BOUNDARIES, } from "./histogram/index.js";
|
|
9
|
+
export { DefaultMetricsRegistry, createMetricsRegistry, metricKey, } from "./metrics.registry.js";
|
|
10
|
+
export { PeriodicMetricReader, createPeriodicMetricReader, } from "./metrics.reader.js";
|
|
10
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Metric Reader
|
|
3
|
+
*
|
|
4
|
+
* Metrics are pull-based in-process: the registry holds live numbers and
|
|
5
|
+
* nothing moves them anywhere. This reader is the missing half — it snapshots
|
|
6
|
+
* the registry on an interval and hands the snapshots to an exporter, so a
|
|
7
|
+
* configured `metricExporter` actually receives data.
|
|
8
|
+
*/
|
|
9
|
+
import type { MetricExporter, MetricsRegistry } from "../types.js";
|
|
10
|
+
/** Options for {@link PeriodicMetricReader}. */
|
|
11
|
+
export interface PeriodicMetricReaderOptions {
|
|
12
|
+
readonly registry: MetricsRegistry;
|
|
13
|
+
readonly exporter: MetricExporter;
|
|
14
|
+
/** Export interval in ms. Default: 60,000. `0` disables the timer. */
|
|
15
|
+
readonly intervalMs?: number;
|
|
16
|
+
/** Reports export failures. */
|
|
17
|
+
readonly onError?: (error: unknown, source: string) => void;
|
|
18
|
+
}
|
|
19
|
+
/** Snapshots a registry on an interval and exports the result. */
|
|
20
|
+
export declare class PeriodicMetricReader {
|
|
21
|
+
private readonly registry;
|
|
22
|
+
private readonly exporter;
|
|
23
|
+
private readonly intervalMs;
|
|
24
|
+
private readonly onError?;
|
|
25
|
+
private timer?;
|
|
26
|
+
private started;
|
|
27
|
+
private shuttingDown;
|
|
28
|
+
private inFlight?;
|
|
29
|
+
constructor(options: PeriodicMetricReaderOptions);
|
|
30
|
+
/** Begins periodic export. Calling it twice is a no-op. */
|
|
31
|
+
start(): void;
|
|
32
|
+
/** Exports one snapshot immediately. */
|
|
33
|
+
collect(): Promise<void>;
|
|
34
|
+
/** Exports a final snapshot and stops. Safe to call more than once. */
|
|
35
|
+
shutdown(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/** Creates a periodic metric reader. */
|
|
38
|
+
export declare function createPeriodicMetricReader(options: PeriodicMetricReaderOptions): PeriodicMetricReader;
|
|
39
|
+
//# sourceMappingURL=metrics.reader.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Metric Reader
|
|
3
|
+
*
|
|
4
|
+
* Metrics are pull-based in-process: the registry holds live numbers and
|
|
5
|
+
* nothing moves them anywhere. This reader is the missing half — it snapshots
|
|
6
|
+
* the registry on an interval and hands the snapshots to an exporter, so a
|
|
7
|
+
* configured `metricExporter` actually receives data.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_INTERVAL_MS = 60_000;
|
|
10
|
+
/** Snapshots a registry on an interval and exports the result. */
|
|
11
|
+
export class PeriodicMetricReader {
|
|
12
|
+
registry;
|
|
13
|
+
exporter;
|
|
14
|
+
intervalMs;
|
|
15
|
+
onError;
|
|
16
|
+
timer;
|
|
17
|
+
started = false;
|
|
18
|
+
shuttingDown = false;
|
|
19
|
+
inFlight;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.registry = options.registry;
|
|
22
|
+
this.exporter = options.exporter;
|
|
23
|
+
this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
24
|
+
this.onError = options.onError;
|
|
25
|
+
}
|
|
26
|
+
/** Begins periodic export. Calling it twice is a no-op. */
|
|
27
|
+
start() {
|
|
28
|
+
if (this.started || this.shuttingDown || this.intervalMs <= 0)
|
|
29
|
+
return;
|
|
30
|
+
this.started = true;
|
|
31
|
+
this.timer = setInterval(() => {
|
|
32
|
+
void this.collect();
|
|
33
|
+
}, this.intervalMs);
|
|
34
|
+
if (typeof this.timer === "object" && "unref" in this.timer) {
|
|
35
|
+
this.timer.unref();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Exports one snapshot immediately. */
|
|
39
|
+
async collect() {
|
|
40
|
+
if (this.inFlight) {
|
|
41
|
+
await this.inFlight;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const run = (async () => {
|
|
45
|
+
const snapshots = this.registry.getAll();
|
|
46
|
+
if (snapshots.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
try {
|
|
49
|
+
await this.exporter.export(snapshots);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
this.onError?.(error, "MetricExporter.export");
|
|
53
|
+
}
|
|
54
|
+
})();
|
|
55
|
+
this.inFlight = run;
|
|
56
|
+
try {
|
|
57
|
+
await run;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
if (this.inFlight === run)
|
|
61
|
+
this.inFlight = undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Exports a final snapshot and stops. Safe to call more than once. */
|
|
65
|
+
async shutdown() {
|
|
66
|
+
if (this.shuttingDown)
|
|
67
|
+
return;
|
|
68
|
+
this.shuttingDown = true;
|
|
69
|
+
if (this.timer !== undefined) {
|
|
70
|
+
clearInterval(this.timer);
|
|
71
|
+
this.timer = undefined;
|
|
72
|
+
}
|
|
73
|
+
await this.collect();
|
|
74
|
+
await this.exporter.shutdown();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Creates a periodic metric reader. */
|
|
78
|
+
export function createPeriodicMetricReader(options) {
|
|
79
|
+
return new PeriodicMetricReader(options);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=metrics.reader.js.map
|
|
@@ -4,20 +4,71 @@
|
|
|
4
4
|
* Central registry for all metrics. Creates and caches metrics by name+labels.
|
|
5
5
|
*/
|
|
6
6
|
import type { Counter, Gauge, Histogram, MetricsRegistry, MetricSnapshot } from "../types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Builds the cache key for one series.
|
|
9
|
+
*
|
|
10
|
+
* Label keys and values are JSON-encoded, so `{ a: "b,c=d" }` and
|
|
11
|
+
* `{ a: "b", c: "d" }` cannot collapse onto the same key the way a bare
|
|
12
|
+
* `k=v` join lets them.
|
|
13
|
+
*/
|
|
14
|
+
export declare function metricKey(type: string, name: string, labels?: Record<string, string>): string;
|
|
15
|
+
/** Options for {@link DefaultMetricsRegistry}. */
|
|
16
|
+
export interface MetricsRegistryOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Maximum number of distinct series held at once. Default: 10,000.
|
|
19
|
+
*
|
|
20
|
+
* A label carrying a user ID or a path with IDs in it turns an unbounded
|
|
21
|
+
* registry into a memory leak, so the cap is on by default and creating a
|
|
22
|
+
* series past it reports the offending metric instead of growing silently.
|
|
23
|
+
*/
|
|
24
|
+
readonly maxSeries?: number;
|
|
25
|
+
/** Bucket boundaries for histograms created by this registry. */
|
|
26
|
+
readonly histogramBoundaries?: readonly number[];
|
|
27
|
+
/** Called when the series cap is hit, once per rejected series. */
|
|
28
|
+
readonly onCardinalityLimit?: (name: string, size: number) => void;
|
|
29
|
+
}
|
|
7
30
|
/**
|
|
8
31
|
* In-memory metrics registry. Creates, caches, and manages metrics.
|
|
9
32
|
*/
|
|
10
33
|
export declare class DefaultMetricsRegistry implements MetricsRegistry {
|
|
11
34
|
private readonly metrics;
|
|
35
|
+
/**
|
|
36
|
+
* The type each metric name was first registered as.
|
|
37
|
+
*
|
|
38
|
+
* The cache key includes the type, so the map alone can never surface a
|
|
39
|
+
* differently-typed entry — the conflict check that reads it was dead code,
|
|
40
|
+
* and `counter("x")` followed by `gauge("x")` produced two same-named series
|
|
41
|
+
* of different types in one document. An OTLP or Prometheus backend rejects
|
|
42
|
+
* that, so the whole scrape is lost rather than one metric.
|
|
43
|
+
*/
|
|
44
|
+
private readonly typeByName;
|
|
45
|
+
private readonly maxSeries;
|
|
46
|
+
private readonly histogramBoundaries?;
|
|
47
|
+
private readonly onCardinalityLimit?;
|
|
48
|
+
/** Series rejected by the cap, reused so callers still get a usable object. */
|
|
49
|
+
private readonly overflow;
|
|
50
|
+
/**
|
|
51
|
+
* Cap on the detached-series cache. Bounded well below `maxSeries` so the
|
|
52
|
+
* documented ceiling is not quietly doubled by the overflow path.
|
|
53
|
+
*/
|
|
54
|
+
private readonly maxOverflow;
|
|
55
|
+
constructor(options?: MetricsRegistryOptions);
|
|
56
|
+
private create;
|
|
57
|
+
private obtain;
|
|
12
58
|
counter(name: string, labels?: Record<string, string>): Counter;
|
|
13
59
|
gauge(name: string, labels?: Record<string, string>): Gauge;
|
|
14
60
|
histogram(name: string, labels?: Record<string, string>): Histogram;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
61
|
+
private lookup;
|
|
62
|
+
getCounter(name: string, labels?: Record<string, string>): Counter | undefined;
|
|
63
|
+
getGauge(name: string, labels?: Record<string, string>): Gauge | undefined;
|
|
64
|
+
getHistogram(name: string, labels?: Record<string, string>): Histogram | undefined;
|
|
65
|
+
getSeries(name: string): readonly MetricSnapshot[];
|
|
66
|
+
private snapshot;
|
|
18
67
|
getAll(): MetricSnapshot[];
|
|
68
|
+
size(): number;
|
|
19
69
|
reset(): void;
|
|
70
|
+
clear(): void;
|
|
20
71
|
}
|
|
21
72
|
/** Creates a metrics registry. */
|
|
22
|
-
export declare function createMetricsRegistry(): DefaultMetricsRegistry;
|
|
73
|
+
export declare function createMetricsRegistry(options?: MetricsRegistryOptions): DefaultMetricsRegistry;
|
|
23
74
|
//# sourceMappingURL=metrics.registry.d.ts.map
|