autotel-edge 3.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +333 -0
  3. package/dist/chunk-F32WSLNX.js +309 -0
  4. package/dist/chunk-F32WSLNX.js.map +1 -0
  5. package/dist/events.d.ts +86 -0
  6. package/dist/events.js +157 -0
  7. package/dist/events.js.map +1 -0
  8. package/dist/index.d.ts +326 -0
  9. package/dist/index.js +921 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/logger.d.ts +89 -0
  12. package/dist/logger.js +81 -0
  13. package/dist/logger.js.map +1 -0
  14. package/dist/sampling.d.ts +166 -0
  15. package/dist/sampling.js +108 -0
  16. package/dist/sampling.js.map +1 -0
  17. package/dist/testing.d.ts +2 -0
  18. package/dist/testing.js +3 -0
  19. package/dist/testing.js.map +1 -0
  20. package/dist/types-Dj85cPUj.d.ts +182 -0
  21. package/package.json +88 -0
  22. package/src/api/logger.test.ts +367 -0
  23. package/src/api/logger.ts +197 -0
  24. package/src/compose.ts +243 -0
  25. package/src/core/buffer.ts +16 -0
  26. package/src/core/config.test.ts +388 -0
  27. package/src/core/config.ts +167 -0
  28. package/src/core/context.ts +224 -0
  29. package/src/core/exporter.ts +99 -0
  30. package/src/core/provider.ts +45 -0
  31. package/src/core/span.ts +222 -0
  32. package/src/core/spanprocessor.test.ts +521 -0
  33. package/src/core/spanprocessor.ts +232 -0
  34. package/src/core/trace-context.ts +66 -0
  35. package/src/core/tracer.test.ts +123 -0
  36. package/src/core/tracer.ts +216 -0
  37. package/src/events/index.test.ts +242 -0
  38. package/src/events/index.ts +338 -0
  39. package/src/events.ts +6 -0
  40. package/src/functional.test.ts +702 -0
  41. package/src/functional.ts +846 -0
  42. package/src/index.ts +81 -0
  43. package/src/logger.ts +13 -0
  44. package/src/sampling/index.test.ts +297 -0
  45. package/src/sampling/index.ts +276 -0
  46. package/src/sampling.ts +6 -0
  47. package/src/testing/index.ts +9 -0
  48. package/src/testing.ts +6 -0
  49. package/src/types.ts +267 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Span processor with flush and tail sampling support
3
+ */
4
+
5
+ import type { Context } from '@opentelemetry/api';
6
+ import type {
7
+ ReadableSpan,
8
+ Span,
9
+ SpanExporter,
10
+ SpanProcessor,
11
+ } from '@opentelemetry/sdk-trace-base';
12
+ import type { PostProcessorFn, TailSampleFn, LocalTrace } from '../types';
13
+
14
+ /**
15
+ * Span processor that supports flush by trace ID and tail sampling
16
+ */
17
+ export class SpanProcessorWithFlush implements SpanProcessor {
18
+ private exporter: SpanExporter;
19
+ private postProcessor?: PostProcessorFn;
20
+ private spans: Map<string, ReadableSpan[]> = new Map();
21
+
22
+ constructor(exporter: SpanExporter, postProcessor?: PostProcessorFn) {
23
+ this.exporter = exporter;
24
+ this.postProcessor = postProcessor;
25
+ }
26
+
27
+ onStart(_span: Span, _parentContext: Context): void {
28
+ // No-op for now
29
+ }
30
+
31
+ onEnd(span: ReadableSpan): void {
32
+ const traceId = span.spanContext().traceId;
33
+
34
+ if (!this.spans.has(traceId)) {
35
+ this.spans.set(traceId, []);
36
+ }
37
+
38
+ this.spans.get(traceId)!.push(span);
39
+ }
40
+
41
+ /**
42
+ * Force flush spans for a specific trace
43
+ */
44
+ async forceFlush(traceId?: string): Promise<void> {
45
+ if (traceId) {
46
+ const spans = this.spans.get(traceId);
47
+ if (spans && spans.length > 0) {
48
+ await this.exportSpans(spans);
49
+ this.spans.delete(traceId);
50
+ }
51
+ } else {
52
+ // Flush all traces
53
+ const promises: Promise<void>[] = [];
54
+ for (const [id, spans] of this.spans.entries()) {
55
+ promises.push(this.exportSpans(spans));
56
+ this.spans.delete(id);
57
+ }
58
+ await Promise.all(promises);
59
+ }
60
+ }
61
+
62
+ async shutdown(): Promise<void> {
63
+ await this.forceFlush();
64
+ if (this.exporter) {
65
+ await this.exporter.shutdown();
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Export spans with post-processing
71
+ * Errors are caught and logged but don't throw to prevent worker instability
72
+ */
73
+ private async exportSpans(spans: ReadableSpan[]): Promise<void> {
74
+ if (spans.length === 0) return;
75
+ if (!this.exporter) return; // No exporter configured (e.g., in tests)
76
+
77
+ let processedSpans = spans;
78
+
79
+ if (this.postProcessor) {
80
+ try {
81
+ processedSpans = this.postProcessor(spans);
82
+ } catch (error) {
83
+ // Post-processor errors should not prevent export
84
+ console.error('[autotel-edge] Post-processor error:', error);
85
+ // Continue with original spans
86
+ processedSpans = spans;
87
+ }
88
+ }
89
+
90
+ return new Promise((resolve) => {
91
+ this.exporter.export(processedSpans, (result) => {
92
+ if (result.code === 0) {
93
+ // SUCCESS
94
+ resolve();
95
+ } else {
96
+ // Log but don't reject - exporter failures shouldn't crash the worker
97
+ console.error(
98
+ '[autotel-edge] Exporter error:',
99
+ result.error?.message || 'Unknown error',
100
+ );
101
+ resolve(); // Resolve instead of reject to prevent unhandled promise rejection
102
+ }
103
+ });
104
+ });
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Span processor that supports tail sampling decisions
110
+ */
111
+ export class TailSamplingSpanProcessor implements SpanProcessor {
112
+ private wrapped: SpanProcessorWithFlush;
113
+ private tailSampler?: TailSampleFn;
114
+ private traces: Map<string, LocalTrace> = new Map();
115
+
116
+ constructor(
117
+ exporter: SpanExporter,
118
+ postProcessor?: PostProcessorFn,
119
+ tailSampler?: TailSampleFn,
120
+ ) {
121
+ this.wrapped = new SpanProcessorWithFlush(exporter, postProcessor);
122
+ this.tailSampler = tailSampler;
123
+ }
124
+
125
+ onStart(span: Span, parentContext: Context): void {
126
+ this.wrapped.onStart(span, parentContext);
127
+ }
128
+
129
+ onEnd(span: ReadableSpan): void {
130
+ const traceId = span.spanContext().traceId;
131
+ const spanId = span.spanContext().spanId;
132
+ const parentSpanId = 'parentSpanId' in span ? span.parentSpanId : undefined;
133
+
134
+ // Initialize trace if not exists
135
+ if (!this.traces.has(traceId)) {
136
+ this.traces.set(traceId, {
137
+ traceId,
138
+ spans: [],
139
+ localRootSpan: undefined as any, // Will be set when we identify the local root
140
+ });
141
+ }
142
+
143
+ const trace = this.traces.get(traceId)!;
144
+
145
+ // Determine if this span is a local root by checking if its parent is in buffered spans
146
+ // A span is a local root if:
147
+ // 1. It has no parentSpanId (definitive root)
148
+ // 2. Its parentSpanId doesn't match any already-buffered span (remote parent = distributed trace entry)
149
+ const hasLocalParent = parentSpanId &&
150
+ trace.spans.some(s => s.spanContext().spanId === parentSpanId);
151
+
152
+ // Set localRootSpan if this is the local root (no local parent found in buffer)
153
+ if (!hasLocalParent) {
154
+ trace.localRootSpan = span;
155
+ }
156
+
157
+ trace.spans.push(span); // Buffer the span AFTER checking parent relationships
158
+
159
+ // Auto-flush decision: only auto-flush for normal traces (no parentSpanId at all)
160
+ // For distributed traces (parentSpanId present), we rely on explicit forceFlush() from instrument.ts
161
+ // This ensures we don't trigger before all spans have been buffered
162
+ const isDefinitiveRoot = !parentSpanId;
163
+ const shouldAutoFlush = isDefinitiveRoot && trace.localRootSpan &&
164
+ trace.localRootSpan.spanContext().spanId === spanId;
165
+
166
+ if (shouldAutoFlush) {
167
+ if (this.tailSampler) {
168
+ const shouldKeep = this.tailSampler(trace);
169
+
170
+ if (shouldKeep) {
171
+ // Export ALL buffered spans in the trace
172
+ for (const bufferedSpan of trace.spans) {
173
+ this.wrapped.onEnd(bufferedSpan);
174
+ }
175
+ // Force flush to actually export the spans
176
+ void this.wrapped.forceFlush(traceId);
177
+ }
178
+ // If not keeping, just drop all spans (don't export)
179
+ } else {
180
+ // No tail sampler, export all buffered spans
181
+ for (const bufferedSpan of trace.spans) {
182
+ this.wrapped.onEnd(bufferedSpan);
183
+ }
184
+ // Force flush to actually export the spans
185
+ void this.wrapped.forceFlush(traceId);
186
+ }
187
+
188
+ // Clean up trace after decision
189
+ this.traces.delete(traceId);
190
+ }
191
+ // If not local root span, just buffer it - don't export yet
192
+ }
193
+
194
+ async forceFlush(traceId?: string): Promise<void> {
195
+ if (traceId) {
196
+ // Make tail sampling decision for this specific trace before flushing
197
+ const trace = this.traces.get(traceId);
198
+ if (trace) {
199
+ // Ensure localRootSpan is set (fallback to first span if not)
200
+ // This handles distributed traces where no span has undefined parentSpanId
201
+ if (!trace.localRootSpan && trace.spans.length > 0) {
202
+ trace.localRootSpan = trace.spans[0];
203
+ }
204
+
205
+ if (this.tailSampler) {
206
+ const shouldKeep = this.tailSampler(trace);
207
+
208
+ if (shouldKeep) {
209
+ // Export ALL buffered spans in the trace
210
+ for (const bufferedSpan of trace.spans) {
211
+ this.wrapped.onEnd(bufferedSpan);
212
+ }
213
+ }
214
+ } else {
215
+ // No tail sampler, export all buffered spans
216
+ for (const bufferedSpan of trace.spans) {
217
+ this.wrapped.onEnd(bufferedSpan);
218
+ }
219
+ }
220
+
221
+ // Clean up trace after decision
222
+ this.traces.delete(traceId);
223
+ }
224
+ }
225
+ return this.wrapped.forceFlush(traceId);
226
+ }
227
+
228
+ async shutdown(): Promise<void> {
229
+ this.traces.clear();
230
+ return this.wrapped.shutdown();
231
+ }
232
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Trace context types and utilities
3
+ */
4
+
5
+ import type { Span, SpanStatusCode } from '@opentelemetry/api';
6
+
7
+ /**
8
+ * WeakMap to store span names for active spans.
9
+ * Enables retrieving span names for correlation helpers.
10
+ */
11
+ const spanNameMap = new WeakMap<Span, string>();
12
+
13
+ /**
14
+ * Base trace context containing trace identifiers
15
+ */
16
+ export interface TraceContextBase {
17
+ traceId: string;
18
+ spanId: string;
19
+ correlationId: string;
20
+ 'code.function'?: string;
21
+ }
22
+
23
+ /**
24
+ * Span methods available on trace context
25
+ */
26
+ export interface SpanMethods {
27
+ setAttribute(key: string, value: string | number | boolean): void;
28
+ setAttributes(attrs: Record<string, string | number | boolean>): void;
29
+ setStatus(status: { code: SpanStatusCode; message?: string }): void;
30
+ recordException(exception: Error): void;
31
+ }
32
+
33
+ /**
34
+ * Complete trace context that merges base context and span methods
35
+ *
36
+ * This is the ctx parameter passed to factory functions in trace().
37
+ * It provides access to trace IDs and span manipulation methods.
38
+ */
39
+ export type TraceContext = TraceContextBase & SpanMethods;
40
+
41
+ /**
42
+ * Create a TraceContext from an OpenTelemetry Span
43
+ *
44
+ * This utility extracts trace context information from a span
45
+ * and provides span manipulation methods in a consistent format.
46
+ */
47
+ export function createTraceContext(span: Span): TraceContext {
48
+ const spanContext = span.spanContext();
49
+ return {
50
+ traceId: spanContext.traceId,
51
+ spanId: spanContext.spanId,
52
+ correlationId: spanContext.traceId.slice(0, 16),
53
+ 'code.function': spanNameMap.get(span),
54
+ setAttribute: span.setAttribute.bind(span),
55
+ setAttributes: span.setAttributes.bind(span),
56
+ setStatus: span.setStatus.bind(span),
57
+ recordException: span.recordException.bind(span),
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Store the span name for later retrieval via trace context helpers.
63
+ */
64
+ export function setSpanName(span: Span, name: string): void {
65
+ spanNameMap.set(span, name);
66
+ }
@@ -0,0 +1,123 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { WorkerTracer } from './tracer';
3
+ import { resourceFromAttributes } from '@opentelemetry/resources';
4
+ import { SamplingDecision, type SpanProcessor } from '@opentelemetry/sdk-trace-base';
5
+
6
+ describe('WorkerTracer', () => {
7
+ let tracer: WorkerTracer;
8
+ let mockProcessor: SpanProcessor;
9
+ let mockHeadSampler: any;
10
+
11
+ beforeEach(() => {
12
+ mockProcessor = {
13
+ onStart: vi.fn(),
14
+ onEnd: vi.fn(),
15
+ shutdown: vi.fn(async () => {}),
16
+ forceFlush: vi.fn(async () => {}),
17
+ };
18
+
19
+ mockHeadSampler = {
20
+ shouldSample: vi.fn(() => ({
21
+ decision: SamplingDecision.RECORD_AND_SAMPLED,
22
+ attributes: {},
23
+ traceState: undefined,
24
+ })),
25
+ toString: () => 'MockHeadSampler',
26
+ };
27
+
28
+ const resource = resourceFromAttributes({});
29
+ tracer = new WorkerTracer([mockProcessor], resource);
30
+ tracer.setHeadSampler(mockHeadSampler);
31
+ });
32
+
33
+ describe('Per-span sampler', () => {
34
+ it('should use per-span sampler when provided in options', () => {
35
+ const perSpanSampler = {
36
+ shouldSample: vi.fn(() => ({
37
+ decision: SamplingDecision.NOT_RECORD,
38
+ attributes: {},
39
+ traceState: undefined,
40
+ })),
41
+ toString: () => 'PerSpanSampler',
42
+ };
43
+
44
+ const options: any = { sampler: perSpanSampler };
45
+ tracer.startSpan('test.span', options);
46
+
47
+ // Per-span sampler should be called, NOT head sampler
48
+ expect(perSpanSampler.shouldSample).toHaveBeenCalledTimes(1);
49
+ expect(mockHeadSampler.shouldSample).not.toHaveBeenCalled();
50
+ });
51
+
52
+ it('should use head sampler when no per-span sampler provided', () => {
53
+ tracer.startSpan('test.span', {});
54
+
55
+ // Head sampler should be called
56
+ expect(mockHeadSampler.shouldSample).toHaveBeenCalledTimes(1);
57
+ });
58
+
59
+ it('should pass correct arguments to per-span sampler', () => {
60
+ const perSpanSampler = {
61
+ shouldSample: vi.fn(() => ({
62
+ decision: SamplingDecision.RECORD_AND_SAMPLED,
63
+ attributes: {},
64
+ traceState: undefined,
65
+ })),
66
+ toString: () => 'PerSpanSampler',
67
+ };
68
+
69
+ const options: any = {
70
+ sampler: perSpanSampler,
71
+ attributes: { 'test.attr': 'value' },
72
+ };
73
+
74
+ tracer.startSpan('test.span', options);
75
+
76
+ // Verify sampler was called with correct arguments
77
+ expect(perSpanSampler.shouldSample).toHaveBeenCalledWith(
78
+ expect.anything(), // context
79
+ expect.any(String), // traceId
80
+ 'test.span', // span name
81
+ expect.any(Number), // spanKind
82
+ expect.objectContaining({ 'test.attr': 'value' }), // attributes
83
+ [], // links
84
+ );
85
+ });
86
+
87
+ it('should respect per-span sampler decision to NOT_RECORD', () => {
88
+ const rejectingSampler = {
89
+ shouldSample: vi.fn(() => ({
90
+ decision: SamplingDecision.NOT_RECORD,
91
+ attributes: {},
92
+ traceState: undefined,
93
+ })),
94
+ toString: () => 'RejectingSampler',
95
+ };
96
+
97
+ const options: any = { sampler: rejectingSampler };
98
+ const span = tracer.startSpan('test.span', options);
99
+
100
+ // Span should be created but not sampled
101
+ expect(span).toBeDefined();
102
+ expect(span.spanContext().traceFlags & 1).toBe(0); // Not sampled
103
+ });
104
+
105
+ it('should respect per-span sampler decision to RECORD_AND_SAMPLED', () => {
106
+ const acceptingSampler = {
107
+ shouldSample: vi.fn(() => ({
108
+ decision: SamplingDecision.RECORD_AND_SAMPLED,
109
+ attributes: {},
110
+ traceState: undefined,
111
+ })),
112
+ toString: () => 'AcceptingSampler',
113
+ };
114
+
115
+ const options: any = { sampler: acceptingSampler };
116
+ const span = tracer.startSpan('test.span', options);
117
+
118
+ // Span should be created and sampled
119
+ expect(span).toBeDefined();
120
+ expect(span.spanContext().traceFlags & 1).toBe(1); // Sampled
121
+ });
122
+ });
123
+ });
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Lightweight WorkerTracer for edge environments
3
+ */
4
+
5
+ import type {
6
+ Attributes,
7
+ Tracer,
8
+ Span,
9
+ SpanKind,
10
+ SpanOptions,
11
+ Context,
12
+ } from '@opentelemetry/api';
13
+ import {
14
+ context as api_context,
15
+ trace,
16
+ type SpanContext,
17
+ } from '@opentelemetry/api';
18
+ import { sanitizeAttributes } from '@opentelemetry/core';
19
+ import type { Resource } from '@opentelemetry/resources';
20
+ import {
21
+ type SpanProcessor,
22
+ RandomIdGenerator,
23
+ type ReadableSpan,
24
+ SamplingDecision,
25
+ } from '@opentelemetry/sdk-trace-base';
26
+
27
+ import { SpanImpl } from './span';
28
+ import type { TraceFlushableSpanProcessor } from '../types';
29
+
30
+ const NewTraceFlags = {
31
+ RANDOM_TRACE_ID_SET: 2,
32
+ RANDOM_TRACE_ID_UNSET: 0,
33
+ } as const;
34
+
35
+ type NewTraceFlagValues =
36
+ | typeof NewTraceFlags.RANDOM_TRACE_ID_SET
37
+ | typeof NewTraceFlags.RANDOM_TRACE_ID_UNSET;
38
+
39
+ const idGenerator: RandomIdGenerator = new RandomIdGenerator();
40
+
41
+ let withNextSpanAttributes: Attributes;
42
+
43
+ function getFlagAt(flagSequence: number, position: number): number {
44
+ return ((flagSequence >> (position - 1)) & 1) * position;
45
+ }
46
+
47
+ /**
48
+ * WorkerTracer - Lightweight tracer for edge environments
49
+ */
50
+ export class WorkerTracer implements Tracer {
51
+ private readonly spanProcessors: TraceFlushableSpanProcessor[];
52
+ private readonly resource: Resource;
53
+ private headSampler: any; // Will be set via setHeadSampler
54
+
55
+ constructor(spanProcessors: SpanProcessor[], resource: Resource) {
56
+ this.spanProcessors = spanProcessors as TraceFlushableSpanProcessor[];
57
+ this.resource = resource;
58
+ }
59
+
60
+ /**
61
+ * Set the head sampler (called from config)
62
+ */
63
+ setHeadSampler(sampler: any): void {
64
+ this.headSampler = sampler;
65
+ }
66
+
67
+ /**
68
+ * Force flush spans for a specific trace
69
+ */
70
+ async forceFlush(traceId?: string) {
71
+ const promises = this.spanProcessors.map(async (spanProcessor) => {
72
+ await spanProcessor.forceFlush(traceId);
73
+ });
74
+ await Promise.allSettled(promises);
75
+ }
76
+
77
+ /**
78
+ * Add extra resource attributes
79
+ */
80
+ addToResource(extra: Resource) {
81
+ this.resource.merge(extra);
82
+ }
83
+
84
+ /**
85
+ * Start a new span
86
+ */
87
+ startSpan(
88
+ name: string,
89
+ options: SpanOptions = {},
90
+ context = api_context.active(),
91
+ ): Span {
92
+ if (options.root) {
93
+ context = trace.deleteSpan(context);
94
+ }
95
+
96
+ if (!this.headSampler) {
97
+ throw new Error(
98
+ 'Head sampler not configured. This is a bug in the instrumentation logic',
99
+ );
100
+ }
101
+
102
+ const parentSpanContext = trace.getSpan(context)?.spanContext();
103
+ const { traceId, randomTraceFlag } = getTraceInfo(parentSpanContext);
104
+
105
+ const spanKind = options.kind || (0 as SpanKind); // SpanKind.INTERNAL
106
+ const sanitisedAttrs = sanitizeAttributes(options.attributes);
107
+
108
+ // Use per-span sampler if provided, otherwise use head sampler
109
+ const optionsWithSampler = options as any;
110
+ const sampler = optionsWithSampler.sampler || this.headSampler;
111
+
112
+ const samplingDecision = sampler.shouldSample(
113
+ context,
114
+ traceId,
115
+ name,
116
+ spanKind,
117
+ sanitisedAttrs,
118
+ [],
119
+ );
120
+ const { decision, traceState, attributes: attrs } = samplingDecision;
121
+
122
+ const attributes = Object.assign(
123
+ {},
124
+ options.attributes,
125
+ attrs,
126
+ withNextSpanAttributes,
127
+ );
128
+ withNextSpanAttributes = {};
129
+
130
+ const spanId = idGenerator.generateSpanId();
131
+ const parentSpanId = parentSpanContext?.spanId;
132
+
133
+ const sampleFlag =
134
+ decision === SamplingDecision.RECORD_AND_SAMPLED ? 1 : 0; // TraceFlags.SAMPLED : TraceFlags.NONE
135
+ const traceFlags = sampleFlag + randomTraceFlag;
136
+ const spanContext: SpanContext = { traceId, spanId, traceFlags, traceState };
137
+
138
+ const span = new SpanImpl({
139
+ attributes: sanitizeAttributes(attributes),
140
+ name,
141
+ onEnd: (span) => {
142
+ for (const sp of this.spanProcessors) {
143
+ sp.onEnd(span as unknown as ReadableSpan);
144
+ }
145
+ },
146
+ resource: this.resource,
147
+ spanContext,
148
+ parentSpanContext,
149
+ parentSpanId,
150
+ spanKind,
151
+ startTime: options.startTime,
152
+ });
153
+
154
+ for (const sp of this.spanProcessors) {
155
+ //@ts-ignore - OTel type quirk
156
+ sp.onStart(span, context);
157
+ }
158
+
159
+ return span;
160
+ }
161
+
162
+ /**
163
+ * Start an active span (with automatic context management)
164
+ */
165
+ startActiveSpan<F extends (span: Span) => ReturnType<F>>(
166
+ name: string,
167
+ fn: F,
168
+ ): ReturnType<F>;
169
+ startActiveSpan<F extends (span: Span) => ReturnType<F>>(
170
+ name: string,
171
+ options: SpanOptions,
172
+ fn: F,
173
+ ): ReturnType<F>;
174
+ startActiveSpan<F extends (span: Span) => ReturnType<F>>(
175
+ name: string,
176
+ options: SpanOptions,
177
+ context: Context,
178
+ fn: F,
179
+ ): ReturnType<F>;
180
+ startActiveSpan<F extends (span: Span) => ReturnType<F>>(
181
+ name: string,
182
+ ...args: unknown[]
183
+ ): ReturnType<F> {
184
+ const options = args.length > 1 ? (args[0] as SpanOptions) : undefined;
185
+ const parentContext =
186
+ args.length > 2 ? (args[1] as Context) : api_context.active();
187
+ const fn = args.at(-1) as F;
188
+
189
+ const span = this.startSpan(name, options, parentContext);
190
+ const contextWithSpanSet = trace.setSpan(parentContext, span);
191
+
192
+ return api_context.with(contextWithSpanSet, fn, undefined, span);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Set attributes for the next span created
198
+ */
199
+ export function withNextSpan(attrs: Attributes) {
200
+ withNextSpanAttributes = Object.assign({}, withNextSpanAttributes, attrs);
201
+ }
202
+
203
+ function getTraceInfo(parentSpanContext?: SpanContext): {
204
+ traceId: string;
205
+ randomTraceFlag: NewTraceFlagValues;
206
+ } {
207
+ if (parentSpanContext && trace.isSpanContextValid(parentSpanContext)) {
208
+ const { traceId, traceFlags } = parentSpanContext;
209
+ return { traceId, randomTraceFlag: getFlagAt(traceFlags, 2) as NewTraceFlagValues };
210
+ } else {
211
+ return {
212
+ traceId: idGenerator.generateTraceId(),
213
+ randomTraceFlag: NewTraceFlags.RANDOM_TRACE_ID_SET,
214
+ };
215
+ }
216
+ }