@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
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { LogLevel } from "../types.js";
|
|
9
9
|
import { StructuredLogger } from "../logger/index.js";
|
|
10
|
-
import { DefaultMetricsRegistry } from "../metrics/index.js";
|
|
10
|
+
import { DefaultMetricsRegistry, PeriodicMetricReader, } from "../metrics/index.js";
|
|
11
11
|
import { DefaultTracer } from "../tracing/index.js";
|
|
12
12
|
import { AsyncPropagationManager } from "../propagation/index.js";
|
|
13
|
-
import { ConsoleLogExporter, ConsoleSpanExporter, ConsoleMetricExporter, } from "../exporter/index.js";
|
|
14
|
-
import { BatchSpanProcessor } from "../processor/index.js";
|
|
13
|
+
import { ConsoleLogExporter, ConsoleSpanExporter, ConsoleMetricExporter, noopLogExporter, noopMetricExporter, } from "../exporter/index.js";
|
|
14
|
+
import { BatchLogProcessor, BatchSpanProcessor, noopSpanExporter, } from "../processor/index.js";
|
|
15
|
+
import { createRedactor, createStructureRedactor } from "../redaction/index.js";
|
|
16
|
+
import { AlwaysOnSampler } from "../sampling/index.js";
|
|
15
17
|
/**
|
|
16
18
|
* Default observability implementation.
|
|
17
19
|
*
|
|
@@ -23,66 +25,220 @@ export class DefaultObservability {
|
|
|
23
25
|
metrics;
|
|
24
26
|
tracer;
|
|
25
27
|
propagation;
|
|
28
|
+
pipeline;
|
|
26
29
|
resourceAttributes;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.logger =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
write: (record) => {
|
|
47
|
-
void logExporter.export([record]);
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
// Metrics
|
|
52
|
-
this.metrics = new DefaultMetricsRegistry();
|
|
53
|
-
// Tracing
|
|
54
|
-
const spanExporter = config.spanExporter ?? new ConsoleSpanExporter();
|
|
55
|
-
const processor = new BatchSpanProcessor({ exporter: spanExporter });
|
|
30
|
+
/** Scopes share the parent's pipeline, so only the root tears it down. */
|
|
31
|
+
ownsPipeline;
|
|
32
|
+
shutdownPromise;
|
|
33
|
+
constructor(config, scope) {
|
|
34
|
+
this.resourceAttributes =
|
|
35
|
+
scope?.resourceAttributes ?? buildResourceAttributes(config);
|
|
36
|
+
if (scope) {
|
|
37
|
+
this.pipeline = scope.pipeline;
|
|
38
|
+
this.ownsPipeline = false;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
this.pipeline = buildPipeline(config);
|
|
42
|
+
this.ownsPipeline = true;
|
|
43
|
+
}
|
|
44
|
+
this.logger = this.pipeline.logger;
|
|
45
|
+
this.metrics = this.pipeline.metrics;
|
|
46
|
+
this.propagation = this.pipeline.propagation;
|
|
47
|
+
// The tracer is the one piece that differs per scope, because the
|
|
48
|
+
// resource attributes it stamps onto spans are the scope.
|
|
56
49
|
this.tracer = new DefaultTracer({
|
|
57
|
-
processors:
|
|
58
|
-
exporter: spanExporter,
|
|
50
|
+
processors: this.pipeline.processors,
|
|
59
51
|
resource: this.resourceAttributes,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
await processor.shutdown();
|
|
66
|
-
await spanExporter.shutdown();
|
|
67
|
-
await logExporter.shutdown();
|
|
52
|
+
sampler: this.pipeline.sampler ?? new AlwaysOnSampler(),
|
|
53
|
+
limits: this.pipeline.config.spanLimits,
|
|
54
|
+
captureStackTraces: this.pipeline.config.captureStackTraces,
|
|
55
|
+
redactAttribute: this.pipeline.redactAttribute,
|
|
56
|
+
onError: this.pipeline.config.onError,
|
|
68
57
|
});
|
|
69
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Creates a scope that differs only in its resource attributes.
|
|
61
|
+
*
|
|
62
|
+
* The logger, registry, processors and exporters are shared with the parent:
|
|
63
|
+
* building a second pipeline here silently dropped every configured
|
|
64
|
+
* exporter and log level, and left an orphan flush timer nobody shut down.
|
|
65
|
+
*/
|
|
70
66
|
resource(attributes) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
return new DefaultObservability(this.pipeline.config, {
|
|
68
|
+
pipeline: this.pipeline,
|
|
69
|
+
resourceAttributes: { ...this.resourceAttributes, ...attributes },
|
|
74
70
|
});
|
|
75
|
-
return child;
|
|
76
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Drains every buffer without shutting anything down.
|
|
74
|
+
*
|
|
75
|
+
* A scope shares its parent's pipeline, so draining from one is both safe
|
|
76
|
+
* and what the caller asked for. Returning early because the scope does not
|
|
77
|
+
* *own* the pipeline made `obs.resource({...}).flush()` a silent no-op —
|
|
78
|
+
* the buffered records it was meant to push were still sitting in the queue
|
|
79
|
+
* when the caller went on to exit.
|
|
80
|
+
*/
|
|
81
|
+
async flush() {
|
|
82
|
+
await this.drain();
|
|
83
|
+
}
|
|
84
|
+
async drain() {
|
|
85
|
+
const tasks = [];
|
|
86
|
+
if (this.pipeline.logProcessor) {
|
|
87
|
+
tasks.push(this.pipeline.logProcessor.flush());
|
|
88
|
+
}
|
|
89
|
+
for (const processor of this.pipeline.processors) {
|
|
90
|
+
if (processor.forceFlush)
|
|
91
|
+
tasks.push(processor.forceFlush());
|
|
92
|
+
}
|
|
93
|
+
tasks.push(this.pipeline.metricReader.collect());
|
|
94
|
+
await this.reportFailures(await Promise.allSettled(tasks), "flush");
|
|
95
|
+
}
|
|
96
|
+
async reportFailures(results, source) {
|
|
97
|
+
for (const result of results) {
|
|
98
|
+
if (result.status === "rejected") {
|
|
99
|
+
this.pipeline.config.onError?.(result.reason, source);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Shuts the pipeline down. Idempotent, and one failing step never skips the
|
|
105
|
+
* rest — a half-torn-down telemetry stack is worse than a noisy one.
|
|
106
|
+
*
|
|
107
|
+
* A scope created by {@link DefaultObservability.resource} does not own the
|
|
108
|
+
* pipeline and shutting it down is a no-op; shut down the root instead.
|
|
109
|
+
*/
|
|
77
110
|
async shutdown() {
|
|
78
|
-
|
|
79
|
-
|
|
111
|
+
if (!this.ownsPipeline)
|
|
112
|
+
return;
|
|
113
|
+
this.shutdownPromise ??= this.performShutdown();
|
|
114
|
+
return this.shutdownPromise;
|
|
115
|
+
}
|
|
116
|
+
async performShutdown() {
|
|
117
|
+
// Drain first: whatever is still queued should reach the backend before
|
|
118
|
+
// the exporters close.
|
|
119
|
+
await this.drain();
|
|
120
|
+
const steps = [];
|
|
121
|
+
steps.push(this.pipeline.metricReader.shutdown());
|
|
122
|
+
if (this.pipeline.logProcessor) {
|
|
123
|
+
steps.push(this.pipeline.logProcessor.shutdown());
|
|
80
124
|
}
|
|
81
|
-
|
|
82
|
-
|
|
125
|
+
for (const processor of this.pipeline.processors) {
|
|
126
|
+
steps.push(processor.shutdown());
|
|
83
127
|
}
|
|
128
|
+
// Every exporter this facade created is owned by exactly one processor or
|
|
129
|
+
// reader, which closes it in the step above; an exporter supplied by the
|
|
130
|
+
// caller is therefore never closed twice.
|
|
131
|
+
await this.reportFailures(await Promise.allSettled(steps), "shutdown");
|
|
84
132
|
}
|
|
85
133
|
}
|
|
134
|
+
function buildResourceAttributes(config) {
|
|
135
|
+
return {
|
|
136
|
+
"service.name": config.serviceName,
|
|
137
|
+
...(config.serviceVersion
|
|
138
|
+
? { "service.version": config.serviceVersion }
|
|
139
|
+
: {}),
|
|
140
|
+
...(config.environment
|
|
141
|
+
? { "deployment.environment": config.environment }
|
|
142
|
+
: {}),
|
|
143
|
+
...(config.resource ?? {}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function buildPipeline(config) {
|
|
147
|
+
const useConsole = config.useConsoleExporters ?? true;
|
|
148
|
+
/* ── Logging ─────────────────────────────────────────────────────────── */
|
|
149
|
+
const logExporter = config.logExporter ??
|
|
150
|
+
(useConsole ? new ConsoleLogExporter() : noopLogExporter);
|
|
151
|
+
const logProcessor = new BatchLogProcessor({
|
|
152
|
+
exporter: logExporter,
|
|
153
|
+
batchSize: config.logBatchSize,
|
|
154
|
+
flushIntervalMs: config.logFlushIntervalMs,
|
|
155
|
+
onError: config.onError,
|
|
156
|
+
onDrop: (dropped) => config.onError?.(new Error(`Dropped ${dropped} log records: queue full`), "BatchLogProcessor"),
|
|
157
|
+
});
|
|
158
|
+
// Redaction is applied by the logger, so every transport and exporter
|
|
159
|
+
// downstream sees redacted records — configuring it and not wiring it here
|
|
160
|
+
// is what made the "credentials are never logged" promise untrue.
|
|
161
|
+
const redactor = config.redaction
|
|
162
|
+
? createStructureRedactor(config.redaction)
|
|
163
|
+
: undefined;
|
|
164
|
+
const logger = new StructuredLogger({
|
|
165
|
+
name: config.serviceName,
|
|
166
|
+
level: config.logLevel ?? LogLevel.INFO,
|
|
167
|
+
transport: logProcessor,
|
|
168
|
+
redact: redactor
|
|
169
|
+
? (context) => redactor(context)
|
|
170
|
+
: undefined,
|
|
171
|
+
});
|
|
172
|
+
/* ── Tracing ─────────────────────────────────────────────────────────── */
|
|
173
|
+
// Built only when this facade owns the processor: constructing an exporter
|
|
174
|
+
// the caller's own processors will never touch is waste at best and a
|
|
175
|
+
// second, unclosed handle at worst.
|
|
176
|
+
const ownSpanExporter = () => config.spanExporter ??
|
|
177
|
+
(useConsole ? new ConsoleSpanExporter() : noopSpanExporter);
|
|
178
|
+
const processors = config.processors ?? [
|
|
179
|
+
new BatchSpanProcessor({
|
|
180
|
+
exporter: ownSpanExporter(),
|
|
181
|
+
onError: config.onError,
|
|
182
|
+
onDrop: (dropped) => config.onError?.(new Error(`Dropped ${dropped} spans: queue full`), "BatchSpanProcessor"),
|
|
183
|
+
}),
|
|
184
|
+
];
|
|
185
|
+
// When the caller supplied their own processors, they own the exporter's
|
|
186
|
+
// lifecycle too; otherwise our processor closes it on shutdown.
|
|
187
|
+
/* ── Metrics ─────────────────────────────────────────────────────────── */
|
|
188
|
+
const metrics = new DefaultMetricsRegistry({
|
|
189
|
+
...(config.metrics ?? {}),
|
|
190
|
+
onCardinalityLimit: (name, size) => {
|
|
191
|
+
config.metrics?.onCardinalityLimit?.(name, size);
|
|
192
|
+
config.onError?.(new Error(`Metric "${name}" exceeded the registry's series limit (${size}); ` +
|
|
193
|
+
`check for a high-cardinality label`), "MetricsRegistry");
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
const metricExporter = config.metricExporter ??
|
|
197
|
+
(useConsole ? new ConsoleMetricExporter() : undefined);
|
|
198
|
+
// The reader is built whenever there is anything to export to, even at
|
|
199
|
+
// interval 0. `start()` is a no-op at 0, so periodic export stays disabled
|
|
200
|
+
// as documented — but `flush()` and `shutdown()` still collect a final
|
|
201
|
+
// snapshot and close the exporter. Skipping the reader entirely left a
|
|
202
|
+
// console exporter this facade had constructed running to the end of the
|
|
203
|
+
// process with no metric ever leaving it.
|
|
204
|
+
const metricIntervalMs = config.metricExportIntervalMs ?? 60_000;
|
|
205
|
+
const metricReader = new PeriodicMetricReader({
|
|
206
|
+
registry: metrics,
|
|
207
|
+
exporter: metricExporter ?? noopMetricExporter,
|
|
208
|
+
intervalMs: metricIntervalMs,
|
|
209
|
+
onError: config.onError,
|
|
210
|
+
});
|
|
211
|
+
metricReader.start();
|
|
212
|
+
const redactAttribute = config.redaction
|
|
213
|
+
? buildAttributeRedactor(config.redaction)
|
|
214
|
+
: undefined;
|
|
215
|
+
return {
|
|
216
|
+
logger,
|
|
217
|
+
metrics,
|
|
218
|
+
propagation: new AsyncPropagationManager(),
|
|
219
|
+
processors,
|
|
220
|
+
logProcessor,
|
|
221
|
+
metricReader,
|
|
222
|
+
config,
|
|
223
|
+
sampler: config.sampler,
|
|
224
|
+
redactAttribute,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Builds the span-attribute redactor.
|
|
229
|
+
*
|
|
230
|
+
* A sensitive key replaces its whole value; anything else is still walked, so
|
|
231
|
+
* a token nested inside an otherwise innocuous `request` attribute is caught
|
|
232
|
+
* too.
|
|
233
|
+
*/
|
|
234
|
+
function buildAttributeRedactor(redaction) {
|
|
235
|
+
const leaf = createRedactor(redaction);
|
|
236
|
+
const deep = createStructureRedactor(redaction);
|
|
237
|
+
return (key, value) => {
|
|
238
|
+
const replaced = leaf(key, value);
|
|
239
|
+
return replaced === value ? deep(value) : replaced;
|
|
240
|
+
};
|
|
241
|
+
}
|
|
86
242
|
/** Creates an observability instance. */
|
|
87
243
|
export function createObservability(config) {
|
|
88
244
|
return new DefaultObservability(config);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @zudojs/observability —
|
|
2
|
+
* @zudojs/observability — Processors
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Batching and simple processors for spans, and a batching log transport.
|
|
5
5
|
*/
|
|
6
|
-
export { BatchSpanProcessor, createBatchSpanProcessor, } from "./processor.batch.js";
|
|
6
|
+
export { BatchSpanProcessor, createBatchSpanProcessor, SimpleSpanProcessor, createSimpleSpanProcessor, noopSpanExporter, type BatchSpanProcessorOptions, } from "./processor.batch.js";
|
|
7
|
+
export { BatchLogProcessor, createBatchLogProcessor, type BatchLogProcessorOptions, } from "./processor.log.js";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/processor/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @zudojs/observability —
|
|
2
|
+
* @zudojs/observability — Processors
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Batching and simple processors for spans, and a batching log transport.
|
|
5
5
|
*/
|
|
6
|
-
export { BatchSpanProcessor, createBatchSpanProcessor, } from "./processor.batch.js";
|
|
6
|
+
export { BatchSpanProcessor, createBatchSpanProcessor, SimpleSpanProcessor, createSimpleSpanProcessor, noopSpanExporter, } from "./processor.batch.js";
|
|
7
|
+
export { BatchLogProcessor, createBatchLogProcessor, } from "./processor.log.js";
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -2,9 +2,27 @@
|
|
|
2
2
|
* @zudojs/observability — Processor
|
|
3
3
|
*
|
|
4
4
|
* Batch span processor that accumulates spans and exports them periodically.
|
|
5
|
-
*
|
|
5
|
+
* Genuinely memory-bounded: the queue has a hard cap, and spans past it are
|
|
6
|
+
* dropped and counted rather than growing the process until it dies.
|
|
6
7
|
*/
|
|
7
8
|
import type { ReadableSpan, Span, SpanExporter, SpanProcessor } from "../types.js";
|
|
9
|
+
/** A span exporter that discards everything, for tests and disabled tracing. */
|
|
10
|
+
export declare const noopSpanExporter: SpanExporter;
|
|
11
|
+
/** Options for {@link BatchSpanProcessor}. */
|
|
12
|
+
export interface BatchSpanProcessorOptions {
|
|
13
|
+
/** Where completed spans go. Required — a processor with no exporter is a leak with extra steps. */
|
|
14
|
+
readonly exporter: SpanExporter;
|
|
15
|
+
/** Spans per export call. Default: 512. */
|
|
16
|
+
readonly batchSize?: number;
|
|
17
|
+
/** How often the queue is drained, in ms. Default: 5,000. */
|
|
18
|
+
readonly flushIntervalMs?: number;
|
|
19
|
+
/** Hard cap on queued spans. Default: 2,048. */
|
|
20
|
+
readonly maxQueueSize?: number;
|
|
21
|
+
/** Reports export failures and dropped spans. */
|
|
22
|
+
readonly onError?: (error: unknown, source: string) => void;
|
|
23
|
+
/** Called when spans are dropped because the queue was full. */
|
|
24
|
+
readonly onDrop?: (droppedCount: number) => void;
|
|
25
|
+
}
|
|
8
26
|
/**
|
|
9
27
|
* Collects completed spans and exports them in batches.
|
|
10
28
|
* Flushes on batch size or interval, whichever comes first.
|
|
@@ -13,23 +31,60 @@ export declare class BatchSpanProcessor implements SpanProcessor {
|
|
|
13
31
|
private readonly exporter;
|
|
14
32
|
private readonly batchSize;
|
|
15
33
|
private readonly flushIntervalMs;
|
|
34
|
+
private readonly maxQueueSize;
|
|
35
|
+
private readonly onError?;
|
|
36
|
+
private readonly onDrop?;
|
|
16
37
|
private buffer;
|
|
17
38
|
private timer?;
|
|
18
39
|
private shuttingDown;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
readonly flushIntervalMs?: number;
|
|
23
|
-
});
|
|
40
|
+
private inFlight?;
|
|
41
|
+
private droppedSpans;
|
|
42
|
+
constructor(options: BatchSpanProcessorOptions);
|
|
24
43
|
onStart(_span: Span): void;
|
|
25
44
|
onEnd(span: ReadableSpan): void;
|
|
45
|
+
private startTimer;
|
|
46
|
+
private stopTimer;
|
|
47
|
+
/** Number of spans dropped because the queue was full. */
|
|
48
|
+
getDroppedCount(): number;
|
|
49
|
+
/** Spans currently queued. */
|
|
50
|
+
getQueueSize(): number;
|
|
51
|
+
/**
|
|
52
|
+
* Exports everything queued.
|
|
53
|
+
*
|
|
54
|
+
* Concurrent calls are serialised: two overlapping exports against the same
|
|
55
|
+
* backend is a good way to double-deliver a batch or exhaust a connection
|
|
56
|
+
* pool.
|
|
57
|
+
*/
|
|
26
58
|
flush(): Promise<void>;
|
|
59
|
+
private drain;
|
|
60
|
+
/** Drains the queue without shutting the processor down. */
|
|
61
|
+
forceFlush(): Promise<void>;
|
|
27
62
|
shutdown(): Promise<void>;
|
|
28
63
|
}
|
|
29
64
|
/** Creates a batch span processor. */
|
|
30
|
-
export declare function createBatchSpanProcessor(options
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
65
|
+
export declare function createBatchSpanProcessor(options: BatchSpanProcessorOptions): BatchSpanProcessor;
|
|
66
|
+
/**
|
|
67
|
+
* A processor that exports each span as it ends.
|
|
68
|
+
*
|
|
69
|
+
* Simple and immediate; use it in tests and short-lived processes, and the
|
|
70
|
+
* batch processor everywhere else.
|
|
71
|
+
*/
|
|
72
|
+
export declare class SimpleSpanProcessor implements SpanProcessor {
|
|
73
|
+
private readonly exporter;
|
|
74
|
+
private readonly onError?;
|
|
75
|
+
private pending;
|
|
76
|
+
constructor(options: {
|
|
77
|
+
readonly exporter: SpanExporter;
|
|
78
|
+
readonly onError?: (error: unknown, source: string) => void;
|
|
79
|
+
});
|
|
80
|
+
onStart(_span: Span): void;
|
|
81
|
+
onEnd(span: ReadableSpan): void;
|
|
82
|
+
forceFlush(): Promise<void>;
|
|
83
|
+
shutdown(): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
/** Creates a simple, unbatched span processor. */
|
|
86
|
+
export declare function createSimpleSpanProcessor(options: {
|
|
87
|
+
readonly exporter: SpanExporter;
|
|
88
|
+
readonly onError?: (error: unknown, source: string) => void;
|
|
89
|
+
}): SimpleSpanProcessor;
|
|
35
90
|
//# sourceMappingURL=processor.batch.d.ts.map
|
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
* @zudojs/observability — Processor
|
|
3
3
|
*
|
|
4
4
|
* Batch span processor that accumulates spans and exports them periodically.
|
|
5
|
-
*
|
|
5
|
+
* Genuinely memory-bounded: the queue has a hard cap, and spans past it are
|
|
6
|
+
* dropped and counted rather than growing the process until it dies.
|
|
6
7
|
*/
|
|
7
8
|
const DEFAULT_BATCH_SIZE = 512;
|
|
8
9
|
const DEFAULT_FLUSH_INTERVAL_MS = 5_000;
|
|
10
|
+
const DEFAULT_MAX_QUEUE_SIZE = 2_048;
|
|
11
|
+
/** A span exporter that discards everything, for tests and disabled tracing. */
|
|
12
|
+
export const noopSpanExporter = {
|
|
13
|
+
export: async () => { },
|
|
14
|
+
shutdown: async () => { },
|
|
15
|
+
};
|
|
9
16
|
/**
|
|
10
17
|
* Collects completed spans and exports them in batches.
|
|
11
18
|
* Flushes on batch size or interval, whichever comes first.
|
|
@@ -14,64 +21,158 @@ export class BatchSpanProcessor {
|
|
|
14
21
|
exporter;
|
|
15
22
|
batchSize;
|
|
16
23
|
flushIntervalMs;
|
|
24
|
+
maxQueueSize;
|
|
25
|
+
onError;
|
|
26
|
+
onDrop;
|
|
17
27
|
buffer = [];
|
|
18
28
|
timer;
|
|
19
29
|
shuttingDown = false;
|
|
30
|
+
inFlight;
|
|
31
|
+
droppedSpans = 0;
|
|
20
32
|
constructor(options) {
|
|
21
|
-
this.exporter = options
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
27
|
-
options?.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
33
|
+
this.exporter = options.exporter;
|
|
34
|
+
this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
35
|
+
this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
36
|
+
this.maxQueueSize = Math.max(options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, this.batchSize);
|
|
37
|
+
this.onError = options.onError;
|
|
38
|
+
this.onDrop = options.onDrop;
|
|
28
39
|
}
|
|
29
40
|
onStart(_span) {
|
|
30
|
-
// No-op: we only care about completed spans
|
|
41
|
+
// No-op: we only care about completed spans.
|
|
31
42
|
}
|
|
32
43
|
onEnd(span) {
|
|
33
44
|
if (this.shuttingDown)
|
|
34
45
|
return;
|
|
46
|
+
if (this.buffer.length >= this.maxQueueSize) {
|
|
47
|
+
this.droppedSpans++;
|
|
48
|
+
this.onDrop?.(this.droppedSpans);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
35
51
|
this.buffer.push(span);
|
|
36
52
|
if (this.buffer.length >= this.batchSize) {
|
|
37
53
|
void this.flush();
|
|
38
54
|
return;
|
|
39
55
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
this.startTimer();
|
|
57
|
+
}
|
|
58
|
+
startTimer() {
|
|
59
|
+
if (this.timer !== undefined || this.shuttingDown)
|
|
60
|
+
return;
|
|
61
|
+
this.timer = setInterval(() => {
|
|
62
|
+
if (this.buffer.length === 0) {
|
|
63
|
+
// Nothing left to drain: stop waking the event loop until the next
|
|
64
|
+
// span arrives, rather than ticking forever for the process lifetime.
|
|
65
|
+
this.stopTimer();
|
|
66
|
+
return;
|
|
49
67
|
}
|
|
68
|
+
void this.flush();
|
|
69
|
+
}, this.flushIntervalMs);
|
|
70
|
+
// Allow the process to exit even if the timer is active.
|
|
71
|
+
if (typeof this.timer === "object" && "unref" in this.timer) {
|
|
72
|
+
this.timer.unref();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
stopTimer() {
|
|
76
|
+
if (this.timer !== undefined) {
|
|
77
|
+
clearInterval(this.timer);
|
|
78
|
+
this.timer = undefined;
|
|
50
79
|
}
|
|
51
80
|
}
|
|
81
|
+
/** Number of spans dropped because the queue was full. */
|
|
82
|
+
getDroppedCount() {
|
|
83
|
+
return this.droppedSpans;
|
|
84
|
+
}
|
|
85
|
+
/** Spans currently queued. */
|
|
86
|
+
getQueueSize() {
|
|
87
|
+
return this.buffer.length;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Exports everything queued.
|
|
91
|
+
*
|
|
92
|
+
* Concurrent calls are serialised: two overlapping exports against the same
|
|
93
|
+
* backend is a good way to double-deliver a batch or exhaust a connection
|
|
94
|
+
* pool.
|
|
95
|
+
*/
|
|
52
96
|
async flush() {
|
|
53
|
-
if (this.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
97
|
+
if (this.inFlight) {
|
|
98
|
+
await this.inFlight;
|
|
99
|
+
if (this.buffer.length === 0)
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const run = this.drain();
|
|
103
|
+
this.inFlight = run;
|
|
57
104
|
try {
|
|
58
|
-
await
|
|
105
|
+
await run;
|
|
59
106
|
}
|
|
60
|
-
|
|
61
|
-
|
|
107
|
+
finally {
|
|
108
|
+
if (this.inFlight === run)
|
|
109
|
+
this.inFlight = undefined;
|
|
62
110
|
}
|
|
63
111
|
}
|
|
112
|
+
async drain() {
|
|
113
|
+
while (this.buffer.length > 0) {
|
|
114
|
+
const batch = this.buffer.splice(0, this.batchSize);
|
|
115
|
+
try {
|
|
116
|
+
await this.exporter.export(batch);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// Telemetry failure must not bring down the application — but it must
|
|
120
|
+
// not be invisible either.
|
|
121
|
+
this.onError?.(error, "SpanExporter.export");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Drains the queue without shutting the processor down. */
|
|
126
|
+
async forceFlush() {
|
|
127
|
+
await this.flush();
|
|
128
|
+
}
|
|
64
129
|
async shutdown() {
|
|
65
130
|
this.shuttingDown = true;
|
|
66
|
-
|
|
67
|
-
clearInterval(this.timer);
|
|
68
|
-
this.timer = undefined;
|
|
69
|
-
}
|
|
131
|
+
this.stopTimer();
|
|
70
132
|
await this.flush();
|
|
133
|
+
await this.exporter.shutdown();
|
|
71
134
|
}
|
|
72
135
|
}
|
|
73
136
|
/** Creates a batch span processor. */
|
|
74
137
|
export function createBatchSpanProcessor(options) {
|
|
75
138
|
return new BatchSpanProcessor(options);
|
|
76
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* A processor that exports each span as it ends.
|
|
142
|
+
*
|
|
143
|
+
* Simple and immediate; use it in tests and short-lived processes, and the
|
|
144
|
+
* batch processor everywhere else.
|
|
145
|
+
*/
|
|
146
|
+
export class SimpleSpanProcessor {
|
|
147
|
+
exporter;
|
|
148
|
+
onError;
|
|
149
|
+
pending = new Set();
|
|
150
|
+
constructor(options) {
|
|
151
|
+
this.exporter = options.exporter;
|
|
152
|
+
this.onError = options.onError;
|
|
153
|
+
}
|
|
154
|
+
onStart(_span) { }
|
|
155
|
+
onEnd(span) {
|
|
156
|
+
const task = this.exporter
|
|
157
|
+
.export([span])
|
|
158
|
+
.catch((error) => {
|
|
159
|
+
this.onError?.(error, "SpanExporter.export");
|
|
160
|
+
})
|
|
161
|
+
.finally(() => {
|
|
162
|
+
this.pending.delete(task);
|
|
163
|
+
});
|
|
164
|
+
this.pending.add(task);
|
|
165
|
+
}
|
|
166
|
+
async forceFlush() {
|
|
167
|
+
await Promise.allSettled([...this.pending]);
|
|
168
|
+
}
|
|
169
|
+
async shutdown() {
|
|
170
|
+
await this.forceFlush();
|
|
171
|
+
await this.exporter.shutdown();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Creates a simple, unbatched span processor. */
|
|
175
|
+
export function createSimpleSpanProcessor(options) {
|
|
176
|
+
return new SimpleSpanProcessor(options);
|
|
177
|
+
}
|
|
77
178
|
//# sourceMappingURL=processor.batch.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Log Processor
|
|
3
|
+
*
|
|
4
|
+
* Buffers log records and hands them to an exporter in batches.
|
|
5
|
+
*
|
|
6
|
+
* A logger writes synchronously and cannot await anything, so the naive
|
|
7
|
+
* bridge — `void exporter.export([record])` — exports one record per line and
|
|
8
|
+
* turns any exporter rejection into an unhandled rejection, which ends the
|
|
9
|
+
* process under Node's default. This transport is that bridge done properly:
|
|
10
|
+
* bounded queue, batched export, errors reported, and a `flush()` that
|
|
11
|
+
* shutdown can await so the last records are not lost.
|
|
12
|
+
*/
|
|
13
|
+
import type { LogExporter, LogRecord, LogTransport } from "../types.js";
|
|
14
|
+
/** Options for {@link BatchLogProcessor}. */
|
|
15
|
+
export interface BatchLogProcessorOptions {
|
|
16
|
+
readonly exporter: LogExporter;
|
|
17
|
+
/** Records per export call. Default: 256. */
|
|
18
|
+
readonly batchSize?: number;
|
|
19
|
+
/** How often the queue is drained, in ms. Default: 1,000. */
|
|
20
|
+
readonly flushIntervalMs?: number;
|
|
21
|
+
/** Hard cap on queued records. Default: 4,096. */
|
|
22
|
+
readonly maxQueueSize?: number;
|
|
23
|
+
/** Reports export failures. */
|
|
24
|
+
readonly onError?: (error: unknown, source: string) => void;
|
|
25
|
+
/** Called when records are dropped because the queue was full. */
|
|
26
|
+
readonly onDrop?: (droppedCount: number) => void;
|
|
27
|
+
}
|
|
28
|
+
/** A {@link LogTransport} that batches records into a {@link LogExporter}. */
|
|
29
|
+
export declare class BatchLogProcessor implements LogTransport {
|
|
30
|
+
readonly name = "batch";
|
|
31
|
+
private readonly exporter;
|
|
32
|
+
private readonly batchSize;
|
|
33
|
+
private readonly flushIntervalMs;
|
|
34
|
+
private readonly maxQueueSize;
|
|
35
|
+
private readonly onError?;
|
|
36
|
+
private readonly onDrop?;
|
|
37
|
+
private buffer;
|
|
38
|
+
private timer?;
|
|
39
|
+
private shuttingDown;
|
|
40
|
+
private inFlight?;
|
|
41
|
+
private droppedRecords;
|
|
42
|
+
constructor(options: BatchLogProcessorOptions);
|
|
43
|
+
write(record: LogRecord): void;
|
|
44
|
+
private startTimer;
|
|
45
|
+
private stopTimer;
|
|
46
|
+
/** Records dropped because the queue was full. */
|
|
47
|
+
getDroppedCount(): number;
|
|
48
|
+
/** Records currently queued. */
|
|
49
|
+
getQueueSize(): number;
|
|
50
|
+
flush(): Promise<void>;
|
|
51
|
+
private drain;
|
|
52
|
+
shutdown(): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/** Creates a batching log transport. */
|
|
55
|
+
export declare function createBatchLogProcessor(options: BatchLogProcessorOptions): BatchLogProcessor;
|
|
56
|
+
//# sourceMappingURL=processor.log.d.ts.map
|