@zudojs/observability 0.1.0 → 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.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +260 -13
  3. package/dist/errors/index.d.ts +5 -0
  4. package/dist/errors/index.js +5 -0
  5. package/dist/errors/observabilityError.core.d.ts +32 -0
  6. package/dist/errors/observabilityError.core.js +56 -0
  7. package/dist/exporter/exporter.console.d.ts +51 -3
  8. package/dist/exporter/exporter.console.js +110 -23
  9. package/dist/exporter/index.d.ts +2 -2
  10. package/dist/exporter/index.js +2 -2
  11. package/dist/index.d.ts +17 -11
  12. package/dist/index.js +20 -12
  13. package/dist/internal/ids.core.d.ts +26 -0
  14. package/dist/internal/ids.core.js +51 -0
  15. package/dist/internal/index.d.ts +5 -0
  16. package/dist/internal/index.js +5 -0
  17. package/dist/logLevel/index.d.ts +1 -1
  18. package/dist/logLevel/index.js +1 -1
  19. package/dist/logLevel/logLevel.type.d.ts +8 -2
  20. package/dist/logLevel/logLevel.type.js +27 -20
  21. package/dist/logRecord/index.d.ts +1 -1
  22. package/dist/logRecord/index.js +1 -1
  23. package/dist/logRecord/logRecord.core.d.ts +15 -3
  24. package/dist/logRecord/logRecord.core.js +48 -26
  25. package/dist/logger/logger.core.d.ts +16 -8
  26. package/dist/logger/logger.core.js +54 -23
  27. package/dist/metrics/counter/counter.core.d.ts +4 -1
  28. package/dist/metrics/counter/counter.core.js +11 -3
  29. package/dist/metrics/gauge/gauge.core.d.ts +1 -0
  30. package/dist/metrics/gauge/gauge.core.js +9 -0
  31. package/dist/metrics/histogram/histogram.core.d.ts +22 -11
  32. package/dist/metrics/histogram/histogram.core.js +102 -7
  33. package/dist/metrics/histogram/index.d.ts +1 -1
  34. package/dist/metrics/histogram/index.js +1 -1
  35. package/dist/metrics/index.d.ts +4 -3
  36. package/dist/metrics/index.js +4 -3
  37. package/dist/metrics/metrics.reader.d.ts +39 -0
  38. package/dist/metrics/metrics.reader.js +81 -0
  39. package/dist/metrics/metrics.registry.d.ts +55 -4
  40. package/dist/metrics/metrics.registry.js +136 -49
  41. package/dist/noop/index.d.ts +1 -1
  42. package/dist/noop/index.js +1 -1
  43. package/dist/noop/noopObservability.core.d.ts +14 -2
  44. package/dist/noop/noopObservability.core.js +48 -11
  45. package/dist/observability/observability.core.d.ts +57 -3
  46. package/dist/observability/observability.core.js +206 -50
  47. package/dist/processor/index.d.ts +4 -3
  48. package/dist/processor/index.js +4 -3
  49. package/dist/processor/processor.batch.d.ts +66 -11
  50. package/dist/processor/processor.batch.js +130 -29
  51. package/dist/processor/processor.log.d.ts +56 -0
  52. package/dist/processor/processor.log.js +119 -0
  53. package/dist/propagation/index.d.ts +1 -1
  54. package/dist/propagation/index.js +1 -1
  55. package/dist/propagation/propagation.core.d.ts +26 -6
  56. package/dist/propagation/propagation.core.js +35 -19
  57. package/dist/redaction/index.d.ts +1 -1
  58. package/dist/redaction/index.js +1 -1
  59. package/dist/redaction/redaction.core.d.ts +37 -5
  60. package/dist/redaction/redaction.core.js +180 -36
  61. package/dist/sampling/index.d.ts +1 -1
  62. package/dist/sampling/index.js +1 -1
  63. package/dist/sampling/sampler.type.d.ts +38 -6
  64. package/dist/sampling/sampler.type.js +64 -23
  65. package/dist/tracing/index.d.ts +2 -2
  66. package/dist/tracing/index.js +2 -2
  67. package/dist/tracing/span/index.d.ts +1 -1
  68. package/dist/tracing/span/index.js +1 -1
  69. package/dist/tracing/span/span.core.d.ts +51 -8
  70. package/dist/tracing/span/span.core.js +90 -10
  71. package/dist/tracing/span/spanContext.type.d.ts +12 -3
  72. package/dist/tracing/span/spanContext.type.js +19 -16
  73. package/dist/tracing/tracer/index.d.ts +2 -2
  74. package/dist/tracing/tracer/index.js +2 -2
  75. package/dist/tracing/tracer/tracer.core.d.ts +44 -14
  76. package/dist/tracing/tracer/tracer.core.js +81 -25
  77. package/dist/types/config.types.d.ts +138 -0
  78. package/dist/types/config.types.js +5 -0
  79. package/dist/types/logging.types.d.ts +94 -0
  80. package/dist/types/logging.types.js +20 -0
  81. package/dist/types/metrics.types.d.ts +93 -0
  82. package/dist/types/metrics.types.js +5 -0
  83. package/dist/types/tracing.types.d.ts +115 -0
  84. package/dist/types/tracing.types.js +28 -0
  85. package/package.json +23 -14
  86. package/dist/exporter/exporter.console.d.ts.map +0 -1
  87. package/dist/exporter/exporter.console.js.map +0 -1
  88. package/dist/exporter/index.d.ts.map +0 -1
  89. package/dist/exporter/index.js.map +0 -1
  90. package/dist/index.d.ts.map +0 -1
  91. package/dist/index.js.map +0 -1
  92. package/dist/logLevel/index.d.ts.map +0 -1
  93. package/dist/logLevel/index.js.map +0 -1
  94. package/dist/logLevel/logLevel.type.d.ts.map +0 -1
  95. package/dist/logLevel/logLevel.type.js.map +0 -1
  96. package/dist/logRecord/index.d.ts.map +0 -1
  97. package/dist/logRecord/index.js.map +0 -1
  98. package/dist/logRecord/logRecord.core.d.ts.map +0 -1
  99. package/dist/logRecord/logRecord.core.js.map +0 -1
  100. package/dist/logger/index.d.ts.map +0 -1
  101. package/dist/logger/index.js.map +0 -1
  102. package/dist/logger/logger.core.d.ts.map +0 -1
  103. package/dist/logger/logger.core.js.map +0 -1
  104. package/dist/metrics/counter/counter.core.d.ts.map +0 -1
  105. package/dist/metrics/counter/counter.core.js.map +0 -1
  106. package/dist/metrics/counter/index.d.ts.map +0 -1
  107. package/dist/metrics/counter/index.js.map +0 -1
  108. package/dist/metrics/gauge/gauge.core.d.ts.map +0 -1
  109. package/dist/metrics/gauge/gauge.core.js.map +0 -1
  110. package/dist/metrics/gauge/index.d.ts.map +0 -1
  111. package/dist/metrics/gauge/index.js.map +0 -1
  112. package/dist/metrics/histogram/histogram.core.d.ts.map +0 -1
  113. package/dist/metrics/histogram/histogram.core.js.map +0 -1
  114. package/dist/metrics/histogram/index.d.ts.map +0 -1
  115. package/dist/metrics/histogram/index.js.map +0 -1
  116. package/dist/metrics/index.d.ts.map +0 -1
  117. package/dist/metrics/index.js.map +0 -1
  118. package/dist/metrics/metrics.registry.d.ts.map +0 -1
  119. package/dist/metrics/metrics.registry.js.map +0 -1
  120. package/dist/noop/index.d.ts.map +0 -1
  121. package/dist/noop/index.js.map +0 -1
  122. package/dist/noop/noopObservability.core.d.ts.map +0 -1
  123. package/dist/noop/noopObservability.core.js.map +0 -1
  124. package/dist/observability/index.d.ts.map +0 -1
  125. package/dist/observability/index.js.map +0 -1
  126. package/dist/observability/observability.core.d.ts.map +0 -1
  127. package/dist/observability/observability.core.js.map +0 -1
  128. package/dist/processor/index.d.ts.map +0 -1
  129. package/dist/processor/index.js.map +0 -1
  130. package/dist/processor/processor.batch.d.ts.map +0 -1
  131. package/dist/processor/processor.batch.js.map +0 -1
  132. package/dist/propagation/index.d.ts.map +0 -1
  133. package/dist/propagation/index.js.map +0 -1
  134. package/dist/propagation/propagation.core.d.ts.map +0 -1
  135. package/dist/propagation/propagation.core.js.map +0 -1
  136. package/dist/redaction/index.d.ts.map +0 -1
  137. package/dist/redaction/index.js.map +0 -1
  138. package/dist/redaction/redaction.core.d.ts.map +0 -1
  139. package/dist/redaction/redaction.core.js.map +0 -1
  140. package/dist/sampling/index.d.ts.map +0 -1
  141. package/dist/sampling/index.js.map +0 -1
  142. package/dist/sampling/sampler.type.d.ts.map +0 -1
  143. package/dist/sampling/sampler.type.js.map +0 -1
  144. package/dist/tracing/index.d.ts.map +0 -1
  145. package/dist/tracing/index.js.map +0 -1
  146. package/dist/tracing/span/index.d.ts.map +0 -1
  147. package/dist/tracing/span/index.js.map +0 -1
  148. package/dist/tracing/span/span.core.d.ts.map +0 -1
  149. package/dist/tracing/span/span.core.js.map +0 -1
  150. package/dist/tracing/span/spanContext.type.d.ts.map +0 -1
  151. package/dist/tracing/span/spanContext.type.js.map +0 -1
  152. package/dist/tracing/tracer/index.d.ts.map +0 -1
  153. package/dist/tracing/tracer/index.js.map +0 -1
  154. package/dist/tracing/tracer/tracer.core.d.ts.map +0 -1
  155. package/dist/tracing/tracer/tracer.core.js.map +0 -1
  156. package/dist/types.d.ts.map +0 -1
  157. package/dist/types.js.map +0 -1
@@ -12,18 +12,50 @@ export declare class AlwaysOnSampler implements Sampler {
12
12
  export declare class AlwaysOffSampler implements Sampler {
13
13
  shouldSample(): SamplingResult;
14
14
  }
15
- /** Samples a percentage of traces. */
15
+ /**
16
+ * Samples a fixed fraction of traces.
17
+ *
18
+ * The decision is derived from the trace ID, so every service handling the
19
+ * same trace reaches the same answer and a trace is never sampled in half.
20
+ * The low 8 hex digits are used, matching the OpenTelemetry convention of
21
+ * taking the *trailing* bytes — the leading ones are the least varied in
22
+ * some ID schemes.
23
+ *
24
+ * When no usable trace ID is available the sampler falls back to a random
25
+ * draw at the same rate, rather than failing open in one direction and
26
+ * closed in another.
27
+ */
16
28
  export declare class ProbabilitySampler implements Sampler {
17
- private readonly probability;
29
+ readonly probability: number;
18
30
  constructor(probability: number);
19
31
  shouldSample(_parentContext?: SpanContext, traceId?: string): SamplingResult;
20
32
  }
21
- /** Delegates to parent context's sampling decision, falls back to a root sampler. */
33
+ /** Options for {@link ParentBasedSampler}. */
34
+ export interface ParentBasedSamplerOptions {
35
+ /** Sampler consulted when there is no parent. Default: always on. */
36
+ readonly root?: Sampler;
37
+ /** Decision when the parent was sampled. Default: sample. */
38
+ readonly remoteParentSampled?: Sampler;
39
+ /** Decision when the parent was not sampled. Default: do not record. */
40
+ readonly remoteParentNotSampled?: Sampler;
41
+ }
42
+ /**
43
+ * Delegates to the parent's sampling decision, falling back to a root sampler.
44
+ *
45
+ * The parent's decision travels in `SpanContext.traceFlags`, which the tracer
46
+ * stamps on every span context it creates and every child inherits.
47
+ */
22
48
  export declare class ParentBasedSampler implements Sampler {
23
- private readonly rootSampler;
24
- constructor(rootSampler?: Sampler);
49
+ private readonly root;
50
+ private readonly parentSampled;
51
+ private readonly parentNotSampled;
52
+ constructor(rootOrOptions?: Sampler | ParentBasedSamplerOptions);
25
53
  shouldSample(parentContext?: SpanContext, traceId?: string): SamplingResult;
26
54
  }
55
+ /** True when a sampling decision means the span should be exported. */
56
+ export declare function isSampled(result: SamplingResult): boolean;
57
+ /** True when a sampling decision means the span should be built at all. */
58
+ export declare function isRecording(result: SamplingResult): boolean;
27
59
  /** Creates an always-on sampler. */
28
60
  export declare function createAlwaysOnSampler(): AlwaysOnSampler;
29
61
  /** Creates an always-off sampler. */
@@ -31,5 +63,5 @@ export declare function createAlwaysOffSampler(): AlwaysOffSampler;
31
63
  /** Creates a probability sampler. */
32
64
  export declare function createProbabilitySampler(probability: number): ProbabilitySampler;
33
65
  /** Creates a parent-based sampler. */
34
- export declare function createParentBasedSampler(rootSampler?: Sampler): ParentBasedSampler;
66
+ export declare function createParentBasedSampler(rootOrOptions?: Sampler | ParentBasedSamplerOptions): ParentBasedSampler;
35
67
  //# sourceMappingURL=sampler.type.d.ts.map
@@ -3,52 +3,93 @@
3
3
  *
4
4
  * Sampling strategies for controlling trace overhead.
5
5
  */
6
+ import { TraceFlags } from "../types.js";
7
+ import { generateHexId, isValidTraceId } from "../internal/index.js";
8
+ import { ObservabilityConfigError } from "../errors/index.js";
9
+ const RECORD_AND_SAMPLE = { decision: "RECORD_AND_SAMPLE" };
10
+ const DO_NOT_RECORD = { decision: "DO_NOT_RECORD" };
6
11
  /** Always records and samples. */
7
12
  export class AlwaysOnSampler {
8
13
  shouldSample() {
9
- return { decision: "RECORD_AND_SAMPLE" };
14
+ return RECORD_AND_SAMPLE;
10
15
  }
11
16
  }
12
17
  /** Never records or samples. */
13
18
  export class AlwaysOffSampler {
14
19
  shouldSample() {
15
- return { decision: "DO_NOT_RECORD" };
20
+ return DO_NOT_RECORD;
16
21
  }
17
22
  }
18
- /** Samples a percentage of traces. */
23
+ /**
24
+ * Samples a fixed fraction of traces.
25
+ *
26
+ * The decision is derived from the trace ID, so every service handling the
27
+ * same trace reaches the same answer and a trace is never sampled in half.
28
+ * The low 8 hex digits are used, matching the OpenTelemetry convention of
29
+ * taking the *trailing* bytes — the leading ones are the least varied in
30
+ * some ID schemes.
31
+ *
32
+ * When no usable trace ID is available the sampler falls back to a random
33
+ * draw at the same rate, rather than failing open in one direction and
34
+ * closed in another.
35
+ */
19
36
  export class ProbabilitySampler {
20
37
  probability;
21
38
  constructor(probability) {
39
+ if (!Number.isFinite(probability)) {
40
+ throw new ObservabilityConfigError(`Sampling probability must be a finite number; received ${probability}`, { probability });
41
+ }
22
42
  this.probability = Math.max(0, Math.min(1, probability));
23
43
  }
24
44
  shouldSample(_parentContext, traceId) {
25
- if (!traceId)
26
- return { decision: "RECORD_AND_SAMPLE" };
27
- // Use the first 8 hex chars as a number for deterministic sampling
28
- const sample = parseInt(traceId.slice(0, 8), 16) / 0xffffffff;
29
- return sample < this.probability
30
- ? { decision: "RECORD_AND_SAMPLE" }
31
- : { decision: "DO_NOT_RECORD" };
45
+ if (this.probability >= 1)
46
+ return RECORD_AND_SAMPLE;
47
+ if (this.probability <= 0)
48
+ return DO_NOT_RECORD;
49
+ const draw = traceId !== undefined && isValidTraceId(traceId)
50
+ ? parseInt(traceId.slice(-8), 16) / 0xffffffff
51
+ : parseInt(generateHexId(4), 16) / 0xffffffff;
52
+ return draw < this.probability ? RECORD_AND_SAMPLE : DO_NOT_RECORD;
32
53
  }
33
54
  }
34
- /** Delegates to parent context's sampling decision, falls back to a root sampler. */
55
+ /**
56
+ * Delegates to the parent's sampling decision, falling back to a root sampler.
57
+ *
58
+ * The parent's decision travels in `SpanContext.traceFlags`, which the tracer
59
+ * stamps on every span context it creates and every child inherits.
60
+ */
35
61
  export class ParentBasedSampler {
36
- rootSampler;
37
- constructor(rootSampler) {
38
- this.rootSampler = rootSampler ?? new AlwaysOnSampler();
62
+ root;
63
+ parentSampled;
64
+ parentNotSampled;
65
+ constructor(rootOrOptions) {
66
+ const options = rootOrOptions !== undefined && "shouldSample" in rootOrOptions
67
+ ? { root: rootOrOptions }
68
+ : (rootOrOptions ?? {});
69
+ this.root = options.root ?? new AlwaysOnSampler();
70
+ this.parentSampled = options.remoteParentSampled ?? new AlwaysOnSampler();
71
+ this.parentNotSampled =
72
+ options.remoteParentNotSampled ?? new AlwaysOffSampler();
39
73
  }
40
74
  shouldSample(parentContext, traceId) {
41
75
  if (!parentContext) {
42
- return this.rootSampler.shouldSample(parentContext, traceId);
43
- }
44
- // If parent is sampled, sample the child too
45
- const flags = parentContext.traceFlags ?? 0;
46
- if ((flags & 1) === 1) {
47
- return { decision: "RECORD_AND_SAMPLE" };
76
+ return this.root.shouldSample(parentContext, traceId);
48
77
  }
49
- return { decision: "DO_NOT_RECORD" };
78
+ const sampled = ((parentContext.traceFlags ?? TraceFlags.NONE) & TraceFlags.SAMPLED) ===
79
+ TraceFlags.SAMPLED;
80
+ return sampled
81
+ ? this.parentSampled.shouldSample(parentContext, traceId)
82
+ : this.parentNotSampled.shouldSample(parentContext, traceId);
50
83
  }
51
84
  }
85
+ /** True when a sampling decision means the span should be exported. */
86
+ export function isSampled(result) {
87
+ return result.decision === "RECORD_AND_SAMPLE";
88
+ }
89
+ /** True when a sampling decision means the span should be built at all. */
90
+ export function isRecording(result) {
91
+ return result.decision !== "DO_NOT_RECORD";
92
+ }
52
93
  /** Creates an always-on sampler. */
53
94
  export function createAlwaysOnSampler() {
54
95
  return new AlwaysOnSampler();
@@ -62,7 +103,7 @@ export function createProbabilitySampler(probability) {
62
103
  return new ProbabilitySampler(probability);
63
104
  }
64
105
  /** Creates a parent-based sampler. */
65
- export function createParentBasedSampler(rootSampler) {
66
- return new ParentBasedSampler(rootSampler);
106
+ export function createParentBasedSampler(rootOrOptions) {
107
+ return new ParentBasedSampler(rootOrOptions);
67
108
  }
68
109
  //# sourceMappingURL=sampler.type.js.map
@@ -3,6 +3,6 @@
3
3
  *
4
4
  * Distributed tracing with spans, context, and exporters.
5
5
  */
6
- export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, } from "./span/index.js";
7
- export { DefaultTracer, createTracer } from "./tracer/index.js";
6
+ export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, isSampledContext, } from "./span/index.js";
7
+ export { DefaultTracer, createTracer, type TracerOptions, } from "./tracer/index.js";
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -3,6 +3,6 @@
3
3
  *
4
4
  * Distributed tracing with spans, context, and exporters.
5
5
  */
6
- export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, } from "./span/index.js";
7
- export { DefaultTracer, createTracer } from "./tracer/index.js";
6
+ export { DefaultSpan, createSpan, createSpanContext, createChildSpanContext, isSampledContext, } from "./span/index.js";
7
+ export { DefaultTracer, createTracer, } 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, } from "./spanContext.type.js";
7
+ export { createSpanContext, createChildSpanContext, isSampledContext, } 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, } from "./spanContext.type.js";
7
+ export { createSpanContext, createChildSpanContext, isSampledContext, } from "./spanContext.type.js";
8
8
  //# sourceMappingURL=index.js.map
@@ -4,11 +4,37 @@
4
4
  * In-memory span that records attributes, events, status, and errors.
5
5
  * Exported as a ReadableSpan when ended.
6
6
  */
7
- import type { ReadableSpan, Span, SpanContext, SpanOptions } from "../../types.js";
7
+ import type { ReadableSpan, Span, SpanContext, SpanLimits, SpanOptions } from "../../types.js";
8
8
  import { SpanKind, SpanStatus } from "../../types.js";
9
+ /** Extra construction options a tracer supplies alongside {@link SpanOptions}. */
10
+ export interface SpanInternalOptions {
11
+ readonly resource?: Record<string, unknown>;
12
+ readonly limits?: SpanLimits;
13
+ /** Record `exception.stacktrace` on `recordError`. Default: `true`. */
14
+ readonly captureStackTraces?: boolean;
15
+ /** Called once, after `end()`, with the completed span. */
16
+ readonly onEnd?: (span: ReadableSpan) => void;
17
+ /**
18
+ * Redacts one attribute before it is stored.
19
+ *
20
+ * Applied inside the span rather than at the exporter, so every processor,
21
+ * exporter and in-process reader sees the redacted value. A span is where
22
+ * an `authorization` header or a request body most often lands, and the
23
+ * configured redaction rules used to stop at the logger.
24
+ */
25
+ readonly redactAttribute?: (key: string, value: unknown) => unknown;
26
+ /**
27
+ * A non-recording span keeps its context — so children still correlate —
28
+ * but drops attributes and events instead of accumulating them.
29
+ */
30
+ readonly recording?: boolean;
31
+ }
9
32
  /**
10
33
  * Default span implementation.
34
+ *
11
35
  * Records attributes, events, errors, and timing until end() is called.
36
+ * Attribute and event counts are capped, because an unbounded span is a
37
+ * memory leak that only shows up under the load that produced it.
12
38
  */
13
39
  export declare class DefaultSpan implements Span {
14
40
  readonly name: string;
@@ -21,10 +47,25 @@ export declare class DefaultSpan implements Span {
21
47
  private statusMessage?;
22
48
  private endTime?;
23
49
  private ended;
24
- private resource;
25
- constructor(name: string, context: SpanContext, options?: SpanOptions & {
26
- readonly resource?: Record<string, unknown>;
27
- });
50
+ private droppedAttributes;
51
+ private droppedEvents;
52
+ private readonly resource;
53
+ private readonly limits;
54
+ private readonly captureStackTraces;
55
+ private readonly recording;
56
+ private readonly onEnd?;
57
+ private readonly redactAttribute?;
58
+ /**
59
+ * Monotonic reference for the duration. `Date` is subject to NTP steps and
60
+ * manual clock changes, which produce negative or inflated durations; the
61
+ * wall-clock timestamps stay for display.
62
+ */
63
+ private readonly startedAt;
64
+ private endedAt?;
65
+ constructor(name: string, context: SpanContext, options?: SpanOptions & SpanInternalOptions);
66
+ /** Redacts, then truncates — a replacement string is never itself cut. */
67
+ private sanitize;
68
+ private truncate;
28
69
  setAttribute(key: string, value: unknown): void;
29
70
  addEvent(name: string, attributes?: Record<string, unknown>): void;
30
71
  setStatus(status: SpanStatus, message?: string): void;
@@ -32,11 +73,13 @@ export declare class DefaultSpan implements Span {
32
73
  end(): void;
33
74
  getDuration(): number;
34
75
  isRecording(): boolean;
76
+ /** True once {@link DefaultSpan.end} has run. */
77
+ hasEnded(): boolean;
78
+ /** The message passed alongside the current status, if any. */
79
+ getStatusMessage(): string | undefined;
35
80
  /** Exports the span as a ReadableSpan. */
36
81
  toReadableSpan(): ReadableSpan;
37
82
  }
38
83
  /** Creates a new span. */
39
- export declare function createSpan(name: string, options?: SpanOptions & {
40
- readonly resource?: Record<string, unknown>;
41
- }): DefaultSpan;
84
+ export declare function createSpan(name: string, options?: SpanOptions & SpanInternalOptions): DefaultSpan;
42
85
  //# sourceMappingURL=span.core.d.ts.map
@@ -6,9 +6,19 @@
6
6
  */
7
7
  import { SpanKind, SpanStatus } from "../../types.js";
8
8
  import { createSpanContext, createChildSpanContext, } from "./spanContext.type.js";
9
+ /** Defaults matching the OpenTelemetry specification. */
10
+ const DEFAULT_LIMITS = {
11
+ maxAttributes: 128,
12
+ maxEvents: 128,
13
+ maxAttributesPerEvent: 128,
14
+ maxAttributeValueLength: 4096,
15
+ };
9
16
  /**
10
17
  * Default span implementation.
18
+ *
11
19
  * Records attributes, events, errors, and timing until end() is called.
20
+ * Attribute and event counts are capped, because an unbounded span is a
21
+ * memory leak that only shows up under the load that produced it.
12
22
  */
13
23
  export class DefaultSpan {
14
24
  name;
@@ -21,31 +31,87 @@ export class DefaultSpan {
21
31
  statusMessage;
22
32
  endTime;
23
33
  ended = false;
34
+ droppedAttributes = 0;
35
+ droppedEvents = 0;
24
36
  resource;
37
+ limits;
38
+ captureStackTraces;
39
+ recording;
40
+ onEnd;
41
+ redactAttribute;
42
+ /**
43
+ * Monotonic reference for the duration. `Date` is subject to NTP steps and
44
+ * manual clock changes, which produce negative or inflated durations; the
45
+ * wall-clock timestamps stay for display.
46
+ */
47
+ startedAt;
48
+ endedAt;
25
49
  constructor(name, context, options) {
26
50
  this.name = name;
27
51
  this.context = context;
28
52
  this.kind = options?.kind ?? SpanKind.INTERNAL;
29
53
  this.startTime = new Date();
54
+ this.startedAt = performance.now();
30
55
  this.resource = { ...(options?.resource ?? {}) };
56
+ this.limits = { ...DEFAULT_LIMITS, ...(options?.limits ?? {}) };
57
+ this.captureStackTraces = options?.captureStackTraces ?? true;
58
+ this.recording = options?.recording ?? true;
59
+ this.onEnd = options?.onEnd;
60
+ this.redactAttribute = options?.redactAttribute;
31
61
  if (options?.attributes) {
32
62
  for (const [key, value] of Object.entries(options.attributes)) {
33
- this.attributes[key] = value;
63
+ this.setAttribute(key, value);
34
64
  }
35
65
  }
36
66
  }
67
+ /** Redacts, then truncates — a replacement string is never itself cut. */
68
+ sanitize(key, value) {
69
+ const redacted = this.redactAttribute
70
+ ? this.redactAttribute(key, value)
71
+ : value;
72
+ return this.truncate(redacted);
73
+ }
74
+ truncate(value) {
75
+ if (typeof value === "string" &&
76
+ value.length > this.limits.maxAttributeValueLength) {
77
+ return value.slice(0, this.limits.maxAttributeValueLength);
78
+ }
79
+ return value;
80
+ }
37
81
  setAttribute(key, value) {
38
- if (this.ended)
82
+ if (this.ended || !this.recording)
83
+ return;
84
+ if (!(key in this.attributes) &&
85
+ Object.keys(this.attributes).length >= this.limits.maxAttributes) {
86
+ this.droppedAttributes++;
39
87
  return;
40
- this.attributes[key] = value;
88
+ }
89
+ this.attributes[key] = this.sanitize(key, value);
41
90
  }
42
91
  addEvent(name, attributes) {
43
- if (this.ended)
92
+ if (this.ended || !this.recording)
93
+ return;
94
+ if (this.events.length >= this.limits.maxEvents) {
95
+ this.droppedEvents++;
44
96
  return;
97
+ }
98
+ let eventAttributes;
99
+ if (attributes) {
100
+ eventAttributes = {};
101
+ let kept = 0;
102
+ for (const [key, value] of Object.entries(attributes)) {
103
+ if (kept >= this.limits.maxAttributesPerEvent) {
104
+ this.droppedAttributes++;
105
+ continue;
106
+ }
107
+ eventAttributes[key] = this.sanitize(key, value);
108
+ kept++;
109
+ }
110
+ }
45
111
  this.events.push({
46
112
  name,
47
113
  timestamp: new Date(),
48
- attributes,
114
+ attributes: eventAttributes,
49
115
  });
50
116
  }
51
117
  setStatus(status, message) {
@@ -62,7 +128,9 @@ export class DefaultSpan {
62
128
  this.addEvent("exception", {
63
129
  "exception.type": error.name,
64
130
  "exception.message": error.message,
65
- "exception.stacktrace": error.stack,
131
+ ...(this.captureStackTraces
132
+ ? { "exception.stacktrace": error.stack }
133
+ : {}),
66
134
  });
67
135
  }
68
136
  end() {
@@ -70,14 +138,23 @@ export class DefaultSpan {
70
138
  return;
71
139
  this.ended = true;
72
140
  this.endTime = new Date();
141
+ this.endedAt = performance.now();
142
+ this.onEnd?.(this.toReadableSpan());
73
143
  }
74
144
  getDuration() {
75
- if (!this.endTime)
76
- return Date.now() - this.startTime.getTime();
77
- return this.endTime.getTime() - this.startTime.getTime();
145
+ const finished = this.endedAt ?? performance.now();
146
+ return finished - this.startedAt;
78
147
  }
79
148
  isRecording() {
80
- return !this.ended;
149
+ return !this.ended && this.recording;
150
+ }
151
+ /** True once {@link DefaultSpan.end} has run. */
152
+ hasEnded() {
153
+ return this.ended;
154
+ }
155
+ /** The message passed alongside the current status, if any. */
156
+ getStatusMessage() {
157
+ return this.statusMessage;
81
158
  }
82
159
  /** Exports the span as a ReadableSpan. */
83
160
  toReadableSpan() {
@@ -89,9 +166,12 @@ export class DefaultSpan {
89
166
  endTime: this.endTime ?? new Date(),
90
167
  duration: this.getDuration(),
91
168
  status: this.status,
169
+ statusMessage: this.statusMessage,
92
170
  attributes: { ...this.attributes },
93
171
  events: [...this.events],
94
172
  resource: { ...this.resource },
173
+ droppedAttributes: this.droppedAttributes,
174
+ droppedEvents: this.droppedEvents,
95
175
  };
96
176
  }
97
177
  }
@@ -4,13 +4,22 @@
4
4
  * Factory for creating span context identifiers.
5
5
  */
6
6
  import type { SpanContext } from "../../types.js";
7
- /** Creates a new span context with random IDs. */
7
+ /** Creates a new span context with cryptographically random IDs. */
8
8
  export declare function createSpanContext(options?: {
9
9
  readonly traceId?: string;
10
10
  readonly spanId?: string;
11
11
  readonly parentSpanId?: string;
12
12
  readonly traceFlags?: number;
13
13
  }): SpanContext;
14
- /** Creates a child span context from a parent. */
15
- export declare function createChildSpanContext(parent: SpanContext): SpanContext;
14
+ /**
15
+ * Creates a child span context from a parent.
16
+ *
17
+ * The trace ID and the trace flags both carry over: dropping the flags is
18
+ * what makes a parent-based sampler discard every child of a sampled trace.
19
+ */
20
+ export declare function createChildSpanContext(parent: SpanContext, overrides?: {
21
+ readonly traceFlags?: number;
22
+ }): SpanContext;
23
+ /** True when the context carries the W3C sampled flag. */
24
+ export declare function isSampledContext(context: SpanContext): boolean;
16
25
  //# sourceMappingURL=spanContext.type.d.ts.map
@@ -3,30 +3,33 @@
3
3
  *
4
4
  * Factory for creating span context identifiers.
5
5
  */
6
- /** Generates a random hex ID. */
7
- function generateHexId(byteLength) {
8
- const bytes = new Uint8Array(byteLength);
9
- for (let i = 0; i < byteLength; i++) {
10
- bytes[i] = Math.floor(Math.random() * 256);
11
- }
12
- return Array.from(bytes)
13
- .map((b) => b.toString(16).padStart(2, "0"))
14
- .join("");
15
- }
16
- /** Creates a new span context with random IDs. */
6
+ import { TraceFlags } from "../../types.js";
7
+ import { generateSpanId, generateTraceId } from "../../internal/index.js";
8
+ /** Creates a new span context with cryptographically random IDs. */
17
9
  export function createSpanContext(options) {
18
10
  return {
19
- traceId: options?.traceId ?? generateHexId(16),
20
- spanId: options?.spanId ?? generateHexId(8),
11
+ traceId: options?.traceId ?? generateTraceId(),
12
+ spanId: options?.spanId ?? generateSpanId(),
21
13
  parentSpanId: options?.parentSpanId,
22
- traceFlags: options?.traceFlags,
14
+ traceFlags: options?.traceFlags ?? TraceFlags.NONE,
23
15
  };
24
16
  }
25
- /** Creates a child span context from a parent. */
26
- export function createChildSpanContext(parent) {
17
+ /**
18
+ * Creates a child span context from a parent.
19
+ *
20
+ * The trace ID and the trace flags both carry over: dropping the flags is
21
+ * what makes a parent-based sampler discard every child of a sampled trace.
22
+ */
23
+ export function createChildSpanContext(parent, overrides) {
27
24
  return createSpanContext({
28
25
  traceId: parent.traceId,
29
26
  parentSpanId: parent.spanId,
27
+ traceFlags: overrides?.traceFlags ?? parent.traceFlags ?? TraceFlags.NONE,
30
28
  });
31
29
  }
30
+ /** True when the context carries the W3C sampled flag. */
31
+ export function isSampledContext(context) {
32
+ return (((context.traceFlags ?? TraceFlags.NONE) & TraceFlags.SAMPLED) ===
33
+ TraceFlags.SAMPLED);
34
+ }
32
35
  //# sourceMappingURL=spanContext.type.js.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @zudojs/observability — Tracer
3
3
  *
4
- * Span creation and processor notification.
4
+ * Span creation, sampling, and processor notification.
5
5
  */
6
- export { DefaultTracer, createTracer } from "./tracer.core.js";
6
+ export { DefaultTracer, createTracer, type TracerOptions, } from "./tracer.core.js";
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @zudojs/observability — Tracer
3
3
  *
4
- * Span creation and processor notification.
4
+ * Span creation, sampling, and processor notification.
5
5
  */
6
- export { DefaultTracer, createTracer } from "./tracer.core.js";
6
+ export { DefaultTracer, createTracer, } from "./tracer.core.js";
7
7
  //# sourceMappingURL=index.js.map
@@ -1,9 +1,34 @@
1
1
  /**
2
2
  * @zudojs/observability — Tracer
3
3
  *
4
- * Creates spans and notifies processors on start/end.
4
+ * Creates spans, applies the sampling decision, and notifies processors on
5
+ * start and end.
5
6
  */
6
- import type { ReadableSpan, Span, SpanExporter, SpanOptions, SpanProcessor, Tracer } from "../../types.js";
7
+ import type { ReadableSpan, Sampler, Span, SpanExporter, SpanLimits, SpanOptions, SpanProcessor, Tracer } from "../../types.js";
8
+ /** Options for {@link DefaultTracer}. */
9
+ export interface TracerOptions {
10
+ readonly processors?: readonly SpanProcessor[];
11
+ /**
12
+ * Exporter used by {@link DefaultTracer.exportSpan} for a direct, unbatched
13
+ * export. Spans created through {@link DefaultTracer.startSpan} reach their
14
+ * backend through the processors, not through this.
15
+ */
16
+ readonly exporter?: SpanExporter;
17
+ readonly resource?: Record<string, unknown>;
18
+ /** Decides which traces are recorded. Default: always on. */
19
+ readonly sampler?: Sampler;
20
+ /** Caps on what a single span may accumulate. */
21
+ readonly limits?: SpanLimits;
22
+ /** Record `exception.stacktrace` on `recordError`. Default: `true`. */
23
+ readonly captureStackTraces?: boolean;
24
+ /**
25
+ * Redacts every span attribute and event attribute before it is recorded.
26
+ * Supply one whenever spans can carry request data.
27
+ */
28
+ readonly redactAttribute?: (key: string, value: unknown) => unknown;
29
+ /** Reports a processor failure that would otherwise be swallowed. */
30
+ readonly onError?: (error: unknown, source: string) => void;
31
+ }
7
32
  /**
8
33
  * Default tracer that creates spans and notifies processors on start/end.
9
34
  */
@@ -11,21 +36,26 @@ export declare class DefaultTracer implements Tracer {
11
36
  private readonly processors;
12
37
  private readonly exporter?;
13
38
  private readonly resource;
14
- constructor(options?: {
15
- readonly processors?: readonly SpanProcessor[];
16
- readonly exporter?: SpanExporter;
17
- readonly resource?: Record<string, unknown>;
18
- });
39
+ private readonly sampler;
40
+ private readonly limits?;
41
+ private readonly captureStackTraces;
42
+ private readonly redactAttribute?;
43
+ private readonly onError?;
44
+ constructor(options?: TracerOptions);
19
45
  startSpan(name: string, options?: SpanOptions): Span;
20
- /** Exports a completed span directly. */
46
+ private notifyEnd;
47
+ /** Exports a completed span directly, bypassing the processors. */
21
48
  exportSpan(span: ReadableSpan): Promise<void>;
22
- /** Shuts down all processors and the exporter. */
49
+ /** Drains every processor without shutting anything down. */
50
+ forceFlush(): Promise<void>;
51
+ /**
52
+ * Shuts down every processor.
53
+ *
54
+ * The exporter is not shut down here: the owner that supplied it shuts it
55
+ * down, and doing it in both places closed the same connection twice.
56
+ */
23
57
  shutdown(): Promise<void>;
24
58
  }
25
59
  /** Creates a tracer. */
26
- export declare function createTracer(options?: {
27
- readonly processors?: readonly SpanProcessor[];
28
- readonly exporter?: SpanExporter;
29
- readonly resource?: Record<string, unknown>;
30
- }): DefaultTracer;
60
+ export declare function createTracer(options?: TracerOptions): DefaultTracer;
31
61
  //# sourceMappingURL=tracer.core.d.ts.map