autotel 2.26.3 → 3.0.3
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 +50 -23
- package/dist/attribute-redacting-processor.cjs +8 -8
- package/dist/attribute-redacting-processor.d.cts +10 -1
- package/dist/attribute-redacting-processor.d.ts +10 -1
- package/dist/attribute-redacting-processor.js +1 -1
- package/dist/attributes.cjs +21 -21
- package/dist/attributes.d.cts +3 -3
- package/dist/attributes.d.ts +3 -3
- package/dist/attributes.js +2 -2
- package/dist/auto.cjs +3 -3
- package/dist/auto.js +2 -2
- package/dist/business-baggage.d.cts +1 -1
- package/dist/business-baggage.d.ts +1 -1
- package/dist/chunk-4P6ZOARG.cjs +33 -0
- package/dist/chunk-4P6ZOARG.cjs.map +1 -0
- package/dist/{chunk-U54FTVFH.js → chunk-52PUSFC2.js} +3 -3
- package/dist/{chunk-U54FTVFH.js.map → chunk-52PUSFC2.js.map} +1 -1
- package/dist/{chunk-YEVCD6DR.cjs → chunk-7SMNC4LS.cjs} +7 -7
- package/dist/{chunk-YEVCD6DR.cjs.map → chunk-7SMNC4LS.cjs.map} +1 -1
- package/dist/{chunk-563EL6O6.cjs → chunk-BPO2PQ3T.cjs} +12 -8
- package/dist/chunk-BPO2PQ3T.cjs.map +1 -0
- package/dist/{chunk-WZOKY3PW.cjs → chunk-DAZ7EGR4.cjs} +19 -19
- package/dist/{chunk-WZOKY3PW.cjs.map → chunk-DAZ7EGR4.cjs.map} +1 -1
- package/dist/{chunk-ER43K7ES.js → chunk-DDXIUZEG.js} +3 -3
- package/dist/{chunk-ER43K7ES.js.map → chunk-DDXIUZEG.js.map} +1 -1
- package/dist/{chunk-JKIMEPI2.cjs → chunk-DQ2SUROF.cjs} +4 -4
- package/dist/{chunk-JKIMEPI2.cjs.map → chunk-DQ2SUROF.cjs.map} +1 -1
- package/dist/{chunk-B3ZHLLMP.js → chunk-DSMSIVTG.js} +2 -2
- package/dist/chunk-DSMSIVTG.js.map +1 -0
- package/dist/{chunk-OBWXM4NN.cjs → chunk-HKZHUGGN.cjs} +15 -14
- package/dist/chunk-HKZHUGGN.cjs.map +1 -0
- package/dist/{chunk-TDNKIHKT.js → chunk-JVWJDHDB.js} +13 -4
- package/dist/chunk-JVWJDHDB.js.map +1 -0
- package/dist/{chunk-YN7USLHW.js → chunk-K7HSRLP5.js} +11 -10
- package/dist/chunk-K7HSRLP5.js.map +1 -0
- package/dist/chunk-KIL5CUN6.js +31 -0
- package/dist/chunk-KIL5CUN6.js.map +1 -0
- package/dist/chunk-KKGM42RQ.cjs +1207 -0
- package/dist/chunk-KKGM42RQ.cjs.map +1 -0
- package/dist/{chunk-6YGUN7IY.cjs → chunk-MOO75VE4.cjs} +18 -17
- package/dist/chunk-MOO75VE4.cjs.map +1 -0
- package/dist/{chunk-GML3FBOT.cjs → chunk-NCSMD3TK.cjs} +2 -2
- package/dist/chunk-NCSMD3TK.cjs.map +1 -0
- package/dist/{chunk-CMNGGTQL.cjs → chunk-NXLRY2CE.cjs} +13 -4
- package/dist/chunk-NXLRY2CE.cjs.map +1 -0
- package/dist/{chunk-BJ2XPN77.js → chunk-OM4OSBOP.js} +5 -5
- package/dist/{chunk-BJ2XPN77.js.map → chunk-OM4OSBOP.js.map} +1 -1
- package/dist/{chunk-HPUGKUMZ.js → chunk-PMRWMRXY.js} +13 -640
- package/dist/chunk-PMRWMRXY.js.map +1 -0
- package/dist/{chunk-UTZR7P7E.cjs → chunk-QPH5ZKP5.cjs} +43 -673
- package/dist/chunk-QPH5ZKP5.cjs.map +1 -0
- package/dist/chunk-SEO6NAQT.js +14 -0
- package/dist/chunk-SEO6NAQT.js.map +1 -0
- package/dist/{chunk-QC5MNKVF.js → chunk-TFRZOUTV.js} +13 -12
- package/dist/chunk-TFRZOUTV.js.map +1 -0
- package/dist/chunk-VQTCQKHQ.cjs +17 -0
- package/dist/chunk-VQTCQKHQ.cjs.map +1 -0
- package/dist/chunk-Z7VAOK5X.js +1183 -0
- package/dist/chunk-Z7VAOK5X.js.map +1 -0
- package/dist/{chunk-W35FVJBC.js → chunk-ZDPIWKWD.js} +9 -5
- package/dist/chunk-ZDPIWKWD.js.map +1 -0
- package/dist/correlation-id.cjs +22 -10
- package/dist/correlation-id.js +14 -2
- package/dist/decorators.cjs +7 -8
- package/dist/decorators.cjs.map +1 -1
- package/dist/decorators.d.cts +1 -1
- package/dist/decorators.d.ts +1 -1
- package/dist/decorators.js +6 -7
- package/dist/decorators.js.map +1 -1
- package/dist/event.cjs +8 -9
- package/dist/event.js +5 -6
- package/dist/functional.cjs +13 -14
- package/dist/functional.d.cts +1 -1
- package/dist/functional.d.ts +1 -1
- package/dist/functional.js +6 -7
- package/dist/http.cjs +13 -2
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +12 -1
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +305 -280
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -10
- package/dist/index.d.ts +89 -10
- package/dist/index.js +180 -181
- package/dist/index.js.map +1 -1
- package/dist/instrumentation.cjs +9 -9
- package/dist/instrumentation.js +2 -2
- package/dist/messaging-adapters.d.cts +1 -1
- package/dist/messaging-adapters.d.ts +1 -1
- package/dist/messaging-testing.d.cts +1 -1
- package/dist/messaging-testing.d.ts +1 -1
- package/dist/messaging.cjs +11 -11
- package/dist/messaging.d.cts +1 -1
- package/dist/messaging.d.ts +1 -1
- package/dist/messaging.js +8 -8
- package/dist/semantic-helpers.cjs +11 -12
- package/dist/semantic-helpers.d.cts +1 -1
- package/dist/semantic-helpers.d.ts +1 -1
- package/dist/semantic-helpers.js +7 -8
- package/dist/{trace-context-t5X1AP-e.d.cts → trace-context-DbGKd1Rn.d.cts} +18 -5
- package/dist/{trace-context-t5X1AP-e.d.ts → trace-context-DbGKd1Rn.d.ts} +18 -5
- package/dist/trace-helpers.cjs +13 -13
- package/dist/trace-helpers.d.cts +2 -2
- package/dist/trace-helpers.d.ts +2 -2
- package/dist/trace-helpers.js +1 -1
- package/dist/{utils-CbUkl8r1.d.cts → utils-BahBCFtJ.d.cts} +1 -1
- package/dist/{utils-Buel3cj0.d.ts → utils-CLKwaUlG.d.ts} +1 -1
- package/dist/webhook.cjs +21 -12
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +1 -1
- package/dist/webhook.d.ts +1 -1
- package/dist/webhook.js +20 -11
- package/dist/webhook.js.map +1 -1
- package/dist/workflow-distributed.cjs +25 -21
- package/dist/workflow-distributed.cjs.map +1 -1
- package/dist/workflow-distributed.d.cts +1 -1
- package/dist/workflow-distributed.d.ts +1 -1
- package/dist/workflow-distributed.js +23 -19
- package/dist/workflow-distributed.js.map +1 -1
- package/dist/workflow.cjs +12 -12
- package/dist/workflow.d.cts +1 -1
- package/dist/workflow.d.ts +1 -1
- package/dist/workflow.js +8 -8
- package/package.json +43 -45
- package/skills/analyze-traces/SKILL.md +178 -0
- package/skills/autotel-core/SKILL.md +2 -7
- package/skills/autotel-events/SKILL.md +2 -6
- package/skills/autotel-frameworks/SKILL.md +2 -9
- package/skills/autotel-instrumentation/SKILL.md +2 -7
- package/skills/autotel-request-logging/SKILL.md +2 -8
- package/skills/autotel-structured-errors/SKILL.md +2 -7
- package/skills/build-audit-trails/SKILL.md +302 -0
- package/skills/debug-missing-spans/SKILL.md +248 -0
- package/skills/migrate-to-autotel/SKILL.md +268 -0
- package/skills/review-otel-patterns/SKILL.md +488 -0
- package/skills/review-otel-patterns/references/code-review.md +75 -0
- package/skills/review-otel-patterns/references/processor-pipeline.md +205 -0
- package/skills/review-otel-patterns/references/structured-errors.md +102 -0
- package/skills/review-otel-patterns/references/wide-spans.md +85 -0
- package/skills/tune-sampling/SKILL.md +210 -0
- package/src/attribute-redacting-processor.test.ts +6 -4
- package/src/attribute-redacting-processor.ts +11 -2
- package/src/correlated-events.test.ts +151 -0
- package/src/correlated-events.ts +47 -0
- package/src/drain-toolkit.test.ts +113 -0
- package/src/drain-toolkit.ts +129 -0
- package/src/enricher-toolkit.test.ts +67 -0
- package/src/enricher-toolkit.ts +79 -0
- package/src/functional.ts +2 -0
- package/src/gen-ai-events.ts +14 -5
- package/src/index.ts +39 -4
- package/src/messaging.ts +10 -9
- package/src/redact-values.test.ts +24 -10
- package/src/redact-values.ts +9 -2
- package/src/request-logger.test.ts +91 -0
- package/src/request-logger.ts +40 -5
- package/src/structured-error.test.ts +86 -1
- package/src/structured-error.ts +9 -2
- package/src/trace-context.ts +39 -11
- package/src/trace-helpers.ts +2 -2
- package/src/trace-hybrid.test.ts +42 -0
- package/src/trace-hybrid.ts +37 -0
- package/src/webhook.ts +16 -7
- package/src/workflow-distributed.ts +18 -13
- package/src/workflow.ts +7 -6
- package/bin/intent.js +0 -6
- package/dist/chunk-563EL6O6.cjs.map +0 -1
- package/dist/chunk-6YGUN7IY.cjs.map +0 -1
- package/dist/chunk-B3ZHLLMP.js.map +0 -1
- package/dist/chunk-BBBWDIYQ.js +0 -211
- package/dist/chunk-BBBWDIYQ.js.map +0 -1
- package/dist/chunk-CMNGGTQL.cjs.map +0 -1
- package/dist/chunk-D5LMF53P.cjs +0 -150
- package/dist/chunk-D5LMF53P.cjs.map +0 -1
- package/dist/chunk-GML3FBOT.cjs.map +0 -1
- package/dist/chunk-HPUGKUMZ.js.map +0 -1
- package/dist/chunk-HZ3FYBJG.cjs +0 -217
- package/dist/chunk-HZ3FYBJG.cjs.map +0 -1
- package/dist/chunk-JSNUWSBH.cjs +0 -62
- package/dist/chunk-JSNUWSBH.cjs.map +0 -1
- package/dist/chunk-OBWXM4NN.cjs.map +0 -1
- package/dist/chunk-QC5MNKVF.js.map +0 -1
- package/dist/chunk-S4OFEXLA.js +0 -53
- package/dist/chunk-S4OFEXLA.js.map +0 -1
- package/dist/chunk-TDNKIHKT.js.map +0 -1
- package/dist/chunk-UTZR7P7E.cjs.map +0 -1
- package/dist/chunk-W35FVJBC.js.map +0 -1
- package/dist/chunk-WD4RP6IV.js +0 -146
- package/dist/chunk-WD4RP6IV.js.map +0 -1
- package/dist/chunk-YN7USLHW.js.map +0 -1
- package/src/package-manifest.test.ts +0 -24
package/src/index.ts
CHANGED
|
@@ -60,12 +60,16 @@ export {
|
|
|
60
60
|
AttributeRedactingProcessor,
|
|
61
61
|
REDACTOR_PATTERNS,
|
|
62
62
|
REDACTOR_PRESETS,
|
|
63
|
+
builtinPatterns,
|
|
63
64
|
createAttributeRedactor,
|
|
64
65
|
createRedactedSpan,
|
|
66
|
+
normalizeAttributeRedactorConfig,
|
|
65
67
|
type AttributeRedactorFn,
|
|
66
68
|
type AttributeRedactorPreset,
|
|
67
69
|
type AttributeRedactorConfig,
|
|
68
70
|
type AttributeRedactingProcessorOptions,
|
|
71
|
+
type BuiltinPatternName,
|
|
72
|
+
type MaskFn,
|
|
69
73
|
type ValuePatternConfig,
|
|
70
74
|
} from './attribute-redacting-processor';
|
|
71
75
|
|
|
@@ -81,7 +85,6 @@ export type {
|
|
|
81
85
|
InstrumentOptions,
|
|
82
86
|
} from './functional';
|
|
83
87
|
export {
|
|
84
|
-
trace,
|
|
85
88
|
instrument,
|
|
86
89
|
withTracing,
|
|
87
90
|
span,
|
|
@@ -89,6 +92,9 @@ export {
|
|
|
89
92
|
withBaggage,
|
|
90
93
|
ctx,
|
|
91
94
|
} from './functional';
|
|
95
|
+
// `trace` is the hybrid: callable like autotel's `trace(fn)` AND carries the
|
|
96
|
+
// full `@opentelemetry/api` TraceAPI surface (getActiveSpan, getTracer, etc).
|
|
97
|
+
export { trace } from './trace-hybrid';
|
|
92
98
|
|
|
93
99
|
// Operation context (for advanced usage)
|
|
94
100
|
export type { OperationContext } from './operation-context';
|
|
@@ -121,6 +127,8 @@ export {
|
|
|
121
127
|
type RequestLogger,
|
|
122
128
|
type RequestLogSnapshot,
|
|
123
129
|
type RequestLoggerOptions,
|
|
130
|
+
type ForkLifecycle,
|
|
131
|
+
type ForkOptions,
|
|
124
132
|
} from './request-logger';
|
|
125
133
|
|
|
126
134
|
// Structured errors
|
|
@@ -145,6 +153,19 @@ export {
|
|
|
145
153
|
type DrainPipelineOptions,
|
|
146
154
|
type PipelineDrainFn,
|
|
147
155
|
} from './drain-pipeline';
|
|
156
|
+
export {
|
|
157
|
+
defineDrain,
|
|
158
|
+
defineHttpDrain,
|
|
159
|
+
type DrainOptions,
|
|
160
|
+
type HttpDrainOptions,
|
|
161
|
+
type HttpDrainRequest,
|
|
162
|
+
} from './drain-toolkit';
|
|
163
|
+
export {
|
|
164
|
+
defineEnricher,
|
|
165
|
+
type EnricherDefinition,
|
|
166
|
+
type EnrichContext,
|
|
167
|
+
type EnricherOptions,
|
|
168
|
+
} from './enricher-toolkit';
|
|
148
169
|
|
|
149
170
|
// Pretty log formatting
|
|
150
171
|
export { formatDuration } from './pretty-log-formatter';
|
|
@@ -333,19 +354,33 @@ export {
|
|
|
333
354
|
type TLSAttrs,
|
|
334
355
|
} from './attributes';
|
|
335
356
|
|
|
336
|
-
// Re-export common OpenTelemetry types and utilities
|
|
337
|
-
//
|
|
357
|
+
// Re-export common OpenTelemetry types and utilities so plugins, apps, and
|
|
358
|
+
// existing OTel code can `import { ... } from 'autotel'` without also taking
|
|
359
|
+
// a separate `@opentelemetry/api` dependency.
|
|
338
360
|
export type {
|
|
339
361
|
Span,
|
|
340
362
|
SpanContext,
|
|
363
|
+
SpanAttributes,
|
|
341
364
|
Tracer,
|
|
365
|
+
TracerProvider,
|
|
342
366
|
Context,
|
|
367
|
+
Attributes,
|
|
368
|
+
AttributeValue,
|
|
369
|
+
Link,
|
|
343
370
|
Link as SpanLink,
|
|
371
|
+
TimeInput,
|
|
372
|
+
HrTime,
|
|
373
|
+
Baggage,
|
|
374
|
+
BaggageEntry,
|
|
375
|
+
Exception,
|
|
376
|
+
TraceFlags,
|
|
377
|
+
TraceState,
|
|
344
378
|
TextMapSetter,
|
|
345
379
|
TextMapGetter,
|
|
346
380
|
} from '@opentelemetry/api';
|
|
347
381
|
export { SpanKind, ROOT_CONTEXT } from '@opentelemetry/api';
|
|
348
|
-
// Note: trace
|
|
382
|
+
// Note: trace, context, propagation, SpanStatusCode already exported above
|
|
383
|
+
// (`trace` is the hybrid; `otelTrace` is the pure TraceAPI singleton).
|
|
349
384
|
|
|
350
385
|
// Export typed baggage helper
|
|
351
386
|
export { defineBaggageSchema } from './trace-context';
|
package/src/messaging.ts
CHANGED
|
@@ -50,6 +50,7 @@ import type {
|
|
|
50
50
|
} from '@opentelemetry/api';
|
|
51
51
|
import { trace } from './functional';
|
|
52
52
|
import type { TraceContext } from './trace-context';
|
|
53
|
+
import { emitCorrelatedEvent } from './correlated-events';
|
|
53
54
|
import { createLinkFromHeaders, extractLinksFromBatch } from './sampling';
|
|
54
55
|
|
|
55
56
|
// ============================================================================
|
|
@@ -1406,7 +1407,7 @@ function extendContextForConsumer(
|
|
|
1406
1407
|
producerLink.context.spanId;
|
|
1407
1408
|
}
|
|
1408
1409
|
|
|
1409
|
-
baseCtx
|
|
1410
|
+
emitCorrelatedEvent(baseCtx, 'dlq_routed', eventAttrs);
|
|
1410
1411
|
|
|
1411
1412
|
// Call user's onDLQ callback if provided
|
|
1412
1413
|
if (config.onDLQ) {
|
|
@@ -1447,7 +1448,7 @@ function extendContextForConsumer(
|
|
|
1447
1448
|
}),
|
|
1448
1449
|
};
|
|
1449
1450
|
|
|
1450
|
-
baseCtx
|
|
1451
|
+
emitCorrelatedEvent(baseCtx, 'dlq_replay', eventAttrs);
|
|
1451
1452
|
},
|
|
1452
1453
|
|
|
1453
1454
|
recordRetry(attemptNumber: number, maxAttempts?: number): void {
|
|
@@ -1455,7 +1456,7 @@ function extendContextForConsumer(
|
|
|
1455
1456
|
if (maxAttempts !== undefined) {
|
|
1456
1457
|
baseCtx.setAttribute('messaging.retry.max_attempts', maxAttempts);
|
|
1457
1458
|
}
|
|
1458
|
-
baseCtx
|
|
1459
|
+
emitCorrelatedEvent(baseCtx, 'retry_attempt', {
|
|
1459
1460
|
'messaging.retry.count': attemptNumber,
|
|
1460
1461
|
...(maxAttempts !== undefined && {
|
|
1461
1462
|
'messaging.retry.max_attempts': maxAttempts,
|
|
@@ -1585,7 +1586,7 @@ function extendContextForConsumer(
|
|
|
1585
1586
|
event.partitions.map((p) => `${p.topic}:${p.partition}`).join(',');
|
|
1586
1587
|
}
|
|
1587
1588
|
|
|
1588
|
-
baseCtx
|
|
1589
|
+
emitCorrelatedEvent(baseCtx, `consumer_group_${event.type}`, eventAttrs);
|
|
1589
1590
|
|
|
1590
1591
|
// Call user's onRebalance callback if provided
|
|
1591
1592
|
if (config.consumerGroupTracking?.onRebalance) {
|
|
@@ -1627,7 +1628,7 @@ function extendContextForConsumer(
|
|
|
1627
1628
|
);
|
|
1628
1629
|
}
|
|
1629
1630
|
|
|
1630
|
-
baseCtx
|
|
1631
|
+
emitCorrelatedEvent(baseCtx, 'consumer_group_heartbeat', {
|
|
1631
1632
|
'messaging.consumer_group.heartbeat.healthy': healthy,
|
|
1632
1633
|
'messaging.consumer_group.heartbeat.timestamp':
|
|
1633
1634
|
groupState.lastHeartbeat,
|
|
@@ -1644,7 +1645,7 @@ function extendContextForConsumer(
|
|
|
1644
1645
|
baseCtx.setAttribute(`${prefix}.end_offset`, lag.endOffset);
|
|
1645
1646
|
baseCtx.setAttribute(`${prefix}.lag`, lag.lag);
|
|
1646
1647
|
|
|
1647
|
-
baseCtx
|
|
1648
|
+
emitCorrelatedEvent(baseCtx, 'partition_lag_recorded', {
|
|
1648
1649
|
'messaging.consumer_group.lag.topic': lag.topic,
|
|
1649
1650
|
'messaging.consumer_group.lag.partition': lag.partition,
|
|
1650
1651
|
'messaging.consumer_group.lag.current_offset': lag.currentOffset,
|
|
@@ -1899,7 +1900,7 @@ async function extractLagMetrics(
|
|
|
1899
1900
|
ctx.setAttribute('messaging.kafka.consumer_lag', lag);
|
|
1900
1901
|
|
|
1901
1902
|
// Add lag event
|
|
1902
|
-
ctx
|
|
1903
|
+
emitCorrelatedEvent(ctx, 'consumer_lag_measured', {
|
|
1903
1904
|
'messaging.kafka.consumer_lag': lag,
|
|
1904
1905
|
'messaging.kafka.message.offset': currentOffset,
|
|
1905
1906
|
'messaging.kafka.high_watermark': endOffset,
|
|
@@ -2119,7 +2120,7 @@ function extractAndProcessOrdering(
|
|
|
2119
2120
|
}
|
|
2120
2121
|
|
|
2121
2122
|
// Add event for each out-of-order message
|
|
2122
|
-
ctx
|
|
2123
|
+
emitCorrelatedEvent(ctx, 'message_out_of_order', {
|
|
2123
2124
|
'messaging.ordering.batch_index': i,
|
|
2124
2125
|
'messaging.ordering.current_sequence': msgSequence,
|
|
2125
2126
|
'messaging.ordering.expected_sequence': expectedSequence,
|
|
@@ -2155,7 +2156,7 @@ function extractAndProcessOrdering(
|
|
|
2155
2156
|
duplicateCount++;
|
|
2156
2157
|
|
|
2157
2158
|
// Add event for each duplicate
|
|
2158
|
-
ctx
|
|
2159
|
+
emitCorrelatedEvent(ctx, 'message_duplicate', {
|
|
2159
2160
|
'messaging.ordering.batch_index': i,
|
|
2160
2161
|
'messaging.message.id': msgId,
|
|
2161
2162
|
});
|
|
@@ -10,18 +10,32 @@ describe('createStringRedactor', () => {
|
|
|
10
10
|
redact = createStringRedactor('default');
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
it('
|
|
13
|
+
it('smart-masks emails', () => {
|
|
14
14
|
expect(redact('Contact user@example.com for info')).toBe(
|
|
15
|
-
'Contact
|
|
15
|
+
'Contact u***@***.com for info',
|
|
16
16
|
);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
it('
|
|
20
|
-
expect(redact('Call
|
|
19
|
+
it('smart-masks international phone numbers (country code + last 2 digits)', () => {
|
|
20
|
+
expect(redact('Call +33 1 23 45 67 89 now')).toBe('Call +33******89 now');
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it('
|
|
24
|
-
expect(redact('
|
|
23
|
+
it('smart-masks phone numbers with parens (last 2 digits)', () => {
|
|
24
|
+
expect(redact('Call (415) 555-1234 now')).toBe('Call ********34 now');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('smart-masks common US phone formats', () => {
|
|
28
|
+
expect(redact('Call 555-123-4567 now')).toBe('Call ********67 now');
|
|
29
|
+
expect(redact('Call 5551234567 now')).toBe('Call ********67 now');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('does not mistake bare digit runs for phone numbers', () => {
|
|
33
|
+
// UUIDs, order ids etc. should pass through untouched.
|
|
34
|
+
expect(redact('Order: 12345678 ok')).toBe('Order: 12345678 ok');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('smart-masks credit card numbers (last four digits preserved)', () => {
|
|
38
|
+
expect(redact('Card: 4111-1111-1111-1111')).toBe('Card: ****1111');
|
|
25
39
|
});
|
|
26
40
|
|
|
27
41
|
it('returns input unchanged when no patterns match', () => {
|
|
@@ -36,15 +50,15 @@ describe('createStringRedactor', () => {
|
|
|
36
50
|
redact = createStringRedactor('strict');
|
|
37
51
|
});
|
|
38
52
|
|
|
39
|
-
it('
|
|
53
|
+
it('smart-masks JWTs', () => {
|
|
40
54
|
const jwt =
|
|
41
55
|
'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.abc123_-def';
|
|
42
|
-
expect(redact(`Token: ${jwt}`)).toBe('Token:
|
|
56
|
+
expect(redact(`Token: ${jwt}`)).toBe('Token: eyJ***.***');
|
|
43
57
|
});
|
|
44
58
|
|
|
45
|
-
it('
|
|
59
|
+
it('smart-masks bearer tokens', () => {
|
|
46
60
|
expect(redact('Authorization: Bearer abc123.xyz')).toBe(
|
|
47
|
-
'Authorization:
|
|
61
|
+
'Authorization: Bearer ***',
|
|
48
62
|
);
|
|
49
63
|
});
|
|
50
64
|
});
|
package/src/redact-values.ts
CHANGED
|
@@ -18,9 +18,16 @@ export function createStringRedactor(
|
|
|
18
18
|
|
|
19
19
|
return (value: string): string => {
|
|
20
20
|
let result = value;
|
|
21
|
-
for (const { pattern, replacement } of valuePatterns) {
|
|
21
|
+
for (const { pattern, replacement, mask } of valuePatterns) {
|
|
22
22
|
pattern.lastIndex = 0;
|
|
23
|
-
|
|
23
|
+
// Smart masks (e.g. email → a***@***.com) take precedence over the
|
|
24
|
+
// static replacement so callers see the same output as the
|
|
25
|
+
// span-attribute redactor does.
|
|
26
|
+
if (mask) {
|
|
27
|
+
result = result.replaceAll(pattern, (match) => mask(match));
|
|
28
|
+
} else {
|
|
29
|
+
result = result.replaceAll(pattern, replacement ?? defaultReplacement);
|
|
30
|
+
}
|
|
24
31
|
}
|
|
25
32
|
return result;
|
|
26
33
|
};
|
|
@@ -254,6 +254,97 @@ describe('log.fork()', () => {
|
|
|
254
254
|
expect(ctx.setAttributes).not.toHaveBeenCalled();
|
|
255
255
|
expect(childSpan.end).toHaveBeenCalledTimes(1);
|
|
256
256
|
});
|
|
257
|
+
|
|
258
|
+
it('fork lifecycle hooks fire around child handler', async () => {
|
|
259
|
+
const ctx = createMockContext();
|
|
260
|
+
const log = getRequestLogger(ctx);
|
|
261
|
+
const childSpan = {
|
|
262
|
+
spanContext: () => ({
|
|
263
|
+
traceId: 'a'.repeat(32),
|
|
264
|
+
spanId: 'b'.repeat(16),
|
|
265
|
+
}),
|
|
266
|
+
setAttribute: vi.fn(),
|
|
267
|
+
setAttributes: vi.fn(),
|
|
268
|
+
setStatus: vi.fn(),
|
|
269
|
+
recordException: vi.fn(),
|
|
270
|
+
addEvent: vi.fn(),
|
|
271
|
+
addLink: vi.fn(),
|
|
272
|
+
addLinks: vi.fn(),
|
|
273
|
+
updateName: vi.fn(),
|
|
274
|
+
isRecording: vi.fn(() => true),
|
|
275
|
+
end: vi.fn(),
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
vi.spyOn(otelTrace, 'getTracer').mockReturnValue({
|
|
279
|
+
startActiveSpan: (
|
|
280
|
+
_name: string,
|
|
281
|
+
cb: (span: typeof childSpan) => Promise<void>,
|
|
282
|
+
) => cb(childSpan),
|
|
283
|
+
} as unknown as ReturnType<typeof otelTrace.getTracer>);
|
|
284
|
+
|
|
285
|
+
const calls: string[] = [];
|
|
286
|
+
const onChildEnter = vi.fn(() => calls.push('enter'));
|
|
287
|
+
const onChildExit = vi.fn(() => calls.push('exit'));
|
|
288
|
+
|
|
289
|
+
log.fork(
|
|
290
|
+
'bg',
|
|
291
|
+
async () => {
|
|
292
|
+
calls.push('handler');
|
|
293
|
+
},
|
|
294
|
+
{ lifecycle: { onChildEnter, onChildExit } },
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
298
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
299
|
+
|
|
300
|
+
expect(onChildEnter).toHaveBeenCalledTimes(1);
|
|
301
|
+
expect(onChildExit).toHaveBeenCalledTimes(1);
|
|
302
|
+
expect(calls).toEqual(['enter', 'handler', 'exit']);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('fork onChildExit hook errors do not crash fork cleanup', async () => {
|
|
306
|
+
const ctx = createMockContext();
|
|
307
|
+
const log = getRequestLogger(ctx);
|
|
308
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
309
|
+
const childSpan = {
|
|
310
|
+
spanContext: () => ({
|
|
311
|
+
traceId: 'a'.repeat(32),
|
|
312
|
+
spanId: 'b'.repeat(16),
|
|
313
|
+
}),
|
|
314
|
+
setAttribute: vi.fn(),
|
|
315
|
+
setAttributes: vi.fn(),
|
|
316
|
+
setStatus: vi.fn(),
|
|
317
|
+
recordException: vi.fn(),
|
|
318
|
+
addEvent: vi.fn(),
|
|
319
|
+
addLink: vi.fn(),
|
|
320
|
+
addLinks: vi.fn(),
|
|
321
|
+
updateName: vi.fn(),
|
|
322
|
+
isRecording: vi.fn(() => true),
|
|
323
|
+
end: vi.fn(),
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
vi.spyOn(otelTrace, 'getTracer').mockReturnValue({
|
|
327
|
+
startActiveSpan: (
|
|
328
|
+
_name: string,
|
|
329
|
+
cb: (span: typeof childSpan) => Promise<void>,
|
|
330
|
+
) => cb(childSpan),
|
|
331
|
+
} as unknown as ReturnType<typeof otelTrace.getTracer>);
|
|
332
|
+
|
|
333
|
+
log.fork('bg', async () => {}, {
|
|
334
|
+
lifecycle: {
|
|
335
|
+
onChildExit: () => {
|
|
336
|
+
throw new Error('hook exploded');
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
342
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
343
|
+
|
|
344
|
+
expect(childSpan.end).toHaveBeenCalledTimes(1);
|
|
345
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
346
|
+
consoleSpy.mockRestore();
|
|
347
|
+
});
|
|
257
348
|
});
|
|
258
349
|
|
|
259
350
|
describe('getRequestLogger', () => {
|
package/src/request-logger.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { TraceContext } from './trace-context';
|
|
|
4
4
|
import { createTraceContext } from './trace-context';
|
|
5
5
|
import { recordStructuredError } from './structured-error';
|
|
6
6
|
import { flattenToAttributes } from './flatten-attributes';
|
|
7
|
+
import { emitCorrelatedEvent } from './correlated-events';
|
|
7
8
|
|
|
8
9
|
const POST_EMIT_FORK_HINT =
|
|
9
10
|
"For intentional background work tied to this request, use log.fork('label', fn) when available.";
|
|
@@ -55,7 +56,11 @@ export interface RequestLogger {
|
|
|
55
56
|
error(error: Error | string, fields?: Record<string, unknown>): void;
|
|
56
57
|
getContext(): Record<string, unknown>;
|
|
57
58
|
emitNow(overrides?: Record<string, unknown>): RequestLogSnapshot;
|
|
58
|
-
fork(
|
|
59
|
+
fork(
|
|
60
|
+
label: string,
|
|
61
|
+
fn: () => void | Promise<void>,
|
|
62
|
+
options?: ForkOptions,
|
|
63
|
+
): void;
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
export interface RequestLogSnapshot {
|
|
@@ -71,6 +76,21 @@ export interface RequestLoggerOptions {
|
|
|
71
76
|
onEmit?: (snapshot: RequestLogSnapshot) => void | Promise<void>;
|
|
72
77
|
}
|
|
73
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Optional lifecycle hooks for adapters that need to track child loggers
|
|
81
|
+
* spawned by `log.fork()` (e.g. active logger maps in framework integrations).
|
|
82
|
+
*/
|
|
83
|
+
export interface ForkLifecycle {
|
|
84
|
+
/** Called after the child logger is created, before `fn` runs. */
|
|
85
|
+
onChildEnter?: (child: RequestLogger) => void;
|
|
86
|
+
/** Called after the child has finished (emit + drain), success or failure. */
|
|
87
|
+
onChildExit?: (child: RequestLogger) => void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface ForkOptions {
|
|
91
|
+
lifecycle?: ForkLifecycle;
|
|
92
|
+
}
|
|
93
|
+
|
|
74
94
|
function resolveContext(ctx?: TraceContext): TraceContext {
|
|
75
95
|
if (ctx) return ctx;
|
|
76
96
|
|
|
@@ -101,9 +121,9 @@ export function getRequestLogger(
|
|
|
101
121
|
fields?: Record<string, unknown>,
|
|
102
122
|
) => {
|
|
103
123
|
const attrs = fields ? flattenToAttributes(fields) : undefined;
|
|
104
|
-
activeContext
|
|
124
|
+
emitCorrelatedEvent(activeContext, `log.${level}`, {
|
|
105
125
|
message,
|
|
106
|
-
...attrs,
|
|
126
|
+
...(attrs ?? {}),
|
|
107
127
|
});
|
|
108
128
|
};
|
|
109
129
|
|
|
@@ -191,7 +211,7 @@ export function getRequestLogger(
|
|
|
191
211
|
context: mergedContext,
|
|
192
212
|
};
|
|
193
213
|
|
|
194
|
-
activeContext
|
|
214
|
+
emitCorrelatedEvent(activeContext, 'log.emit.manual', {
|
|
195
215
|
...flattened,
|
|
196
216
|
});
|
|
197
217
|
|
|
@@ -206,7 +226,11 @@ export function getRequestLogger(
|
|
|
206
226
|
return snapshot;
|
|
207
227
|
},
|
|
208
228
|
|
|
209
|
-
fork(
|
|
229
|
+
fork(
|
|
230
|
+
label: string,
|
|
231
|
+
fn: () => void | Promise<void>,
|
|
232
|
+
forkOptions?: ForkOptions,
|
|
233
|
+
): void {
|
|
210
234
|
const parentRequestId = activeContext.correlationId;
|
|
211
235
|
if (typeof parentRequestId !== 'string' || parentRequestId.length === 0) {
|
|
212
236
|
throw new Error(
|
|
@@ -216,6 +240,7 @@ export function getRequestLogger(
|
|
|
216
240
|
}
|
|
217
241
|
|
|
218
242
|
const tracer = otelTrace.getTracer('autotel.request-logger');
|
|
243
|
+
const lifecycle = forkOptions?.lifecycle;
|
|
219
244
|
void tracer.startActiveSpan(`request.fork:${label}`, (childSpan) => {
|
|
220
245
|
const childContext: TraceContext = {
|
|
221
246
|
...createTraceContext(childSpan),
|
|
@@ -229,6 +254,8 @@ export function getRequestLogger(
|
|
|
229
254
|
_parentCorrelationId: parentRequestId,
|
|
230
255
|
});
|
|
231
256
|
|
|
257
|
+
lifecycle?.onChildEnter?.(childLog);
|
|
258
|
+
|
|
232
259
|
void Promise.resolve()
|
|
233
260
|
.then(() => fn())
|
|
234
261
|
.then(() => {
|
|
@@ -240,6 +267,14 @@ export function getRequestLogger(
|
|
|
240
267
|
childLog.emitNow();
|
|
241
268
|
})
|
|
242
269
|
.finally(() => {
|
|
270
|
+
try {
|
|
271
|
+
lifecycle?.onChildExit?.(childLog);
|
|
272
|
+
} catch (hookError) {
|
|
273
|
+
console.warn(
|
|
274
|
+
'[autotel] fork onChildExit hook threw:',
|
|
275
|
+
hookError,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
243
278
|
childSpan.end();
|
|
244
279
|
});
|
|
245
280
|
});
|
|
@@ -1,10 +1,41 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { Span, SpanContext } from '@opentelemetry/api';
|
|
2
3
|
import {
|
|
3
4
|
createStructuredError,
|
|
4
5
|
getStructuredErrorAttributes,
|
|
5
6
|
recordStructuredError,
|
|
6
7
|
} from './structured-error';
|
|
7
|
-
import type
|
|
8
|
+
import { createTraceContext, type TraceContext } from './trace-context';
|
|
9
|
+
|
|
10
|
+
function createFakeSpan(): {
|
|
11
|
+
span: Span;
|
|
12
|
+
recordException: ReturnType<typeof vi.fn>;
|
|
13
|
+
setStatus: ReturnType<typeof vi.fn>;
|
|
14
|
+
setAttributes: ReturnType<typeof vi.fn>;
|
|
15
|
+
} {
|
|
16
|
+
const recordException = vi.fn();
|
|
17
|
+
const setStatus = vi.fn();
|
|
18
|
+
const setAttributes = vi.fn();
|
|
19
|
+
const spanContext: SpanContext = {
|
|
20
|
+
traceId: '0123456789abcdef0123456789abcdef',
|
|
21
|
+
spanId: '0123456789abcdef',
|
|
22
|
+
traceFlags: 1,
|
|
23
|
+
};
|
|
24
|
+
const span = {
|
|
25
|
+
spanContext: () => spanContext,
|
|
26
|
+
setAttribute: vi.fn(),
|
|
27
|
+
setAttributes,
|
|
28
|
+
setStatus,
|
|
29
|
+
recordException,
|
|
30
|
+
addEvent: vi.fn(),
|
|
31
|
+
addLink: vi.fn(),
|
|
32
|
+
addLinks: vi.fn(),
|
|
33
|
+
updateName: vi.fn(),
|
|
34
|
+
isRecording: () => true,
|
|
35
|
+
end: vi.fn(),
|
|
36
|
+
} as unknown as Span;
|
|
37
|
+
return { span, recordException, setStatus, setAttributes };
|
|
38
|
+
}
|
|
8
39
|
|
|
9
40
|
describe('structured-error helpers', () => {
|
|
10
41
|
it('creates an error with structured diagnostic fields', () => {
|
|
@@ -104,3 +135,57 @@ describe('structured-error helpers', () => {
|
|
|
104
135
|
);
|
|
105
136
|
});
|
|
106
137
|
});
|
|
138
|
+
|
|
139
|
+
describe('ctx.recordError', () => {
|
|
140
|
+
it('records a structured error onto the underlying span', () => {
|
|
141
|
+
const { span, recordException, setStatus, setAttributes } =
|
|
142
|
+
createFakeSpan();
|
|
143
|
+
const ctx = createTraceContext(span);
|
|
144
|
+
const err = createStructuredError({
|
|
145
|
+
message: 'Order failed',
|
|
146
|
+
why: 'Inventory unavailable',
|
|
147
|
+
fix: 'Retry after restock',
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
ctx.recordError(err);
|
|
151
|
+
|
|
152
|
+
expect(recordException).toHaveBeenCalledWith(err);
|
|
153
|
+
expect(setStatus).toHaveBeenCalledWith({
|
|
154
|
+
code: 2,
|
|
155
|
+
message: 'Order failed',
|
|
156
|
+
});
|
|
157
|
+
expect(setAttributes).toHaveBeenCalledWith(
|
|
158
|
+
expect.objectContaining({
|
|
159
|
+
'error.message': 'Order failed',
|
|
160
|
+
'error.why': 'Inventory unavailable',
|
|
161
|
+
'error.fix': 'Retry after restock',
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('coerces non-Error values to Error so it is safe in catch blocks', () => {
|
|
167
|
+
const { span, recordException, setStatus, setAttributes } =
|
|
168
|
+
createFakeSpan();
|
|
169
|
+
const ctx = createTraceContext(span);
|
|
170
|
+
|
|
171
|
+
ctx.recordError('boom');
|
|
172
|
+
|
|
173
|
+
expect(recordException).toHaveBeenCalledTimes(1);
|
|
174
|
+
const recorded = recordException.mock.calls[0][0];
|
|
175
|
+
expect(recorded).toBeInstanceOf(Error);
|
|
176
|
+
expect(recorded.message).toBe('boom');
|
|
177
|
+
expect(setStatus).toHaveBeenCalledWith({ code: 2, message: 'boom' });
|
|
178
|
+
expect(setAttributes).toHaveBeenCalled();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('ctx.track', () => {
|
|
183
|
+
it('exposes track on the trace context as the ergonomic replacement for ctx.addEvent', () => {
|
|
184
|
+
const { span } = createFakeSpan();
|
|
185
|
+
const ctx = createTraceContext(span);
|
|
186
|
+
|
|
187
|
+
expect(typeof ctx.track).toBe('function');
|
|
188
|
+
// Smoke test — should not throw without init() (track is a no-op when no queue is configured)
|
|
189
|
+
expect(() => ctx.track('test.event', { foo: 'bar' })).not.toThrow();
|
|
190
|
+
});
|
|
191
|
+
});
|
package/src/structured-error.ts
CHANGED
|
@@ -138,10 +138,17 @@ export function getStructuredErrorAttributes(
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
export function recordStructuredError(
|
|
141
|
-
ctx: Pick<TraceContext, '
|
|
141
|
+
ctx: Pick<TraceContext, 'setAttributes' | 'setStatus'>,
|
|
142
142
|
error: Error,
|
|
143
143
|
): void {
|
|
144
|
-
|
|
144
|
+
const maybeRecordException = (
|
|
145
|
+
ctx as unknown as {
|
|
146
|
+
recordException?: (e: Error) => void;
|
|
147
|
+
}
|
|
148
|
+
).recordException;
|
|
149
|
+
if (typeof maybeRecordException === 'function') {
|
|
150
|
+
maybeRecordException(error);
|
|
151
|
+
}
|
|
145
152
|
ctx.setStatus({
|
|
146
153
|
code: SpanStatusCode.ERROR,
|
|
147
154
|
message: error.message,
|
package/src/trace-context.ts
CHANGED
|
@@ -8,10 +8,11 @@ import type {
|
|
|
8
8
|
BaggageEntry,
|
|
9
9
|
Context,
|
|
10
10
|
Link,
|
|
11
|
-
TimeInput,
|
|
12
11
|
} from '@opentelemetry/api';
|
|
13
12
|
import { context, propagation } from '@opentelemetry/api';
|
|
14
13
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
14
|
+
import { recordStructuredError } from './structured-error';
|
|
15
|
+
import { track } from './track';
|
|
15
16
|
|
|
16
17
|
type AsyncLocalBox<T> = {
|
|
17
18
|
value: T;
|
|
@@ -132,14 +133,6 @@ export interface SpanMethods {
|
|
|
132
133
|
setAttributes(attrs: Record<string, AttributeValue>): void;
|
|
133
134
|
/** Set the status of the span */
|
|
134
135
|
setStatus(status: { code: SpanStatusCode; message?: string }): void;
|
|
135
|
-
/** Record an exception on the span */
|
|
136
|
-
recordException(exception: Error, time?: TimeInput): void;
|
|
137
|
-
/** Add an event to the span (for logging milestones/checkpoints) */
|
|
138
|
-
addEvent(
|
|
139
|
-
name: string,
|
|
140
|
-
attributesOrStartTime?: Record<string, AttributeValue> | TimeInput,
|
|
141
|
-
startTime?: TimeInput,
|
|
142
|
-
): void;
|
|
143
136
|
/** Add a link to another span */
|
|
144
137
|
addLink(link: Link): void;
|
|
145
138
|
/** Add multiple links to other spans */
|
|
@@ -148,6 +141,26 @@ export interface SpanMethods {
|
|
|
148
141
|
updateName(name: string): void;
|
|
149
142
|
/** Check if the span is recording */
|
|
150
143
|
isRecording(): boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Record an error on the span: sets ERROR status, structured `error.*`
|
|
146
|
+
* attributes (including `why`/`fix`/`link` from `createStructuredError`),
|
|
147
|
+
* and during the OTel Span Event API back-compat window also records the
|
|
148
|
+
* exception via the legacy span event API.
|
|
149
|
+
*
|
|
150
|
+
* Replaces the deprecated `recordException` (OTEP 4430). Accepts `unknown`
|
|
151
|
+
* so it can be called directly with the value caught from a `catch` block.
|
|
152
|
+
*/
|
|
153
|
+
recordError(error: unknown): void;
|
|
154
|
+
/**
|
|
155
|
+
* Emit a tracked event correlated to this span. Equivalent to the standalone
|
|
156
|
+
* `track(event, data)` but reads naturally on `ctx`. Replaces the deprecated
|
|
157
|
+
* `ctx.addEvent` (OTEP 4430) — events become correlated logs rather than
|
|
158
|
+
* span events.
|
|
159
|
+
*/
|
|
160
|
+
track<Events extends Record<string, unknown> = Record<string, unknown>>(
|
|
161
|
+
event: keyof Events & string,
|
|
162
|
+
data?: Events[keyof Events & string],
|
|
163
|
+
): void;
|
|
151
164
|
}
|
|
152
165
|
|
|
153
166
|
/**
|
|
@@ -411,7 +424,13 @@ export function createTraceContext<
|
|
|
411
424
|
: never,
|
|
412
425
|
};
|
|
413
426
|
|
|
414
|
-
|
|
427
|
+
// `recordException` and `addEvent` are intentionally bound at runtime but
|
|
428
|
+
// omitted from the `SpanMethods` type. They exist solely so existing call
|
|
429
|
+
// sites keep working through the OTel Span Event API deprecation window
|
|
430
|
+
// (see MIGRATION.md). New code MUST go through `recordStructuredError`,
|
|
431
|
+
// `emitCorrelatedEvent`, or the request logger. The cast below is what hides
|
|
432
|
+
// these compatibility-only fields from the public type.
|
|
433
|
+
const traceCtx = {
|
|
415
434
|
traceId: spanContext.traceId,
|
|
416
435
|
spanId: spanContext.spanId,
|
|
417
436
|
correlationId: spanContext.traceId.slice(0, 16),
|
|
@@ -424,8 +443,17 @@ export function createTraceContext<
|
|
|
424
443
|
addLinks: span.addLinks.bind(span),
|
|
425
444
|
updateName: span.updateName.bind(span),
|
|
426
445
|
isRecording: span.isRecording.bind(span),
|
|
446
|
+
recordError: (error: unknown) => {
|
|
447
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
448
|
+
recordStructuredError(traceCtx, err);
|
|
449
|
+
},
|
|
450
|
+
track: (event: string, data?: Record<string, unknown>) => {
|
|
451
|
+
track(event, data);
|
|
452
|
+
},
|
|
427
453
|
...baggageHelpers,
|
|
428
|
-
}
|
|
454
|
+
} as unknown as TraceContext<TBaggage>;
|
|
455
|
+
|
|
456
|
+
return traceCtx;
|
|
429
457
|
}
|
|
430
458
|
|
|
431
459
|
/**
|