@zudojs/observability 1.0.1 → 1.1.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 +48 -0
- package/dist/errors/observabilityError.core.d.ts +8 -9
- package/dist/errors/observabilityError.core.js +7 -16
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/logLevel/index.d.ts +1 -0
- package/dist/logLevel/index.js +1 -0
- package/dist/logLevel/logLevel.bridge.d.ts +25 -0
- package/dist/logLevel/logLevel.bridge.js +38 -0
- package/dist/metrics/metrics.registry.d.ts +6 -0
- package/dist/metrics/metrics.registry.js +14 -1
- package/dist/observability/observability.core.js +6 -0
- package/dist/propagation/index.d.ts +1 -0
- package/dist/propagation/index.js +1 -0
- package/dist/propagation/propagation.core.d.ts +7 -1
- package/dist/propagation/propagation.core.js +16 -6
- package/dist/propagation/propagation.traceparent.d.ts +28 -0
- package/dist/propagation/propagation.traceparent.js +58 -0
- package/dist/redaction/redaction.core.js +8 -2
- 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/spanContext.type.d.ts +17 -1
- package/dist/tracing/span/spanContext.type.js +36 -6
- package/dist/tracing/tracer/index.d.ts +1 -0
- package/dist/tracing/tracer/index.js +1 -0
- package/dist/tracing/tracer/tracer.active.d.ts +19 -0
- package/dist/tracing/tracer/tracer.active.js +67 -0
- package/dist/tracing/tracer/tracer.core.d.ts +5 -0
- package/dist/tracing/tracer/tracer.core.js +15 -6
- package/dist/tracing/tracer/tracer.parent.d.ts +28 -0
- package/dist/tracing/tracer/tracer.parent.js +36 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Structured logging, metrics, tracing, context propagation, and telemetry exporters for Zudojs applications.
|
|
4
4
|
|
|
5
|
+
<!-- zudo-docs:start -->
|
|
6
|
+
|
|
7
|
+
**Documentation:** [zudojs.oyinlola.site/docs/packages-observability](https://zudojs.oyinlola.site/docs/packages-observability) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-observability.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
|
|
8
|
+
|
|
9
|
+
<!-- zudo-docs:end -->
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -68,6 +74,12 @@ await obs.logger.flush();
|
|
|
68
74
|
Every record carries `traceId` and `spanId` when a propagation context is
|
|
69
75
|
active, so logs and traces line up without threading IDs by hand.
|
|
70
76
|
|
|
77
|
+
`LogLevel` here counts upward in severity (`TRACE = 0 … FATAL = 5`), the
|
|
78
|
+
opposite of `@zudojs/logger`'s `LoggerLevel` (`FATAL = 0 … TRACE = 5`).
|
|
79
|
+
Never pass a raw level number between the two; convert with
|
|
80
|
+
`toLoggerLevel(LogLevel.ERROR)` (→ `1`) and `fromLoggerLevel(1)`
|
|
81
|
+
(→ `LogLevel.ERROR`).
|
|
82
|
+
|
|
71
83
|
## Redaction
|
|
72
84
|
|
|
73
85
|
Redaction is off unless you configure it, and on once you do. The logger
|
|
@@ -143,6 +155,10 @@ const obs = createObservability({
|
|
|
143
155
|
});
|
|
144
156
|
```
|
|
145
157
|
|
|
158
|
+
`metrics.onCardinalityLimit` fires once per rejected series, and the facade
|
|
159
|
+
raises `onError` once per metric name, so a label explosion is one report
|
|
160
|
+
rather than one per metric call.
|
|
161
|
+
|
|
146
162
|
One metric name may only ever be one type: registering `counter("latency")`
|
|
147
163
|
and then `histogram("latency")` throws, because a document carrying the same
|
|
148
164
|
name as two types is rejected wholesale by OTLP and Prometheus.
|
|
@@ -218,6 +234,38 @@ obs.propagation.current(); // undefined outside a run() scope
|
|
|
218
234
|
`current()` returns `undefined` when there is no active context, so "no trace"
|
|
219
235
|
stays distinguishable from a real one.
|
|
220
236
|
|
|
237
|
+
A span started without an explicit `parent` joins the active context, so the
|
|
238
|
+
span and the log records written in the same scope share one `traceId`. To
|
|
239
|
+
make a span itself the active context — so logs inside it carry its `spanId`
|
|
240
|
+
and nested spans become its children — use `withSpan` (or
|
|
241
|
+
`tracer.startActiveSpan` on a `DefaultTracer`). It ends the span when the
|
|
242
|
+
callback returns or its promise settles, and records a throw or rejection:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { withSpan } from "@zudojs/observability";
|
|
246
|
+
|
|
247
|
+
const rows = await withSpan(obs.tracer, "db.query", async (span) => {
|
|
248
|
+
span.setAttribute("db.system", "postgresql");
|
|
249
|
+
obs.logger.info("querying"); // carries this span's traceId and spanId
|
|
250
|
+
return [1, 2, 3];
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Inbound trace IDs are validated. `parseTraceparent` reads a W3C
|
|
255
|
+
`traceparent` header and returns `undefined` for anything malformed;
|
|
256
|
+
`formatTraceparent` writes one for an outgoing request. A parent or
|
|
257
|
+
propagation context whose trace or span ID is not valid W3C hex is never
|
|
258
|
+
joined — the span starts a fresh trace instead:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { formatTraceparent, parseTraceparent } from "@zudojs/observability";
|
|
262
|
+
|
|
263
|
+
const parent = parseTraceparent(request.headers["traceparent"]);
|
|
264
|
+
const span = obs.tracer.startSpan("handle", { parent }); // fresh trace if absent
|
|
265
|
+
const outgoing = formatTraceparent(span.context); // "00-<trace>-<span>-01"
|
|
266
|
+
span.end();
|
|
267
|
+
```
|
|
268
|
+
|
|
221
269
|
## Exporters
|
|
222
270
|
|
|
223
271
|
Console exporters ship for development. They serialize defensively — a
|
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
* recorded with a value that cannot be aggregated. Failures in the transport
|
|
7
7
|
* layer are reported through `ObservabilityConfig.onError` instead.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
9
|
+
import { ObservabilityError } from "@zudojs/errors";
|
|
10
|
+
/**
|
|
11
|
+
* Base error for all observability failures (500, not exposed). Owned by
|
|
12
|
+
* `@zudojs/errors` and re-exported here, so `instanceof` matches across
|
|
13
|
+
* both import paths.
|
|
14
|
+
*/
|
|
15
|
+
export { ObservabilityError };
|
|
16
|
+
export type { ObservabilityErrorOptions } from "@zudojs/errors";
|
|
18
17
|
/** An exporter failed to deliver telemetry. */
|
|
19
18
|
export declare class ExporterError extends ObservabilityError {
|
|
20
19
|
constructor(exporterName: string, cause?: unknown);
|
|
@@ -6,22 +6,13 @@
|
|
|
6
6
|
* recorded with a value that cannot be aggregated. Failures in the transport
|
|
7
7
|
* layer are reported through `ObservabilityConfig.onError` instead.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
}
|
|
9
|
+
import { ErrorCode, ObservabilityError } from "@zudojs/errors";
|
|
10
|
+
/**
|
|
11
|
+
* Base error for all observability failures (500, not exposed). Owned by
|
|
12
|
+
* `@zudojs/errors` and re-exported here, so `instanceof` matches across
|
|
13
|
+
* both import paths.
|
|
14
|
+
*/
|
|
15
|
+
export { ObservabilityError };
|
|
25
16
|
/** An exporter failed to deliver telemetry. */
|
|
26
17
|
export class ExporterError extends ObservabilityError {
|
|
27
18
|
constructor(exporterName, cause) {
|
package/dist/index.d.ts
CHANGED
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
export { LogLevel, SpanStatus, SpanKind, TraceFlags, type LogLevelName, type LogRecord, type LogRecordError, type Logger, type LoggerOptions, type LogTransport, type PropagationContext, type PropagationContextOptions, type PropagationManager, type Counter, type Gauge, type Histogram, type HistogramValue, type MetricsRegistry, type MetricSnapshot, type Span, type SpanContext, type SpanEvent, type SpanOptions, type SpanLimits, type Tracer, type ReadableSpan, type SpanExporter, type LogExporter, type MetricExporter, type SpanProcessor, type SamplingResult, type Sampler, type RedactionConfig, type RedactionMatchMode, type Observability, type ObservabilityConfig, } from "./types.js";
|
|
34
34
|
export { ObservabilityError, ExporterError, ObservabilityConfigError, MetricValueError, isObservabilityError, } from "./errors/index.js";
|
|
35
35
|
export { generateTraceId, generateSpanId, isValidTraceId, isValidSpanId, } from "./internal/index.js";
|
|
36
|
-
export { logLevelToName, logLevelFromName, parseLogLevel, shouldLog, getLogLevelNames, } from "./logLevel/index.js";
|
|
36
|
+
export { logLevelToName, logLevelFromName, parseLogLevel, shouldLog, getLogLevelNames, toLoggerLevel, fromLoggerLevel, } from "./logLevel/index.js";
|
|
37
37
|
export { createLogRecord, createErrorLogRecord, serializeError, } from "./logRecord/index.js";
|
|
38
38
|
export { StructuredLogger, createStructuredLogger } from "./logger/index.js";
|
|
39
|
-
export { createPropagationContext, derivePropagationContext, getCurrentContext, requireCurrentContext, AsyncPropagationManager, createPropagationManager, } from "./propagation/index.js";
|
|
39
|
+
export { createPropagationContext, derivePropagationContext, getCurrentContext, requireCurrentContext, AsyncPropagationManager, createPropagationManager, parseTraceparent, formatTraceparent, TRACEPARENT_HEADER, } from "./propagation/index.js";
|
|
40
40
|
export { DefaultCounter, createCounter, DefaultGauge, createGauge, DefaultHistogram, createHistogram, DEFAULT_BUCKET_BOUNDARIES, DefaultMetricsRegistry, createMetricsRegistry, metricKey, PeriodicMetricReader, createPeriodicMetricReader, type MetricsRegistryOptions, type PeriodicMetricReaderOptions, } from "./metrics/index.js";
|
|
41
|
-
export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, isSampledContext, DefaultTracer, createTracer, type TracerOptions, } from "./tracing/index.js";
|
|
41
|
+
export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, isSampledContext, isValidSpanContext, DefaultTracer, createTracer, withSpan, type TracerOptions, } from "./tracing/index.js";
|
|
42
42
|
export { AlwaysOnSampler, AlwaysOffSampler, ProbabilitySampler, ParentBasedSampler, createAlwaysOnSampler, createAlwaysOffSampler, createProbabilitySampler, createParentBasedSampler, isSampled, isRecording, type ParentBasedSamplerOptions, } from "./sampling/index.js";
|
|
43
43
|
export { ConsoleSpanExporter, ConsoleLogExporter, ConsoleMetricExporter, createConsoleSpanExporter, createConsoleLogExporter, createConsoleMetricExporter, noopLogExporter, noopMetricExporter, safeStringify, type ConsoleExporterOptions, type ConsoleLike, } from "./exporter/index.js";
|
|
44
44
|
export { BatchSpanProcessor, createBatchSpanProcessor, SimpleSpanProcessor, createSimpleSpanProcessor, BatchLogProcessor, createBatchLogProcessor, noopSpanExporter, type BatchSpanProcessorOptions, type BatchLogProcessorOptions, } from "./processor/index.js";
|
package/dist/index.js
CHANGED
|
@@ -37,17 +37,17 @@ export { ObservabilityError, ExporterError, ObservabilityConfigError, MetricValu
|
|
|
37
37
|
/* ─── Identifiers ───────────────────────────────────────────────────────── */
|
|
38
38
|
export { generateTraceId, generateSpanId, isValidTraceId, isValidSpanId, } from "./internal/index.js";
|
|
39
39
|
/* ─── Log Level ─────────────────────────────────────────────────────────── */
|
|
40
|
-
export { logLevelToName, logLevelFromName, parseLogLevel, shouldLog, getLogLevelNames, } from "./logLevel/index.js";
|
|
40
|
+
export { logLevelToName, logLevelFromName, parseLogLevel, shouldLog, getLogLevelNames, toLoggerLevel, fromLoggerLevel, } from "./logLevel/index.js";
|
|
41
41
|
/* ─── Log Record ────────────────────────────────────────────────────────── */
|
|
42
42
|
export { createLogRecord, createErrorLogRecord, serializeError, } from "./logRecord/index.js";
|
|
43
43
|
/* ─── Logger ────────────────────────────────────────────────────────────── */
|
|
44
44
|
export { StructuredLogger, createStructuredLogger } from "./logger/index.js";
|
|
45
45
|
/* ─── Propagation ───────────────────────────────────────────────────────── */
|
|
46
|
-
export { createPropagationContext, derivePropagationContext, getCurrentContext, requireCurrentContext, AsyncPropagationManager, createPropagationManager, } from "./propagation/index.js";
|
|
46
|
+
export { createPropagationContext, derivePropagationContext, getCurrentContext, requireCurrentContext, AsyncPropagationManager, createPropagationManager, parseTraceparent, formatTraceparent, TRACEPARENT_HEADER, } from "./propagation/index.js";
|
|
47
47
|
/* ─── Metrics ───────────────────────────────────────────────────────────── */
|
|
48
48
|
export { DefaultCounter, createCounter, DefaultGauge, createGauge, DefaultHistogram, createHistogram, DEFAULT_BUCKET_BOUNDARIES, DefaultMetricsRegistry, createMetricsRegistry, metricKey, PeriodicMetricReader, createPeriodicMetricReader, } from "./metrics/index.js";
|
|
49
49
|
/* ─── Tracing ───────────────────────────────────────────────────────────── */
|
|
50
|
-
export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, isSampledContext, DefaultTracer, createTracer, } from "./tracing/index.js";
|
|
50
|
+
export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, isSampledContext, isValidSpanContext, DefaultTracer, createTracer, withSpan, } from "./tracing/index.js";
|
|
51
51
|
/* ─── Sampling ──────────────────────────────────────────────────────────── */
|
|
52
52
|
export { AlwaysOnSampler, AlwaysOffSampler, ProbabilitySampler, ParentBasedSampler, createAlwaysOnSampler, createAlwaysOffSampler, createProbabilitySampler, createParentBasedSampler, isSampled, isRecording, } from "./sampling/index.js";
|
|
53
53
|
/* ─── Exporters ─────────────────────────────────────────────────────────── */
|
package/dist/logLevel/index.d.ts
CHANGED
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* Level names, conversion, and filtering utilities.
|
|
5
5
|
*/
|
|
6
6
|
export { logLevelToName, logLevelFromName, parseLogLevel, shouldLog, getLogLevelNames, } from "./logLevel.type.js";
|
|
7
|
+
export { toLoggerLevel, fromLoggerLevel } from "./logLevel.bridge.js";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/logLevel/index.js
CHANGED
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* Level names, conversion, and filtering utilities.
|
|
5
5
|
*/
|
|
6
6
|
export { logLevelToName, logLevelFromName, parseLogLevel, shouldLog, getLogLevelNames, } from "./logLevel.type.js";
|
|
7
|
+
export { toLoggerLevel, fromLoggerLevel } from "./logLevel.bridge.js";
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Level bridge to @zudojs/logger
|
|
3
|
+
*
|
|
4
|
+
* The two packages number their levels in opposite directions: here a higher
|
|
5
|
+
* value is more severe (`TRACE = 0 … FATAL = 5`), in `@zudojs/logger` a lower
|
|
6
|
+
* value is more severe (`FATAL = 0 … TRACE = 5`). Passing a raw number from
|
|
7
|
+
* one to the other inverts filtering, so convert explicitly with these.
|
|
8
|
+
*/
|
|
9
|
+
import { LogLevel } from "../types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Converts an observability {@link LogLevel} to the numeric value of the
|
|
12
|
+
* same-named `@zudojs/logger` `LoggerLevel`.
|
|
13
|
+
*
|
|
14
|
+
* Returns `undefined` for {@link LogLevel.OFF}, which has no logger
|
|
15
|
+
* counterpart, and for any value outside the enum.
|
|
16
|
+
*/
|
|
17
|
+
export declare function toLoggerLevel(level: LogLevel): number | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Converts a numeric `@zudojs/logger` `LoggerLevel` to the same-named
|
|
20
|
+
* observability {@link LogLevel}.
|
|
21
|
+
*
|
|
22
|
+
* Returns `undefined` for a value outside `LoggerLevel` (`0`–`5`).
|
|
23
|
+
*/
|
|
24
|
+
export declare function fromLoggerLevel(level: number): LogLevel | undefined;
|
|
25
|
+
//# sourceMappingURL=logLevel.bridge.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Level bridge to @zudojs/logger
|
|
3
|
+
*
|
|
4
|
+
* The two packages number their levels in opposite directions: here a higher
|
|
5
|
+
* value is more severe (`TRACE = 0 … FATAL = 5`), in `@zudojs/logger` a lower
|
|
6
|
+
* value is more severe (`FATAL = 0 … TRACE = 5`). Passing a raw number from
|
|
7
|
+
* one to the other inverts filtering, so convert explicitly with these.
|
|
8
|
+
*/
|
|
9
|
+
import { LogLevel } from "../types.js";
|
|
10
|
+
/** The most verbose `@zudojs/logger` `LoggerLevel` value (`TRACE`). */
|
|
11
|
+
const LOGGER_TRACE = 5;
|
|
12
|
+
/**
|
|
13
|
+
* Converts an observability {@link LogLevel} to the numeric value of the
|
|
14
|
+
* same-named `@zudojs/logger` `LoggerLevel`.
|
|
15
|
+
*
|
|
16
|
+
* Returns `undefined` for {@link LogLevel.OFF}, which has no logger
|
|
17
|
+
* counterpart, and for any value outside the enum.
|
|
18
|
+
*/
|
|
19
|
+
export function toLoggerLevel(level) {
|
|
20
|
+
if (!Number.isInteger(level) || level < LogLevel.TRACE)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (level > LogLevel.FATAL)
|
|
23
|
+
return undefined;
|
|
24
|
+
return LOGGER_TRACE - level;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Converts a numeric `@zudojs/logger` `LoggerLevel` to the same-named
|
|
28
|
+
* observability {@link LogLevel}.
|
|
29
|
+
*
|
|
30
|
+
* Returns `undefined` for a value outside `LoggerLevel` (`0`–`5`).
|
|
31
|
+
*/
|
|
32
|
+
export function fromLoggerLevel(level) {
|
|
33
|
+
if (!Number.isInteger(level) || level < 0 || level > LOGGER_TRACE) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
return (LOGGER_TRACE - level);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=logLevel.bridge.js.map
|
|
@@ -52,6 +52,12 @@ export declare class DefaultMetricsRegistry implements MetricsRegistry {
|
|
|
52
52
|
* documented ceiling is not quietly doubled by the overflow path.
|
|
53
53
|
*/
|
|
54
54
|
private readonly maxOverflow;
|
|
55
|
+
/**
|
|
56
|
+
* Series already reported to `onCardinalityLimit`. Kept apart from the
|
|
57
|
+
* overflow cache, whose small bound made the callback re-fire for every
|
|
58
|
+
* repeat of an already-rejected series once it was full.
|
|
59
|
+
*/
|
|
60
|
+
private readonly rejected;
|
|
55
61
|
constructor(options?: MetricsRegistryOptions);
|
|
56
62
|
private create;
|
|
57
63
|
private obtain;
|
|
@@ -25,6 +25,8 @@ export function metricKey(type, name, labels) {
|
|
|
25
25
|
const DEFAULT_MAX_SERIES = 10_000;
|
|
26
26
|
/** Upper bound on the detached series kept for callers past the cap. */
|
|
27
27
|
const MAX_OVERFLOW_SERIES = 1_024;
|
|
28
|
+
/** Upper bound on the rejected series remembered for de-duplicated reporting. */
|
|
29
|
+
const MAX_REJECTED_KEYS = 10_000;
|
|
28
30
|
/**
|
|
29
31
|
* In-memory metrics registry. Creates, caches, and manages metrics.
|
|
30
32
|
*/
|
|
@@ -50,6 +52,12 @@ export class DefaultMetricsRegistry {
|
|
|
50
52
|
* documented ceiling is not quietly doubled by the overflow path.
|
|
51
53
|
*/
|
|
52
54
|
maxOverflow;
|
|
55
|
+
/**
|
|
56
|
+
* Series already reported to `onCardinalityLimit`. Kept apart from the
|
|
57
|
+
* overflow cache, whose small bound made the callback re-fire for every
|
|
58
|
+
* repeat of an already-rejected series once it was full.
|
|
59
|
+
*/
|
|
60
|
+
rejected = new Set();
|
|
53
61
|
constructor(options) {
|
|
54
62
|
this.maxSeries = options?.maxSeries ?? DEFAULT_MAX_SERIES;
|
|
55
63
|
this.maxOverflow = Math.max(1, Math.min(this.maxSeries, MAX_OVERFLOW_SERIES));
|
|
@@ -83,7 +91,11 @@ export class DefaultMetricsRegistry {
|
|
|
83
91
|
const cached = this.overflow.get(key);
|
|
84
92
|
if (cached)
|
|
85
93
|
return cached;
|
|
86
|
-
|
|
94
|
+
if (!this.rejected.has(key)) {
|
|
95
|
+
if (this.rejected.size < MAX_REJECTED_KEYS)
|
|
96
|
+
this.rejected.add(key);
|
|
97
|
+
this.onCardinalityLimit?.(name, this.metrics.size);
|
|
98
|
+
}
|
|
87
99
|
const detached = this.create(type, name, labels);
|
|
88
100
|
if (this.overflow.size < this.maxOverflow)
|
|
89
101
|
this.overflow.set(key, detached);
|
|
@@ -170,6 +182,7 @@ export class DefaultMetricsRegistry {
|
|
|
170
182
|
clear() {
|
|
171
183
|
this.metrics.clear();
|
|
172
184
|
this.overflow.clear();
|
|
185
|
+
this.rejected.clear();
|
|
173
186
|
this.typeByName.clear();
|
|
174
187
|
}
|
|
175
188
|
}
|
|
@@ -185,10 +185,16 @@ function buildPipeline(config) {
|
|
|
185
185
|
// When the caller supplied their own processors, they own the exporter's
|
|
186
186
|
// lifecycle too; otherwise our processor closes it on shutdown.
|
|
187
187
|
/* ── Metrics ─────────────────────────────────────────────────────────── */
|
|
188
|
+
// onError hears about each over-cardinality metric name once: a label
|
|
189
|
+
// explosion is one problem, not one Error allocation per metric call.
|
|
190
|
+
const reportedNames = new Set();
|
|
188
191
|
const metrics = new DefaultMetricsRegistry({
|
|
189
192
|
...(config.metrics ?? {}),
|
|
190
193
|
onCardinalityLimit: (name, size) => {
|
|
191
194
|
config.metrics?.onCardinalityLimit?.(name, size);
|
|
195
|
+
if (reportedNames.has(name) || reportedNames.size >= 1_024)
|
|
196
|
+
return;
|
|
197
|
+
reportedNames.add(name);
|
|
192
198
|
config.onError?.(new Error(`Metric "${name}" exceeded the registry's series limit (${size}); ` +
|
|
193
199
|
`check for a high-cardinality label`), "MetricsRegistry");
|
|
194
200
|
},
|
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* Context propagation with AsyncLocalStorage for request-scoped IDs.
|
|
5
5
|
*/
|
|
6
6
|
export { createPropagationContext, derivePropagationContext, getCurrentContext, requireCurrentContext, AsyncPropagationManager, createPropagationManager, } from "./propagation.core.js";
|
|
7
|
+
export { parseTraceparent, formatTraceparent, TRACEPARENT_HEADER, } from "./propagation.traceparent.js";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* Context propagation with AsyncLocalStorage for request-scoped IDs.
|
|
5
5
|
*/
|
|
6
6
|
export { createPropagationContext, derivePropagationContext, getCurrentContext, requireCurrentContext, AsyncPropagationManager, createPropagationManager, } from "./propagation.core.js";
|
|
7
|
+
export { parseTraceparent, formatTraceparent, TRACEPARENT_HEADER, } from "./propagation.traceparent.js";
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
* trace, span, request, and correlation IDs.
|
|
6
6
|
*/
|
|
7
7
|
import type { PropagationContext, PropagationContextOptions, PropagationManager } from "../types.js";
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new propagation context.
|
|
10
|
+
*
|
|
11
|
+
* Supplied trace and span IDs are validated: an invalid `traceId` or
|
|
12
|
+
* `parentSpanId` starts a fresh trace (and drops the untrusted sampling
|
|
13
|
+
* flags), and an invalid `spanId` is replaced.
|
|
14
|
+
*/
|
|
9
15
|
export declare function createPropagationContext(options?: PropagationContextOptions): PropagationContext;
|
|
10
16
|
/**
|
|
11
17
|
* Derives a child context from a parent.
|
|
@@ -5,19 +5,29 @@
|
|
|
5
5
|
* trace, span, request, and correlation IDs.
|
|
6
6
|
*/
|
|
7
7
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
|
-
import { generateSpanId, generateTraceId } from "../internal/index.js";
|
|
8
|
+
import { generateSpanId, generateTraceId, isValidSpanId, isValidTraceId, } from "../internal/index.js";
|
|
9
9
|
const storage = new AsyncLocalStorage();
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new propagation context.
|
|
12
|
+
*
|
|
13
|
+
* Supplied trace and span IDs are validated: an invalid `traceId` or
|
|
14
|
+
* `parentSpanId` starts a fresh trace (and drops the untrusted sampling
|
|
15
|
+
* flags), and an invalid `spanId` is replaced.
|
|
16
|
+
*/
|
|
11
17
|
export function createPropagationContext(options) {
|
|
18
|
+
const joins = (options?.traceId === undefined || isValidTraceId(options.traceId)) &&
|
|
19
|
+
(options?.parentSpanId === undefined ||
|
|
20
|
+
isValidSpanId(options.parentSpanId));
|
|
21
|
+
const spanId = options?.spanId;
|
|
12
22
|
return {
|
|
13
|
-
traceId: options?.traceId ?? generateTraceId(),
|
|
14
|
-
spanId:
|
|
15
|
-
parentSpanId: options?.parentSpanId,
|
|
23
|
+
traceId: (joins ? options?.traceId : undefined) ?? generateTraceId(),
|
|
24
|
+
spanId: spanId !== undefined && isValidSpanId(spanId) ? spanId : generateSpanId(),
|
|
25
|
+
parentSpanId: joins ? options?.parentSpanId : undefined,
|
|
16
26
|
requestId: options?.requestId,
|
|
17
27
|
correlationId: options?.correlationId,
|
|
18
28
|
userId: options?.userId,
|
|
19
29
|
service: options?.service,
|
|
20
|
-
traceFlags: options?.traceFlags,
|
|
30
|
+
traceFlags: joins ? options?.traceFlags : undefined,
|
|
21
31
|
baggage: options?.baggage
|
|
22
32
|
? Object.freeze({ ...options.baggage })
|
|
23
33
|
: undefined,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — W3C `traceparent` header
|
|
3
|
+
*
|
|
4
|
+
* Parses and formats the W3C Trace Context `traceparent` header, using the
|
|
5
|
+
* same ID validators as the span and propagation factories so an inbound
|
|
6
|
+
* header can never smuggle a malformed ID into a trace.
|
|
7
|
+
*/
|
|
8
|
+
import type { SpanContext } from "../types.js";
|
|
9
|
+
/** Name of the W3C trace context header. */
|
|
10
|
+
export declare const TRACEPARENT_HEADER = "traceparent";
|
|
11
|
+
/**
|
|
12
|
+
* Parses a `traceparent` header value.
|
|
13
|
+
*
|
|
14
|
+
* Returns the remote span context (its `spanId` is the caller's span, to be
|
|
15
|
+
* used as `parent` when starting the local span), or `undefined` when the
|
|
16
|
+
* header is missing or malformed in any way — including an all-zero trace or
|
|
17
|
+
* span ID, uppercase hex, the forbidden version `ff`, or extra fields on
|
|
18
|
+
* version `00`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseTraceparent(header: string | readonly string[] | undefined | null): SpanContext | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Formats a span context as a version-`00` `traceparent` header value.
|
|
23
|
+
*
|
|
24
|
+
* Returns `undefined` when the context's trace or span ID is invalid, so a
|
|
25
|
+
* malformed context is never propagated downstream.
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatTraceparent(context: Pick<SpanContext, "traceId" | "spanId" | "traceFlags">): string | undefined;
|
|
28
|
+
//# sourceMappingURL=propagation.traceparent.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — W3C `traceparent` header
|
|
3
|
+
*
|
|
4
|
+
* Parses and formats the W3C Trace Context `traceparent` header, using the
|
|
5
|
+
* same ID validators as the span and propagation factories so an inbound
|
|
6
|
+
* header can never smuggle a malformed ID into a trace.
|
|
7
|
+
*/
|
|
8
|
+
import { isValidSpanId, isValidTraceId } from "../internal/index.js";
|
|
9
|
+
/** Name of the W3C trace context header. */
|
|
10
|
+
export const TRACEPARENT_HEADER = "traceparent";
|
|
11
|
+
const HEX2 = /^[0-9a-f]{2}$/;
|
|
12
|
+
/**
|
|
13
|
+
* Parses a `traceparent` header value.
|
|
14
|
+
*
|
|
15
|
+
* Returns the remote span context (its `spanId` is the caller's span, to be
|
|
16
|
+
* used as `parent` when starting the local span), or `undefined` when the
|
|
17
|
+
* header is missing or malformed in any way — including an all-zero trace or
|
|
18
|
+
* span ID, uppercase hex, the forbidden version `ff`, or extra fields on
|
|
19
|
+
* version `00`.
|
|
20
|
+
*/
|
|
21
|
+
export function parseTraceparent(header) {
|
|
22
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
23
|
+
if (typeof value !== "string")
|
|
24
|
+
return undefined;
|
|
25
|
+
const parts = value.trim().split("-");
|
|
26
|
+
if (parts.length < 4)
|
|
27
|
+
return undefined;
|
|
28
|
+
const [version, traceId, spanId, flags] = parts;
|
|
29
|
+
if (!HEX2.test(version) || version === "ff")
|
|
30
|
+
return undefined;
|
|
31
|
+
if (version === "00" && parts.length !== 4)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (!HEX2.test(flags))
|
|
34
|
+
return undefined;
|
|
35
|
+
if (!isValidTraceId(traceId) || !isValidSpanId(spanId))
|
|
36
|
+
return undefined;
|
|
37
|
+
return Object.freeze({
|
|
38
|
+
traceId,
|
|
39
|
+
spanId,
|
|
40
|
+
traceFlags: Number.parseInt(flags, 16),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Formats a span context as a version-`00` `traceparent` header value.
|
|
45
|
+
*
|
|
46
|
+
* Returns `undefined` when the context's trace or span ID is invalid, so a
|
|
47
|
+
* malformed context is never propagated downstream.
|
|
48
|
+
*/
|
|
49
|
+
export function formatTraceparent(context) {
|
|
50
|
+
if (!isValidTraceId(context.traceId) || !isValidSpanId(context.spanId)) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const flags = (context.traceFlags ?? 0) & 0xff;
|
|
54
|
+
return `00-${context.traceId}-${context.spanId}-${flags
|
|
55
|
+
.toString(16)
|
|
56
|
+
.padStart(2, "0")}`;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=propagation.traceparent.js.map
|
|
@@ -210,8 +210,14 @@ function walk(value, compiled, depth, seen) {
|
|
|
210
210
|
const redacted = redactField(key, entry, compiled);
|
|
211
211
|
// A field the matcher replaced is done — never walk into it, or a
|
|
212
212
|
// nested object under a sensitive key would leak through its children.
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
// defineProperty, not assignment: an own `__proto__` key (JSON.parse
|
|
214
|
+
// makes one) would otherwise replace the output's prototype.
|
|
215
|
+
Object.defineProperty(result, key, {
|
|
216
|
+
value: redacted === entry ? walk(entry, compiled, depth + 1, seen) : redacted,
|
|
217
|
+
enumerable: true,
|
|
218
|
+
writable: true,
|
|
219
|
+
configurable: true,
|
|
220
|
+
});
|
|
215
221
|
}
|
|
216
222
|
seen.delete(value);
|
|
217
223
|
return result;
|
package/dist/tracing/index.d.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Distributed tracing with spans, context, and exporters.
|
|
5
5
|
*/
|
|
6
|
-
export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, isSampledContext, } from "./span/index.js";
|
|
7
|
-
export { DefaultTracer, createTracer, type TracerOptions, } from "./tracer/index.js";
|
|
6
|
+
export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, isSampledContext, isValidSpanContext, } from "./span/index.js";
|
|
7
|
+
export { DefaultTracer, createTracer, withSpan, type TracerOptions, } from "./tracer/index.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/tracing/index.js
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Distributed tracing with spans, context, and exporters.
|
|
5
5
|
*/
|
|
6
|
-
export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, isSampledContext, } from "./span/index.js";
|
|
7
|
-
export { DefaultTracer, createTracer, } from "./tracer/index.js";
|
|
6
|
+
export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, isSampledContext, isValidSpanContext, } from "./span/index.js";
|
|
7
|
+
export { DefaultTracer, createTracer, withSpan, } from "./tracer/index.js";
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* Span implementation and context creation.
|
|
5
5
|
*/
|
|
6
6
|
export { DefaultSpan, createSpan } from "./span.core.js";
|
|
7
|
-
export { createSpanContext, createChildSpanContext, isSampledContext, } from "./spanContext.type.js";
|
|
7
|
+
export { createSpanContext, createChildSpanContext, isSampledContext, isValidSpanContext, } from "./spanContext.type.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* Span implementation and context creation.
|
|
5
5
|
*/
|
|
6
6
|
export { DefaultSpan, createSpan } from "./span.core.js";
|
|
7
|
-
export { createSpanContext, createChildSpanContext, isSampledContext, } from "./spanContext.type.js";
|
|
7
|
+
export { createSpanContext, createChildSpanContext, isSampledContext, isValidSpanContext, } from "./spanContext.type.js";
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,7 +4,21 @@
|
|
|
4
4
|
* Factory for creating span context identifiers.
|
|
5
5
|
*/
|
|
6
6
|
import type { SpanContext } from "../../types.js";
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* True when `context` carries a valid W3C trace ID and span ID.
|
|
9
|
+
*
|
|
10
|
+
* A context that fails this check must never be adopted as a parent: its IDs
|
|
11
|
+
* usually come from an inbound header and would otherwise flow verbatim into
|
|
12
|
+
* every span, log record and exporter.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isValidSpanContext(context: Pick<SpanContext, "traceId" | "spanId"> | undefined): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new span context with cryptographically random IDs.
|
|
17
|
+
*
|
|
18
|
+
* Supplied IDs are validated. An invalid `traceId` or `parentSpanId` starts
|
|
19
|
+
* a fresh trace (new trace ID, no parent) instead of joining a trace the
|
|
20
|
+
* caller cannot vouch for; an invalid `spanId` is replaced with a new one.
|
|
21
|
+
*/
|
|
8
22
|
export declare function createSpanContext(options?: {
|
|
9
23
|
readonly traceId?: string;
|
|
10
24
|
readonly spanId?: string;
|
|
@@ -16,6 +30,8 @@ export declare function createSpanContext(options?: {
|
|
|
16
30
|
*
|
|
17
31
|
* The trace ID and the trace flags both carry over: dropping the flags is
|
|
18
32
|
* what makes a parent-based sampler discard every child of a sampled trace.
|
|
33
|
+
* A parent with an invalid trace or span ID is not joined: the result is the
|
|
34
|
+
* root of a fresh trace.
|
|
19
35
|
*/
|
|
20
36
|
export declare function createChildSpanContext(parent: SpanContext, overrides?: {
|
|
21
37
|
readonly traceFlags?: number;
|
|
@@ -4,14 +4,39 @@
|
|
|
4
4
|
* Factory for creating span context identifiers.
|
|
5
5
|
*/
|
|
6
6
|
import { TraceFlags } from "../../types.js";
|
|
7
|
-
import { generateSpanId, generateTraceId } from "../../internal/index.js";
|
|
8
|
-
/**
|
|
7
|
+
import { generateSpanId, generateTraceId, isValidSpanId, isValidTraceId, } from "../../internal/index.js";
|
|
8
|
+
/**
|
|
9
|
+
* True when `context` carries a valid W3C trace ID and span ID.
|
|
10
|
+
*
|
|
11
|
+
* A context that fails this check must never be adopted as a parent: its IDs
|
|
12
|
+
* usually come from an inbound header and would otherwise flow verbatim into
|
|
13
|
+
* every span, log record and exporter.
|
|
14
|
+
*/
|
|
15
|
+
export function isValidSpanContext(context) {
|
|
16
|
+
if (context === undefined || context === null)
|
|
17
|
+
return false;
|
|
18
|
+
return (typeof context.traceId === "string" &&
|
|
19
|
+
typeof context.spanId === "string" &&
|
|
20
|
+
isValidTraceId(context.traceId) &&
|
|
21
|
+
isValidSpanId(context.spanId));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new span context with cryptographically random IDs.
|
|
25
|
+
*
|
|
26
|
+
* Supplied IDs are validated. An invalid `traceId` or `parentSpanId` starts
|
|
27
|
+
* a fresh trace (new trace ID, no parent) instead of joining a trace the
|
|
28
|
+
* caller cannot vouch for; an invalid `spanId` is replaced with a new one.
|
|
29
|
+
*/
|
|
9
30
|
export function createSpanContext(options) {
|
|
31
|
+
const traceOk = options?.traceId === undefined || isValidTraceId(options.traceId);
|
|
32
|
+
const parentOk = options?.parentSpanId === undefined || isValidSpanId(options.parentSpanId);
|
|
33
|
+
const joins = traceOk && parentOk;
|
|
34
|
+
const spanId = options?.spanId;
|
|
10
35
|
return {
|
|
11
|
-
traceId: options?.traceId ?? generateTraceId(),
|
|
12
|
-
spanId:
|
|
13
|
-
parentSpanId: options?.parentSpanId,
|
|
14
|
-
traceFlags: options?.traceFlags ?? TraceFlags.NONE,
|
|
36
|
+
traceId: (joins ? options?.traceId : undefined) ?? generateTraceId(),
|
|
37
|
+
spanId: spanId !== undefined && isValidSpanId(spanId) ? spanId : generateSpanId(),
|
|
38
|
+
parentSpanId: joins ? options?.parentSpanId : undefined,
|
|
39
|
+
traceFlags: (joins ? options?.traceFlags : undefined) ?? TraceFlags.NONE,
|
|
15
40
|
};
|
|
16
41
|
}
|
|
17
42
|
/**
|
|
@@ -19,8 +44,13 @@ export function createSpanContext(options) {
|
|
|
19
44
|
*
|
|
20
45
|
* The trace ID and the trace flags both carry over: dropping the flags is
|
|
21
46
|
* what makes a parent-based sampler discard every child of a sampled trace.
|
|
47
|
+
* A parent with an invalid trace or span ID is not joined: the result is the
|
|
48
|
+
* root of a fresh trace.
|
|
22
49
|
*/
|
|
23
50
|
export function createChildSpanContext(parent, overrides) {
|
|
51
|
+
if (!isValidSpanContext(parent)) {
|
|
52
|
+
return createSpanContext({ traceFlags: overrides?.traceFlags });
|
|
53
|
+
}
|
|
24
54
|
return createSpanContext({
|
|
25
55
|
traceId: parent.traceId,
|
|
26
56
|
parentSpanId: parent.spanId,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Active spans
|
|
3
|
+
*
|
|
4
|
+
* Runs a callback with a span as the active propagation context, so logs
|
|
5
|
+
* and child spans created inside it join the span's trace automatically.
|
|
6
|
+
*/
|
|
7
|
+
import type { Span, SpanOptions, Tracer } from "../../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Starts a span, runs `fn` with it as the active context, and ends it.
|
|
10
|
+
*
|
|
11
|
+
* Inside `fn`, `getCurrentContext()` returns a context carrying the span's
|
|
12
|
+
* trace and span IDs (plus the request, correlation, user and baggage of
|
|
13
|
+
* the surrounding context), so log records are correlated with the span and
|
|
14
|
+
* spans started without an explicit parent become its children. The span
|
|
15
|
+
* ends when `fn` returns or, for a promise, when it settles; a throw or a
|
|
16
|
+
* rejection is recorded on the span and re-thrown.
|
|
17
|
+
*/
|
|
18
|
+
export declare function withSpan<T>(tracer: Tracer, name: string, fn: (span: Span) => T, options?: SpanOptions): T;
|
|
19
|
+
//# sourceMappingURL=tracer.active.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Active spans
|
|
3
|
+
*
|
|
4
|
+
* Runs a callback with a span as the active propagation context, so logs
|
|
5
|
+
* and child spans created inside it join the span's trace automatically.
|
|
6
|
+
*/
|
|
7
|
+
import { SpanStatus } from "../../types.js";
|
|
8
|
+
import { createPropagationContext, createPropagationManager, getCurrentContext, } from "../../propagation/index.js";
|
|
9
|
+
const manager = createPropagationManager();
|
|
10
|
+
function fail(span, error) {
|
|
11
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
12
|
+
span.recordError(err);
|
|
13
|
+
span.setStatus(SpanStatus.ERROR, err.message);
|
|
14
|
+
span.end();
|
|
15
|
+
}
|
|
16
|
+
function isThenable(value) {
|
|
17
|
+
return (value !== null &&
|
|
18
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
19
|
+
typeof value.then === "function");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Starts a span, runs `fn` with it as the active context, and ends it.
|
|
23
|
+
*
|
|
24
|
+
* Inside `fn`, `getCurrentContext()` returns a context carrying the span's
|
|
25
|
+
* trace and span IDs (plus the request, correlation, user and baggage of
|
|
26
|
+
* the surrounding context), so log records are correlated with the span and
|
|
27
|
+
* spans started without an explicit parent become its children. The span
|
|
28
|
+
* ends when `fn` returns or, for a promise, when it settles; a throw or a
|
|
29
|
+
* rejection is recorded on the span and re-thrown.
|
|
30
|
+
*/
|
|
31
|
+
export function withSpan(tracer, name, fn, options) {
|
|
32
|
+
const span = tracer.startSpan(name, options);
|
|
33
|
+
const ambient = getCurrentContext();
|
|
34
|
+
const context = createPropagationContext({
|
|
35
|
+
requestId: ambient?.requestId,
|
|
36
|
+
correlationId: ambient?.correlationId,
|
|
37
|
+
userId: ambient?.userId,
|
|
38
|
+
service: ambient?.service,
|
|
39
|
+
baggage: ambient?.baggage,
|
|
40
|
+
traceId: span.context.traceId,
|
|
41
|
+
spanId: span.context.spanId,
|
|
42
|
+
parentSpanId: span.context.parentSpanId,
|
|
43
|
+
traceFlags: span.context.traceFlags,
|
|
44
|
+
});
|
|
45
|
+
return manager.runSync(context, () => {
|
|
46
|
+
let result;
|
|
47
|
+
try {
|
|
48
|
+
result = fn(span);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
fail(span, error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
if (!isThenable(result)) {
|
|
55
|
+
span.end();
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
return Promise.resolve(result).then((value) => {
|
|
59
|
+
span.end();
|
|
60
|
+
return value;
|
|
61
|
+
}, (error) => {
|
|
62
|
+
fail(span, error);
|
|
63
|
+
throw error;
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=tracer.active.js.map
|
|
@@ -43,6 +43,11 @@ export declare class DefaultTracer implements Tracer {
|
|
|
43
43
|
private readonly onError?;
|
|
44
44
|
constructor(options?: TracerOptions);
|
|
45
45
|
startSpan(name: string, options?: SpanOptions): Span;
|
|
46
|
+
/**
|
|
47
|
+
* Starts a span and runs `fn` with it as the active context.
|
|
48
|
+
* @see withSpan
|
|
49
|
+
*/
|
|
50
|
+
startActiveSpan<T>(name: string, fn: (span: Span) => T, options?: SpanOptions): T;
|
|
46
51
|
private notifyEnd;
|
|
47
52
|
/** Exports a completed span directly, bypassing the processors. */
|
|
48
53
|
exportSpan(span: ReadableSpan): Promise<void>;
|
|
@@ -8,6 +8,8 @@ import { TraceFlags } from "../../types.js";
|
|
|
8
8
|
import { DefaultSpan } from "../span/span.core.js";
|
|
9
9
|
import { createChildSpanContext, createSpanContext, } from "../span/spanContext.type.js";
|
|
10
10
|
import { AlwaysOnSampler } from "../../sampling/index.js";
|
|
11
|
+
import { resolveSpanParent } from "./tracer.parent.js";
|
|
12
|
+
import { withSpan } from "./tracer.active.js";
|
|
11
13
|
/**
|
|
12
14
|
* Default tracer that creates spans and notifies processors on start/end.
|
|
13
15
|
*/
|
|
@@ -31,12 +33,12 @@ export class DefaultTracer {
|
|
|
31
33
|
this.onError = options?.onError;
|
|
32
34
|
}
|
|
33
35
|
startSpan(name, options) {
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const decision = this.sampler.shouldSample(
|
|
36
|
+
// Join the explicit parent, else the ambient propagation context. Build
|
|
37
|
+
// the context first so the sampler can key on the real trace ID, then
|
|
38
|
+
// stamp the decision into traceFlags so children inherit it.
|
|
39
|
+
const { parent, samplerParent } = resolveSpanParent(options?.parent);
|
|
40
|
+
const base = parent ? createChildSpanContext(parent) : createSpanContext();
|
|
41
|
+
const decision = this.sampler.shouldSample(samplerParent, base.traceId);
|
|
40
42
|
const sampled = decision.decision === "RECORD_AND_SAMPLE";
|
|
41
43
|
const recording = decision.decision !== "DO_NOT_RECORD";
|
|
42
44
|
const context = {
|
|
@@ -71,6 +73,13 @@ export class DefaultTracer {
|
|
|
71
73
|
}
|
|
72
74
|
return span;
|
|
73
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Starts a span and runs `fn` with it as the active context.
|
|
78
|
+
* @see withSpan
|
|
79
|
+
*/
|
|
80
|
+
startActiveSpan(name, fn, options) {
|
|
81
|
+
return withSpan(this, name, fn, options);
|
|
82
|
+
}
|
|
74
83
|
notifyEnd(readable) {
|
|
75
84
|
for (const processor of this.processors) {
|
|
76
85
|
try {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Tracer parent resolution
|
|
3
|
+
*
|
|
4
|
+
* Decides which context a new span joins: the explicit `parent` when it is
|
|
5
|
+
* valid, otherwise the ambient propagation context, otherwise none.
|
|
6
|
+
*/
|
|
7
|
+
import type { SpanContext } from "../../types.js";
|
|
8
|
+
/** The parent a span joins, and the parent the sampler should consult. */
|
|
9
|
+
export interface ResolvedSpanParent {
|
|
10
|
+
/** Context the new span becomes a child of; `undefined` starts a trace. */
|
|
11
|
+
readonly parent?: SpanContext;
|
|
12
|
+
/**
|
|
13
|
+
* Context handed to the sampler. `undefined` when no upstream sampling
|
|
14
|
+
* decision exists, so the root sampler decides — an ambient context
|
|
15
|
+
* created without trace flags is not a "not sampled" parent.
|
|
16
|
+
*/
|
|
17
|
+
readonly samplerParent?: SpanContext;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the parent for a new span.
|
|
21
|
+
*
|
|
22
|
+
* An explicit parent with invalid IDs is never joined (the span starts a
|
|
23
|
+
* fresh trace). Without an explicit parent, the span joins the active
|
|
24
|
+
* propagation context so logs and spans written in one request share one
|
|
25
|
+
* trace ID.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveSpanParent(explicit?: SpanContext): ResolvedSpanParent;
|
|
28
|
+
//# sourceMappingURL=tracer.parent.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/observability — Tracer parent resolution
|
|
3
|
+
*
|
|
4
|
+
* Decides which context a new span joins: the explicit `parent` when it is
|
|
5
|
+
* valid, otherwise the ambient propagation context, otherwise none.
|
|
6
|
+
*/
|
|
7
|
+
import { getCurrentContext } from "../../propagation/index.js";
|
|
8
|
+
import { isValidSpanContext } from "../span/spanContext.type.js";
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the parent for a new span.
|
|
11
|
+
*
|
|
12
|
+
* An explicit parent with invalid IDs is never joined (the span starts a
|
|
13
|
+
* fresh trace). Without an explicit parent, the span joins the active
|
|
14
|
+
* propagation context so logs and spans written in one request share one
|
|
15
|
+
* trace ID.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveSpanParent(explicit) {
|
|
18
|
+
if (explicit !== undefined) {
|
|
19
|
+
return isValidSpanContext(explicit)
|
|
20
|
+
? { parent: explicit, samplerParent: explicit }
|
|
21
|
+
: {};
|
|
22
|
+
}
|
|
23
|
+
const ambient = getCurrentContext();
|
|
24
|
+
if (!ambient || !isValidSpanContext(ambient))
|
|
25
|
+
return {};
|
|
26
|
+
const parent = {
|
|
27
|
+
traceId: ambient.traceId,
|
|
28
|
+
spanId: ambient.spanId,
|
|
29
|
+
traceFlags: ambient.traceFlags,
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
parent,
|
|
33
|
+
samplerParent: ambient.traceFlags === undefined ? undefined : parent,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=tracer.parent.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/observability",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Structured logging, metrics, tracing, context propagation, and exporters for Zudojs applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"!dist/.tsbuildinfo"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@zudojs/errors": "1.0
|
|
29
|
+
"@zudojs/errors": "1.1.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"typescript": "7.0.2",
|