@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
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @zudojs/observability
|
|
2
2
|
|
|
3
|
-
Structured logging, metrics, tracing, context propagation, and exporters for Zudojs applications.
|
|
3
|
+
Structured logging, metrics, tracing, context propagation, and telemetry exporters for Zudojs applications.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -10,25 +10,272 @@ npm install @zudojs/observability
|
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
|
+
`createObservability` is the entry point. It wires the logger, the metrics
|
|
14
|
+
registry, the tracer and the propagation manager into one object, and owns
|
|
15
|
+
their shutdown.
|
|
16
|
+
|
|
13
17
|
```typescript
|
|
14
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
createObservability,
|
|
20
|
+
createProbabilitySampler,
|
|
21
|
+
LogLevel,
|
|
22
|
+
} from "@zudojs/observability";
|
|
23
|
+
|
|
24
|
+
const obs = createObservability({
|
|
25
|
+
serviceName: "checkout-api",
|
|
26
|
+
serviceVersion: "1.4.0",
|
|
27
|
+
environment: "production",
|
|
28
|
+
logLevel: LogLevel.INFO,
|
|
29
|
+
|
|
30
|
+
// Opt in to redaction — see "Redaction" below.
|
|
31
|
+
redaction: {},
|
|
32
|
+
|
|
33
|
+
// Sample 10% of traces. The decision is derived from the trace ID, so a
|
|
34
|
+
// trace is never sampled in half across services.
|
|
35
|
+
sampler: createProbabilitySampler(0.1),
|
|
36
|
+
|
|
37
|
+
// Console exporters are the development default; turn them off in
|
|
38
|
+
// production and supply real ones.
|
|
39
|
+
useConsoleExporters: false,
|
|
40
|
+
});
|
|
15
41
|
|
|
16
|
-
|
|
17
|
-
|
|
42
|
+
obs.logger.info("server started", { port: 3000 });
|
|
43
|
+
obs.metrics.counter("http.requests.total", { route: "/checkout" }).increment();
|
|
18
44
|
|
|
19
|
-
const span = tracer.startSpan("handle-request");
|
|
20
|
-
span.setAttribute("http.method", "
|
|
21
|
-
await handleRequest();
|
|
45
|
+
const span = obs.tracer.startSpan("handle-request");
|
|
46
|
+
span.setAttribute("http.method", "POST");
|
|
22
47
|
span.end();
|
|
48
|
+
|
|
49
|
+
await obs.shutdown(); // drains every buffer, then closes the exporters
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Logging
|
|
53
|
+
|
|
54
|
+
Records are structured, level-filtered, and stamped with the ambient trace.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
obs.logger.info("order placed", { orderId: 42 });
|
|
58
|
+
|
|
59
|
+
// An Error goes in the third parameter. Putting it in the context would
|
|
60
|
+
// serialize to {} — Error's fields are non-enumerable.
|
|
61
|
+
obs.logger.error("charge failed", { orderId: 42 }, error);
|
|
62
|
+
|
|
63
|
+
const child = obs.logger.child("payments", { provider: "stripe" });
|
|
64
|
+
child.setLevel(LogLevel.DEBUG); // adjustable at runtime
|
|
65
|
+
await obs.logger.flush();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Every record carries `traceId` and `spanId` when a propagation context is
|
|
69
|
+
active, so logs and traces line up without threading IDs by hand.
|
|
70
|
+
|
|
71
|
+
## Redaction
|
|
72
|
+
|
|
73
|
+
Redaction is off unless you configure it, and on once you do. The logger
|
|
74
|
+
applies it to every record before any transport sees it, and the tracer
|
|
75
|
+
applies it to every span attribute and span event attribute before any
|
|
76
|
+
processor or exporter sees it — a `Bearer` token attached to a span is
|
|
77
|
+
redacted the same way one written to a log field is.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const obs = createObservability({
|
|
81
|
+
serviceName: "api",
|
|
82
|
+
redaction: {
|
|
83
|
+
// Defaults cover passwords, tokens, cookies, keys and card numbers.
|
|
84
|
+
fields: ["password", "token", "ssn"],
|
|
85
|
+
patterns: [/^x-.*-secret$/i],
|
|
86
|
+
// "contains" (the default) matches on word boundaries: "userPassword"
|
|
87
|
+
// and "x-api-key" match, "shippingAddress" and "authorId" do not.
|
|
88
|
+
matchMode: "contains",
|
|
89
|
+
replacement: "[REDACTED]",
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
obs.logger.info("login", {
|
|
94
|
+
username: "ada",
|
|
95
|
+
password: "hunter2", // → "[REDACTED]"
|
|
96
|
+
headers: [{ authorization: "…" }], // arrays are traversed too
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Traversal handles the shapes secrets actually arrive in: arrays, nested
|
|
101
|
+
objects, and cyclic graphs (a request object in a log context becomes
|
|
102
|
+
`[CIRCULAR]` rather than a stack overflow). Class instances — `Error`, `Date`,
|
|
103
|
+
`Map` — are left intact instead of being flattened to `{}`.
|
|
104
|
+
|
|
105
|
+
`redactObject`, `redactValue` and `createStructureRedactor` are exported for
|
|
106
|
+
use outside the logger.
|
|
107
|
+
|
|
108
|
+
## Metrics
|
|
109
|
+
|
|
110
|
+
Counters, gauges and histograms, keyed by name **and** labels.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
obs.metrics.counter("jobs.processed", { queue: "email" }).increment();
|
|
114
|
+
obs.metrics.gauge("queue.depth", { queue: "email" }).setValue(17);
|
|
115
|
+
|
|
116
|
+
const latency = obs.metrics.histogram("http.duration", { route: "/checkout" });
|
|
117
|
+
latency.record(87);
|
|
118
|
+
|
|
119
|
+
const value = latency.getValue();
|
|
120
|
+
value.p95; // interpolated from the bucket boundaries
|
|
121
|
+
value.mean;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Histograms keep cumulative buckets, so `p50`/`p90`/`p95`/`p99` are real
|
|
125
|
+
answers rather than something you have to reconstruct from count and sum.
|
|
126
|
+
Pass your own boundaries when the unit is not milliseconds.
|
|
127
|
+
|
|
128
|
+
Values that cannot be aggregated — `NaN`, `Infinity`, a negative counter
|
|
129
|
+
increment — throw a `MetricValueError` rather than being silently dropped.
|
|
130
|
+
|
|
131
|
+
**Cardinality.** The registry caps the number of distinct series (default
|
|
132
|
+
10,000). A label carrying a user ID is the usual way to blow past that, and
|
|
133
|
+
the cap turns an unbounded memory leak into a reported error. Lower it — or
|
|
134
|
+
set the histogram boundaries used registry-wide — through `metrics`:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const obs = createObservability({
|
|
138
|
+
serviceName: "api",
|
|
139
|
+
metrics: { maxSeries: 2_000 },
|
|
140
|
+
onError: (error, source) => report(error, source), // cardinality is reported here
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
One metric name may only ever be one type: registering `counter("latency")`
|
|
145
|
+
and then `histogram("latency")` throws, because a document carrying the same
|
|
146
|
+
name as two types is rejected wholesale by OTLP and Prometheus.
|
|
147
|
+
|
|
148
|
+
Metrics are exported by a `PeriodicMetricReader`, which the facade starts for
|
|
149
|
+
you when a `metricExporter` is configured. `metricExportIntervalMs: 0`
|
|
150
|
+
disables the periodic export while still collecting a final snapshot on
|
|
151
|
+
`flush()` and `shutdown()`:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const obs = createObservability({
|
|
155
|
+
serviceName: "api",
|
|
156
|
+
metricExporter: myExporter,
|
|
157
|
+
metricExportIntervalMs: 30_000,
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Tracing
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const span = obs.tracer.startSpan("db.query", { kind: SpanKind.CLIENT });
|
|
165
|
+
try {
|
|
166
|
+
span.setAttribute("db.statement", sql);
|
|
167
|
+
return await run(sql);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
span.recordError(error as Error);
|
|
170
|
+
throw error;
|
|
171
|
+
} finally {
|
|
172
|
+
span.end();
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Spans are capped: 128 attributes, 128 events, 4096-character values by
|
|
177
|
+
default, with the overflow counted in `droppedAttributes` / `droppedEvents`.
|
|
178
|
+
Durations use a monotonic clock, so a clock adjustment cannot produce a
|
|
179
|
+
negative one. `setStatus(status, message)` keeps the message — it is exported
|
|
180
|
+
alongside the status.
|
|
181
|
+
|
|
182
|
+
Stack traces recorded by `recordError` reach the backend unredacted; set
|
|
183
|
+
`captureStackTraces: false` to omit them.
|
|
184
|
+
|
|
185
|
+
### Sampling
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import {
|
|
189
|
+
createAlwaysOnSampler,
|
|
190
|
+
createParentBasedSampler,
|
|
191
|
+
createProbabilitySampler,
|
|
192
|
+
} from "@zudojs/observability";
|
|
193
|
+
|
|
194
|
+
createObservability({
|
|
195
|
+
serviceName: "api",
|
|
196
|
+
sampler: createParentBasedSampler({ root: createProbabilitySampler(0.05) }),
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The decision is stamped into `SpanContext.traceFlags` and inherited by every
|
|
201
|
+
child, which is what keeps a sampled trace whole across services.
|
|
202
|
+
|
|
203
|
+
## Context propagation
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { createPropagationContext } from "@zudojs/observability";
|
|
207
|
+
|
|
208
|
+
await obs.propagation.run(createPropagationContext({ requestId }), async () => {
|
|
209
|
+
obs.logger.info("handling"); // carries traceId, spanId
|
|
210
|
+
await handle();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
obs.propagation.current(); // undefined outside a run() scope
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`current()` returns `undefined` when there is no active context, so "no trace"
|
|
217
|
+
stays distinguishable from a real one.
|
|
218
|
+
|
|
219
|
+
## Exporters
|
|
220
|
+
|
|
221
|
+
Console exporters ship for development. They serialize defensively — a
|
|
222
|
+
circular attribute or a BigInt cannot make an exporter throw inside the
|
|
223
|
+
logging path — and emit one line per record by default, which is what log
|
|
224
|
+
shippers parse.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import {
|
|
228
|
+
createConsoleSpanExporter,
|
|
229
|
+
createBatchSpanProcessor,
|
|
230
|
+
createSimpleSpanProcessor,
|
|
231
|
+
} from "@zudojs/observability";
|
|
232
|
+
|
|
233
|
+
createObservability({
|
|
234
|
+
serviceName: "api",
|
|
235
|
+
processors: [
|
|
236
|
+
createBatchSpanProcessor({
|
|
237
|
+
exporter: createConsoleSpanExporter({ pretty: true }),
|
|
238
|
+
batchSize: 512,
|
|
239
|
+
maxQueueSize: 2048,
|
|
240
|
+
onError: (error, source) => console.error(source, error),
|
|
241
|
+
}),
|
|
242
|
+
],
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Both the span and log processors have a hard queue cap: past it, records are
|
|
247
|
+
dropped and counted rather than growing the process until it dies. Export
|
|
248
|
+
failures go to `onError` instead of vanishing.
|
|
249
|
+
|
|
250
|
+
Exporters for OpenTelemetry, Prometheus or Datadog belong in separate
|
|
251
|
+
packages; implement `SpanExporter`, `LogExporter` or `MetricExporter`.
|
|
252
|
+
|
|
253
|
+
## Scopes
|
|
254
|
+
|
|
255
|
+
`resource()` creates a view that differs only in its resource attributes,
|
|
256
|
+
sharing the parent's logger, registry, processors and exporters:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const podScoped = obs.resource({ "k8s.pod": process.env.POD_NAME });
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Shut down the root; a scope does not own the pipeline. `flush()` works from
|
|
263
|
+
either, because the buffers it drains are the shared ones.
|
|
264
|
+
|
|
265
|
+
## Disabling telemetry
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { createNoopObservability } from "@zudojs/observability";
|
|
269
|
+
|
|
270
|
+
const obs = enabled ? createObservability(config) : createNoopObservability();
|
|
23
271
|
```
|
|
24
272
|
|
|
25
|
-
##
|
|
273
|
+
## Errors
|
|
26
274
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- Exporters (OTLP, Prometheus, etc.)
|
|
275
|
+
`ObservabilityError` and its subclasses — `ExporterError`,
|
|
276
|
+
`ObservabilityConfigError`, `MetricValueError` — cover the cases where this
|
|
277
|
+
package throws. Transport failures never throw; they are reported through
|
|
278
|
+
`onError`.
|
|
32
279
|
|
|
33
280
|
## Use Cases
|
|
34
281
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Errors
|
|
3
|
+
*
|
|
4
|
+
* Telemetry must never be the reason an application fails, so this package
|
|
5
|
+
* throws only for programmer errors — a malformed configuration, a metric
|
|
6
|
+
* recorded with a value that cannot be aggregated. Failures in the transport
|
|
7
|
+
* layer are reported through `ObservabilityConfig.onError` instead.
|
|
8
|
+
*/
|
|
9
|
+
import { BaseError, ErrorCode } from "@zudojs/errors";
|
|
10
|
+
/** Base error for all observability failures. */
|
|
11
|
+
export declare class ObservabilityError extends BaseError {
|
|
12
|
+
constructor(message: string, options?: {
|
|
13
|
+
readonly code?: ErrorCode;
|
|
14
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
15
|
+
readonly cause?: unknown;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** An exporter failed to deliver telemetry. */
|
|
19
|
+
export declare class ExporterError extends ObservabilityError {
|
|
20
|
+
constructor(exporterName: string, cause?: unknown);
|
|
21
|
+
}
|
|
22
|
+
/** A configuration value is unusable. */
|
|
23
|
+
export declare class ObservabilityConfigError extends ObservabilityError {
|
|
24
|
+
constructor(message: string, metadata?: Readonly<Record<string, unknown>>);
|
|
25
|
+
}
|
|
26
|
+
/** A metric was given a value it cannot aggregate. */
|
|
27
|
+
export declare class MetricValueError extends ObservabilityError {
|
|
28
|
+
constructor(metricName: string, value: number, reason: string);
|
|
29
|
+
}
|
|
30
|
+
/** Determines whether an unknown value is an observability error. */
|
|
31
|
+
export declare function isObservabilityError(value: unknown): value is ObservabilityError;
|
|
32
|
+
//# sourceMappingURL=observabilityError.core.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Errors
|
|
3
|
+
*
|
|
4
|
+
* Telemetry must never be the reason an application fails, so this package
|
|
5
|
+
* throws only for programmer errors — a malformed configuration, a metric
|
|
6
|
+
* recorded with a value that cannot be aggregated. Failures in the transport
|
|
7
|
+
* layer are reported through `ObservabilityConfig.onError` instead.
|
|
8
|
+
*/
|
|
9
|
+
import { BaseError, ErrorCode, ErrorCategory, ErrorSeverity, } from "@zudojs/errors";
|
|
10
|
+
/** Base error for all observability failures. */
|
|
11
|
+
export class ObservabilityError extends BaseError {
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message, {
|
|
14
|
+
code: options?.code ?? ErrorCode.OPERATION_FAILED,
|
|
15
|
+
category: ErrorCategory.INTERNAL,
|
|
16
|
+
severity: ErrorSeverity.ERROR,
|
|
17
|
+
statusCode: 500,
|
|
18
|
+
expose: false,
|
|
19
|
+
metadata: options?.metadata,
|
|
20
|
+
cause: options?.cause,
|
|
21
|
+
});
|
|
22
|
+
this.name = "ObservabilityError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** An exporter failed to deliver telemetry. */
|
|
26
|
+
export class ExporterError extends ObservabilityError {
|
|
27
|
+
constructor(exporterName, cause) {
|
|
28
|
+
super(`Exporter "${exporterName}" failed to export telemetry`, {
|
|
29
|
+
metadata: { exporterName },
|
|
30
|
+
cause,
|
|
31
|
+
});
|
|
32
|
+
this.name = "ExporterError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** A configuration value is unusable. */
|
|
36
|
+
export class ObservabilityConfigError extends ObservabilityError {
|
|
37
|
+
constructor(message, metadata) {
|
|
38
|
+
super(message, { code: ErrorCode.VALIDATION_FAILED, metadata });
|
|
39
|
+
this.name = "ObservabilityConfigError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** A metric was given a value it cannot aggregate. */
|
|
43
|
+
export class MetricValueError extends ObservabilityError {
|
|
44
|
+
constructor(metricName, value, reason) {
|
|
45
|
+
super(`Metric "${metricName}" rejected value ${value}: ${reason}`, {
|
|
46
|
+
code: ErrorCode.VALIDATION_FAILED,
|
|
47
|
+
metadata: { metricName, value, reason },
|
|
48
|
+
});
|
|
49
|
+
this.name = "MetricValueError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Determines whether an unknown value is an observability error. */
|
|
53
|
+
export function isObservabilityError(value) {
|
|
54
|
+
return value instanceof ObservabilityError;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=observabilityError.core.js.map
|
|
@@ -2,12 +2,50 @@
|
|
|
2
2
|
* @zudojs/observability — Console Exporter
|
|
3
3
|
*
|
|
4
4
|
* Exports telemetry to the console for development and debugging.
|
|
5
|
+
*
|
|
6
|
+
* Serialization is defensive, because everything here is fed values the API
|
|
7
|
+
* declares as `unknown`: circular structures, BigInts and throwing getters all
|
|
8
|
+
* reach `JSON.stringify` eventually, and an exporter that throws inside the
|
|
9
|
+
* logging path takes the process with it.
|
|
5
10
|
*/
|
|
6
11
|
import type { LogExporter, LogRecord, MetricExporter, MetricSnapshot, ReadableSpan, SpanExporter } from "../types.js";
|
|
12
|
+
/** Shape of the console methods the exporters use. */
|
|
13
|
+
export interface ConsoleLike {
|
|
14
|
+
log(message: string): void;
|
|
15
|
+
warn(message: string): void;
|
|
16
|
+
error(message: string): void;
|
|
17
|
+
}
|
|
18
|
+
/** Options shared by the console exporters. */
|
|
19
|
+
export interface ConsoleExporterOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Indent the JSON. Default: `false` — one line per record is what log
|
|
22
|
+
* shippers parse, and pretty-printing a span is expensive under load.
|
|
23
|
+
*/
|
|
24
|
+
readonly pretty?: boolean;
|
|
25
|
+
/** Where to write. Defaults to the global console. */
|
|
26
|
+
readonly console?: ConsoleLike;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* JSON.stringify that cannot throw.
|
|
30
|
+
*
|
|
31
|
+
* Cycles become `"[Circular]"`, BigInts become their decimal string, and a
|
|
32
|
+
* value that defeats serialization entirely falls back to `String(value)`.
|
|
33
|
+
*
|
|
34
|
+
* "Cycle" means an ancestor, not "seen before". A `WeakSet` of every object
|
|
35
|
+
* already visited also matches a value referenced twice from different
|
|
36
|
+
* branches — `{ user, actor: user }` — and silently replaced the second copy
|
|
37
|
+
* with `[Circular]`, losing real telemetry that was never circular. The
|
|
38
|
+
* replacer's `this` is the object currently being serialized, which is what
|
|
39
|
+
* lets the ancestor chain be tracked exactly.
|
|
40
|
+
*/
|
|
41
|
+
export declare function safeStringify(value: unknown, pretty?: boolean): string;
|
|
7
42
|
/**
|
|
8
43
|
* Exports completed spans to the console.
|
|
9
44
|
*/
|
|
10
45
|
export declare class ConsoleSpanExporter implements SpanExporter {
|
|
46
|
+
private readonly pretty;
|
|
47
|
+
private readonly out;
|
|
48
|
+
constructor(options?: ConsoleExporterOptions);
|
|
11
49
|
export(spans: readonly ReadableSpan[]): Promise<void>;
|
|
12
50
|
shutdown(): Promise<void>;
|
|
13
51
|
}
|
|
@@ -15,6 +53,9 @@ export declare class ConsoleSpanExporter implements SpanExporter {
|
|
|
15
53
|
* Exports log records to the console.
|
|
16
54
|
*/
|
|
17
55
|
export declare class ConsoleLogExporter implements LogExporter {
|
|
56
|
+
private readonly pretty;
|
|
57
|
+
private readonly out;
|
|
58
|
+
constructor(options?: ConsoleExporterOptions);
|
|
18
59
|
export(records: readonly LogRecord[]): Promise<void>;
|
|
19
60
|
shutdown(): Promise<void>;
|
|
20
61
|
}
|
|
@@ -22,13 +63,20 @@ export declare class ConsoleLogExporter implements LogExporter {
|
|
|
22
63
|
* Exports metric snapshots to the console.
|
|
23
64
|
*/
|
|
24
65
|
export declare class ConsoleMetricExporter implements MetricExporter {
|
|
66
|
+
private readonly pretty;
|
|
67
|
+
private readonly out;
|
|
68
|
+
constructor(options?: ConsoleExporterOptions);
|
|
25
69
|
export(snapshots: readonly MetricSnapshot[]): Promise<void>;
|
|
26
70
|
shutdown(): Promise<void>;
|
|
27
71
|
}
|
|
72
|
+
/** A log exporter that discards everything. */
|
|
73
|
+
export declare const noopLogExporter: LogExporter;
|
|
74
|
+
/** A metric exporter that discards everything. */
|
|
75
|
+
export declare const noopMetricExporter: MetricExporter;
|
|
28
76
|
/** Creates a console span exporter. */
|
|
29
|
-
export declare function createConsoleSpanExporter(): ConsoleSpanExporter;
|
|
77
|
+
export declare function createConsoleSpanExporter(options?: ConsoleExporterOptions): ConsoleSpanExporter;
|
|
30
78
|
/** Creates a console log exporter. */
|
|
31
|
-
export declare function createConsoleLogExporter(): ConsoleLogExporter;
|
|
79
|
+
export declare function createConsoleLogExporter(options?: ConsoleExporterOptions): ConsoleLogExporter;
|
|
32
80
|
/** Creates a console metric exporter. */
|
|
33
|
-
export declare function createConsoleMetricExporter(): ConsoleMetricExporter;
|
|
81
|
+
export declare function createConsoleMetricExporter(options?: ConsoleExporterOptions): ConsoleMetricExporter;
|
|
34
82
|
//# sourceMappingURL=exporter.console.d.ts.map
|
|
@@ -2,14 +2,73 @@
|
|
|
2
2
|
* @zudojs/observability — Console Exporter
|
|
3
3
|
*
|
|
4
4
|
* Exports telemetry to the console for development and debugging.
|
|
5
|
+
*
|
|
6
|
+
* Serialization is defensive, because everything here is fed values the API
|
|
7
|
+
* declares as `unknown`: circular structures, BigInts and throwing getters all
|
|
8
|
+
* reach `JSON.stringify` eventually, and an exporter that throws inside the
|
|
9
|
+
* logging path takes the process with it.
|
|
5
10
|
*/
|
|
11
|
+
import { LogLevel } from "../types.js";
|
|
12
|
+
/**
|
|
13
|
+
* JSON.stringify that cannot throw.
|
|
14
|
+
*
|
|
15
|
+
* Cycles become `"[Circular]"`, BigInts become their decimal string, and a
|
|
16
|
+
* value that defeats serialization entirely falls back to `String(value)`.
|
|
17
|
+
*
|
|
18
|
+
* "Cycle" means an ancestor, not "seen before". A `WeakSet` of every object
|
|
19
|
+
* already visited also matches a value referenced twice from different
|
|
20
|
+
* branches — `{ user, actor: user }` — and silently replaced the second copy
|
|
21
|
+
* with `[Circular]`, losing real telemetry that was never circular. The
|
|
22
|
+
* replacer's `this` is the object currently being serialized, which is what
|
|
23
|
+
* lets the ancestor chain be tracked exactly.
|
|
24
|
+
*/
|
|
25
|
+
export function safeStringify(value, pretty = false) {
|
|
26
|
+
const ancestors = [];
|
|
27
|
+
try {
|
|
28
|
+
return (JSON.stringify(value, function replacer(_key, entry) {
|
|
29
|
+
// Unwind to the holder of the value being visited.
|
|
30
|
+
while (ancestors.length > 0 &&
|
|
31
|
+
ancestors[ancestors.length - 1] !== this) {
|
|
32
|
+
ancestors.pop();
|
|
33
|
+
}
|
|
34
|
+
if (typeof entry === "bigint")
|
|
35
|
+
return entry.toString();
|
|
36
|
+
if (typeof entry === "function")
|
|
37
|
+
return "[Function]";
|
|
38
|
+
if (typeof entry === "symbol")
|
|
39
|
+
return entry.toString();
|
|
40
|
+
if (entry instanceof Error) {
|
|
41
|
+
return {
|
|
42
|
+
name: entry.name,
|
|
43
|
+
message: entry.message,
|
|
44
|
+
stack: entry.stack,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (typeof entry === "object" && entry !== null) {
|
|
48
|
+
if (ancestors.includes(entry))
|
|
49
|
+
return "[Circular]";
|
|
50
|
+
ancestors.push(entry);
|
|
51
|
+
}
|
|
52
|
+
return entry;
|
|
53
|
+
}, pretty ? 2 : undefined) ?? String(value));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return String(value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
6
59
|
/**
|
|
7
60
|
* Exports completed spans to the console.
|
|
8
61
|
*/
|
|
9
62
|
export class ConsoleSpanExporter {
|
|
63
|
+
pretty;
|
|
64
|
+
out;
|
|
65
|
+
constructor(options) {
|
|
66
|
+
this.pretty = options?.pretty ?? false;
|
|
67
|
+
this.out = options?.console ?? console;
|
|
68
|
+
}
|
|
10
69
|
async export(spans) {
|
|
11
70
|
for (const span of spans) {
|
|
12
|
-
|
|
71
|
+
this.out.log(safeStringify({
|
|
13
72
|
type: "span",
|
|
14
73
|
name: span.name,
|
|
15
74
|
traceId: span.context.traceId,
|
|
@@ -17,13 +76,20 @@ export class ConsoleSpanExporter {
|
|
|
17
76
|
parentSpanId: span.context.parentSpanId,
|
|
18
77
|
kind: span.kind,
|
|
19
78
|
status: span.status,
|
|
20
|
-
|
|
79
|
+
statusMessage: span.statusMessage,
|
|
80
|
+
durationMs: span.duration,
|
|
21
81
|
startTime: span.startTime.toISOString(),
|
|
22
82
|
endTime: span.endTime.toISOString(),
|
|
23
83
|
attributes: span.attributes,
|
|
24
84
|
events: span.events,
|
|
25
85
|
resource: span.resource,
|
|
26
|
-
|
|
86
|
+
...(span.droppedAttributes > 0
|
|
87
|
+
? { droppedAttributes: span.droppedAttributes }
|
|
88
|
+
: {}),
|
|
89
|
+
...(span.droppedEvents > 0
|
|
90
|
+
? { droppedEvents: span.droppedEvents }
|
|
91
|
+
: {}),
|
|
92
|
+
}, this.pretty));
|
|
27
93
|
}
|
|
28
94
|
}
|
|
29
95
|
async shutdown() {
|
|
@@ -34,25 +100,30 @@ export class ConsoleSpanExporter {
|
|
|
34
100
|
* Exports log records to the console.
|
|
35
101
|
*/
|
|
36
102
|
export class ConsoleLogExporter {
|
|
103
|
+
pretty;
|
|
104
|
+
out;
|
|
105
|
+
constructor(options) {
|
|
106
|
+
this.pretty = options?.pretty ?? false;
|
|
107
|
+
this.out = options?.console ?? console;
|
|
108
|
+
}
|
|
37
109
|
async export(records) {
|
|
38
110
|
for (const record of records) {
|
|
39
|
-
const
|
|
111
|
+
const line = safeStringify({
|
|
40
112
|
timestamp: record.timestamp.toISOString(),
|
|
41
113
|
level: record.levelName,
|
|
42
114
|
logger: record.loggerName,
|
|
43
115
|
message: record.message,
|
|
116
|
+
...(record.traceId ? { traceId: record.traceId } : {}),
|
|
117
|
+
...(record.spanId ? { spanId: record.spanId } : {}),
|
|
44
118
|
...(record.context ? { context: record.context } : {}),
|
|
45
119
|
...(record.error ? { error: record.error } : {}),
|
|
46
|
-
};
|
|
47
|
-
if (record.level >=
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
else {
|
|
54
|
-
console.log(JSON.stringify(output, null, 2));
|
|
55
|
-
}
|
|
120
|
+
}, this.pretty);
|
|
121
|
+
if (record.level >= LogLevel.ERROR)
|
|
122
|
+
this.out.error(line);
|
|
123
|
+
else if (record.level >= LogLevel.WARN)
|
|
124
|
+
this.out.warn(line);
|
|
125
|
+
else
|
|
126
|
+
this.out.log(line);
|
|
56
127
|
}
|
|
57
128
|
}
|
|
58
129
|
async shutdown() {
|
|
@@ -63,32 +134,48 @@ export class ConsoleLogExporter {
|
|
|
63
134
|
* Exports metric snapshots to the console.
|
|
64
135
|
*/
|
|
65
136
|
export class ConsoleMetricExporter {
|
|
137
|
+
pretty;
|
|
138
|
+
out;
|
|
139
|
+
constructor(options) {
|
|
140
|
+
this.pretty = options?.pretty ?? false;
|
|
141
|
+
this.out = options?.console ?? console;
|
|
142
|
+
}
|
|
66
143
|
async export(snapshots) {
|
|
67
144
|
for (const snapshot of snapshots) {
|
|
68
|
-
|
|
145
|
+
this.out.log(safeStringify({
|
|
69
146
|
type: "metric",
|
|
70
147
|
metricType: snapshot.type,
|
|
71
148
|
name: snapshot.name,
|
|
72
149
|
value: snapshot.value,
|
|
73
150
|
labels: snapshot.labels,
|
|
74
|
-
timestamp:
|
|
75
|
-
},
|
|
151
|
+
timestamp: snapshot.timestamp.toISOString(),
|
|
152
|
+
}, this.pretty));
|
|
76
153
|
}
|
|
77
154
|
}
|
|
78
155
|
async shutdown() {
|
|
79
156
|
// No resources to clean up
|
|
80
157
|
}
|
|
81
158
|
}
|
|
159
|
+
/** A log exporter that discards everything. */
|
|
160
|
+
export const noopLogExporter = {
|
|
161
|
+
export: async () => { },
|
|
162
|
+
shutdown: async () => { },
|
|
163
|
+
};
|
|
164
|
+
/** A metric exporter that discards everything. */
|
|
165
|
+
export const noopMetricExporter = {
|
|
166
|
+
export: async () => { },
|
|
167
|
+
shutdown: async () => { },
|
|
168
|
+
};
|
|
82
169
|
/** Creates a console span exporter. */
|
|
83
|
-
export function createConsoleSpanExporter() {
|
|
84
|
-
return new ConsoleSpanExporter();
|
|
170
|
+
export function createConsoleSpanExporter(options) {
|
|
171
|
+
return new ConsoleSpanExporter(options);
|
|
85
172
|
}
|
|
86
173
|
/** Creates a console log exporter. */
|
|
87
|
-
export function createConsoleLogExporter() {
|
|
88
|
-
return new ConsoleLogExporter();
|
|
174
|
+
export function createConsoleLogExporter(options) {
|
|
175
|
+
return new ConsoleLogExporter(options);
|
|
89
176
|
}
|
|
90
177
|
/** Creates a console metric exporter. */
|
|
91
|
-
export function createConsoleMetricExporter() {
|
|
92
|
-
return new ConsoleMetricExporter();
|
|
178
|
+
export function createConsoleMetricExporter(options) {
|
|
179
|
+
return new ConsoleMetricExporter(options);
|
|
93
180
|
}
|
|
94
181
|
//# sourceMappingURL=exporter.console.js.map
|
package/dist/exporter/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zudojs/observability — Exporters
|
|
3
3
|
*
|
|
4
|
-
* Console exporters for spans, logs, and metrics.
|
|
4
|
+
* Console exporters for spans, logs, and metrics, plus no-op exporters.
|
|
5
5
|
*/
|
|
6
|
-
export { ConsoleSpanExporter, ConsoleLogExporter, ConsoleMetricExporter, createConsoleSpanExporter, createConsoleLogExporter, createConsoleMetricExporter, } from "./exporter.console.js";
|
|
6
|
+
export { ConsoleSpanExporter, ConsoleLogExporter, ConsoleMetricExporter, createConsoleSpanExporter, createConsoleLogExporter, createConsoleMetricExporter, noopLogExporter, noopMetricExporter, safeStringify, type ConsoleExporterOptions, type ConsoleLike, } from "./exporter.console.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|