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,521 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { SpanProcessorWithFlush, TailSamplingSpanProcessor } from './spanprocessor';
3
+ import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';
4
+ import { SpanStatusCode } from '@opentelemetry/api';
5
+
6
+ describe('SpanProcessorWithFlush', () => {
7
+ let mockExporter: SpanExporter;
8
+ let processor: SpanProcessorWithFlush;
9
+
10
+ beforeEach(() => {
11
+ mockExporter = {
12
+ export: vi.fn((spans, callback) => {
13
+ callback({ code: 0 }); // SUCCESS
14
+ }),
15
+ shutdown: vi.fn(async () => {}),
16
+ };
17
+
18
+ processor = new SpanProcessorWithFlush(mockExporter);
19
+ });
20
+
21
+ describe('onEnd()', () => {
22
+ it('should buffer spans by trace ID', () => {
23
+ const span1 = createMockSpan('trace-1', 'span-1');
24
+ const span2 = createMockSpan('trace-1', 'span-2');
25
+ const span3 = createMockSpan('trace-2', 'span-3');
26
+
27
+ processor.onEnd(span1);
28
+ processor.onEnd(span2);
29
+ processor.onEnd(span3);
30
+
31
+ // Spans are buffered, not exported yet
32
+ expect(mockExporter.export).not.toHaveBeenCalled();
33
+ });
34
+ });
35
+
36
+ describe('forceFlush()', () => {
37
+ it('should flush specific trace by ID', async () => {
38
+ const span1 = createMockSpan('trace-1', 'span-1');
39
+ const span2 = createMockSpan('trace-1', 'span-2');
40
+ const span3 = createMockSpan('trace-2', 'span-3');
41
+
42
+ processor.onEnd(span1);
43
+ processor.onEnd(span2);
44
+ processor.onEnd(span3);
45
+
46
+ await processor.forceFlush('trace-1');
47
+
48
+ expect(mockExporter.export).toHaveBeenCalledTimes(1);
49
+ expect(mockExporter.export).toHaveBeenCalledWith(
50
+ expect.arrayContaining([span1, span2]),
51
+ expect.any(Function)
52
+ );
53
+ });
54
+
55
+ it('should flush all traces when no ID provided', async () => {
56
+ const span1 = createMockSpan('trace-1', 'span-1');
57
+ const span2 = createMockSpan('trace-2', 'span-2');
58
+
59
+ processor.onEnd(span1);
60
+ processor.onEnd(span2);
61
+
62
+ await processor.forceFlush();
63
+
64
+ expect(mockExporter.export).toHaveBeenCalledTimes(2);
65
+ });
66
+
67
+ it('should apply post-processor before export', async () => {
68
+ const postProcessor = vi.fn((spans) => {
69
+ // Add custom attribute to all spans
70
+ return spans.map((span) => ({
71
+ ...span,
72
+ attributes: { ...span.attributes, 'custom.tag': 'test' },
73
+ }));
74
+ });
75
+
76
+ processor = new SpanProcessorWithFlush(mockExporter, postProcessor);
77
+
78
+ const span1 = createMockSpan('trace-1', 'span-1');
79
+ processor.onEnd(span1);
80
+
81
+ await processor.forceFlush('trace-1');
82
+
83
+ expect(postProcessor).toHaveBeenCalledWith([span1]);
84
+ expect(mockExporter.export).toHaveBeenCalled();
85
+ });
86
+ });
87
+ });
88
+
89
+ describe('TailSamplingSpanProcessor', () => {
90
+ let mockExporter: SpanExporter;
91
+ let processor: TailSamplingSpanProcessor;
92
+ let exportedSpans: ReadableSpan[] = [];
93
+
94
+ beforeEach(() => {
95
+ exportedSpans = [];
96
+
97
+ mockExporter = {
98
+ export: vi.fn((spans, callback) => {
99
+ exportedSpans.push(...spans);
100
+ callback({ code: 0 }); // SUCCESS
101
+ }),
102
+ shutdown: vi.fn(async () => {}),
103
+ };
104
+ });
105
+
106
+ describe('Span buffering', () => {
107
+ it('should buffer all spans until root span ends', async () => {
108
+ // Custom tail sampler that always keeps traces
109
+ const tailSampler = vi.fn(() => true);
110
+
111
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, tailSampler);
112
+
113
+ // Create a trace: root -> child1 -> child2
114
+ const child2 = createMockSpan('trace-1', 'span-3', 'span-2');
115
+ const child1 = createMockSpan('trace-1', 'span-2', 'span-1');
116
+ const root = createMockSpan('trace-1', 'span-1');
117
+
118
+ // End spans in order: child2, child1, root
119
+ processor.onEnd(child2);
120
+ processor.onEnd(child1);
121
+
122
+ // No spans should be exported yet
123
+ expect(mockExporter.export).not.toHaveBeenCalled();
124
+
125
+ // End root span - should trigger export of all buffered spans
126
+ processor.onEnd(root);
127
+
128
+ // Wait for async flush
129
+ await new Promise(resolve => setTimeout(resolve, 0));
130
+
131
+ // Now all spans should be exported
132
+ expect(mockExporter.export).toHaveBeenCalledTimes(1);
133
+ expect(exportedSpans).toHaveLength(3);
134
+ expect(exportedSpans).toContain(child2);
135
+ expect(exportedSpans).toContain(child1);
136
+ expect(exportedSpans).toContain(root);
137
+ });
138
+
139
+ it('should only make tail sampling decision when root span ends', async () => {
140
+ const tailSampler = vi.fn(() => true);
141
+
142
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, tailSampler);
143
+
144
+ const child = createMockSpan('trace-1', 'span-2', 'span-1');
145
+ const root = createMockSpan('trace-1', 'span-1');
146
+
147
+ processor.onEnd(child);
148
+
149
+ // Tail sampler should NOT be called yet
150
+ expect(tailSampler).not.toHaveBeenCalled();
151
+
152
+ processor.onEnd(root);
153
+
154
+ // Wait for async flush
155
+ await new Promise(resolve => setTimeout(resolve, 0));
156
+
157
+ // Tail sampler should be called exactly once when root ends
158
+ expect(tailSampler).toHaveBeenCalledTimes(1);
159
+ });
160
+ });
161
+
162
+ describe('Tail sampling decisions', () => {
163
+ it('should export all buffered spans when tail sampler returns true', async () => {
164
+ const tailSampler = vi.fn(() => true);
165
+
166
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, tailSampler);
167
+
168
+ const child = createMockSpan('trace-1', 'span-2', 'span-1');
169
+ const root = createMockSpan('trace-1', 'span-1');
170
+
171
+ processor.onEnd(child);
172
+ processor.onEnd(root);
173
+
174
+ // Wait for async flush
175
+ await new Promise(resolve => setTimeout(resolve, 0));
176
+
177
+ expect(exportedSpans).toHaveLength(2);
178
+ expect(exportedSpans).toContain(child);
179
+ expect(exportedSpans).toContain(root);
180
+ });
181
+
182
+ it('should drop all buffered spans when tail sampler returns false', async () => {
183
+ const tailSampler = vi.fn(() => false);
184
+
185
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, tailSampler);
186
+
187
+ const child = createMockSpan('trace-1', 'span-2', 'span-1');
188
+ const root = createMockSpan('trace-1', 'span-1');
189
+
190
+ processor.onEnd(child);
191
+ processor.onEnd(root);
192
+
193
+ // No spans should be exported
194
+ expect(mockExporter.export).not.toHaveBeenCalled();
195
+ expect(exportedSpans).toHaveLength(0);
196
+ });
197
+
198
+ it('should keep trace when root span has error (default behavior)', async () => {
199
+ // Default tail sampler: keep if sampled or error
200
+ const defaultTailSampler = (traceInfo: any) => {
201
+ const localRootSpan = traceInfo.localRootSpan;
202
+ const ctx = localRootSpan.spanContext();
203
+ return (ctx.traceFlags & 1) === 1 || localRootSpan.status.code === 2; // SAMPLED | ERROR
204
+ };
205
+
206
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, defaultTailSampler);
207
+
208
+ const child = createMockSpan('trace-1', 'span-2', 'span-1');
209
+ const root = createMockSpan('trace-1', 'span-1');
210
+
211
+ // Set root span status to ERROR
212
+ root.status = { code: SpanStatusCode.ERROR };
213
+
214
+ processor.onEnd(child);
215
+ processor.onEnd(root);
216
+
217
+ // Wait for async flush
218
+ await new Promise(resolve => setTimeout(resolve, 0));
219
+
220
+ // Trace should be kept because root has error
221
+ expect(exportedSpans).toHaveLength(2);
222
+ });
223
+
224
+ it('should keep trace when error in child span affects root decision', async () => {
225
+ // Tail sampler that checks if any span in trace has error
226
+ const errorAwareSampler = (traceInfo: any) => {
227
+ return traceInfo.spans.some((span: any) => span.status.code === SpanStatusCode.ERROR);
228
+ };
229
+
230
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, errorAwareSampler);
231
+
232
+ const child = createMockSpan('trace-1', 'span-2', 'span-1');
233
+ child.status = { code: SpanStatusCode.ERROR }; // Child has error
234
+
235
+ const root = createMockSpan('trace-1', 'span-1');
236
+ root.status = { code: SpanStatusCode.OK }; // Root is OK
237
+
238
+ processor.onEnd(child);
239
+ processor.onEnd(root);
240
+
241
+ // Wait for async flush
242
+ await new Promise(resolve => setTimeout(resolve, 0));
243
+
244
+ // Trace should be kept because child has error
245
+ expect(exportedSpans).toHaveLength(2);
246
+ });
247
+ });
248
+
249
+ describe('Trace cleanup', () => {
250
+ it('should clean up trace after decision', async () => {
251
+ const tailSampler = vi.fn(() => true);
252
+
253
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, tailSampler);
254
+
255
+ const root1 = createMockSpan('trace-1', 'span-1');
256
+ const root2 = createMockSpan('trace-2', 'span-2');
257
+
258
+ processor.onEnd(root1);
259
+ processor.onEnd(root2);
260
+
261
+ // Both traces should have been auto-flushed when root spans ended
262
+ // Wait a tick for async flush to complete
263
+ await new Promise(resolve => setTimeout(resolve, 0));
264
+
265
+ // Both root spans should have been exported
266
+ expect(exportedSpans).toHaveLength(2);
267
+ expect(exportedSpans).toContain(root1);
268
+ expect(exportedSpans).toContain(root2);
269
+ });
270
+ });
271
+
272
+ describe('Without tail sampler', () => {
273
+ it('should export all spans when no tail sampler provided', async () => {
274
+ processor = new TailSamplingSpanProcessor(mockExporter);
275
+
276
+ const child = createMockSpan('trace-1', 'span-2', 'span-1');
277
+ const root = createMockSpan('trace-1', 'span-1');
278
+
279
+ processor.onEnd(child);
280
+ processor.onEnd(root);
281
+
282
+ // Wait for async flush
283
+ await new Promise(resolve => setTimeout(resolve, 0));
284
+
285
+ // All spans should be exported
286
+ expect(exportedSpans).toHaveLength(2);
287
+ });
288
+ });
289
+
290
+ describe('Complex trace scenarios', () => {
291
+ it('should handle multiple traces in parallel', async () => {
292
+ const tailSampler = vi.fn((traceInfo) => {
293
+ // Keep trace-1, drop trace-2
294
+ return traceInfo.traceId === 'trace-1';
295
+ });
296
+
297
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, tailSampler);
298
+
299
+ // Trace 1
300
+ const trace1_child = createMockSpan('trace-1', 'span-2', 'span-1');
301
+ const trace1_root = createMockSpan('trace-1', 'span-1');
302
+
303
+ // Trace 2
304
+ const trace2_child = createMockSpan('trace-2', 'span-4', 'span-3');
305
+ const trace2_root = createMockSpan('trace-2', 'span-3');
306
+
307
+ // Interleave span endings
308
+ processor.onEnd(trace1_child);
309
+ processor.onEnd(trace2_child);
310
+ processor.onEnd(trace1_root);
311
+ processor.onEnd(trace2_root);
312
+
313
+ // Wait for async flush
314
+ await new Promise(resolve => setTimeout(resolve, 0));
315
+
316
+ // Only trace-1 spans should be exported
317
+ expect(exportedSpans).toHaveLength(2);
318
+ expect(exportedSpans).toContain(trace1_child);
319
+ expect(exportedSpans).toContain(trace1_root);
320
+ expect(exportedSpans).not.toContain(trace2_child);
321
+ expect(exportedSpans).not.toContain(trace2_root);
322
+ });
323
+
324
+ it('should handle deeply nested spans', async () => {
325
+ const tailSampler = vi.fn(() => true);
326
+
327
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, tailSampler);
328
+
329
+ // Create a deep trace: root -> child1 -> child2 -> child3
330
+ const child3 = createMockSpan('trace-1', 'span-4', 'span-3');
331
+ const child2 = createMockSpan('trace-1', 'span-3', 'span-2');
332
+ const child1 = createMockSpan('trace-1', 'span-2', 'span-1');
333
+ const root = createMockSpan('trace-1', 'span-1');
334
+
335
+ processor.onEnd(child3);
336
+ processor.onEnd(child2);
337
+ processor.onEnd(child1);
338
+ processor.onEnd(root);
339
+
340
+ // Wait for async flush
341
+ await new Promise(resolve => setTimeout(resolve, 0));
342
+
343
+ // All spans should be exported
344
+ expect(exportedSpans).toHaveLength(4);
345
+ });
346
+
347
+ it('should handle distributed traces (local root with remote parent)', async () => {
348
+ const tailSampler = vi.fn(() => true);
349
+
350
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, tailSampler);
351
+
352
+ // Create a distributed trace:
353
+ // - remote-root (not in this trace, represented by parentSpanId 'remote-1')
354
+ // -> local-root (has parentSpanId but is first span we see)
355
+ // -> child1
356
+ // -> child2
357
+
358
+ const child2 = createMockSpan('trace-1', 'span-3', 'span-2');
359
+ const child1 = createMockSpan('trace-1', 'span-2', 'span-1');
360
+ // Local root has a parentSpanId (from remote) but is our local root
361
+ const localRoot = createMockSpan('trace-1', 'span-1', 'remote-1');
362
+
363
+ // End spans in order
364
+ processor.onEnd(child2);
365
+ processor.onEnd(child1);
366
+ processor.onEnd(localRoot);
367
+
368
+ // Distributed traces don't auto-flush (local root has parentSpanId)
369
+ // Explicitly flush to trigger tail sampling decision (simulates instrument.ts behavior)
370
+ await processor.forceFlush('trace-1');
371
+
372
+ // Wait for async flush
373
+ await new Promise(resolve => setTimeout(resolve, 0));
374
+
375
+ // ALL spans should be exported (including distributed trace entry point)
376
+ expect(exportedSpans).toHaveLength(3);
377
+ expect(exportedSpans).toContain(child2);
378
+ expect(exportedSpans).toContain(child1);
379
+ expect(exportedSpans).toContain(localRoot);
380
+ expect(tailSampler).toHaveBeenCalledTimes(1);
381
+ });
382
+
383
+ it('should NOT leak traces when distributed trace root ends', async () => {
384
+ const tailSampler = vi.fn(() => false); // Drop all traces
385
+
386
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, tailSampler);
387
+
388
+ // Distributed trace
389
+ const child = createMockSpan('trace-1', 'span-2', 'span-1');
390
+ const localRoot = createMockSpan('trace-1', 'span-1', 'remote-1');
391
+
392
+ processor.onEnd(child);
393
+ processor.onEnd(localRoot);
394
+
395
+ // Explicitly flush (simulates instrument.ts behavior)
396
+ await processor.forceFlush('trace-1');
397
+
398
+ // No spans should be exported (tail sampler returned false)
399
+ expect(exportedSpans).toHaveLength(0);
400
+ expect(tailSampler).toHaveBeenCalledTimes(1);
401
+
402
+ // Trace should be cleaned up (not leaked)
403
+ // Start a new trace with same traceId to verify cleanup
404
+ const anotherSpan = createMockSpan('trace-1', 'span-3');
405
+ processor.onEnd(anotherSpan);
406
+
407
+ // Explicitly flush the new trace
408
+ await processor.forceFlush('trace-1');
409
+ await new Promise(resolve => setTimeout(resolve, 0));
410
+
411
+ // Tail sampler should be called again for the new trace
412
+ expect(tailSampler).toHaveBeenCalledTimes(2);
413
+ });
414
+
415
+ it('should handle distributed trace where ALL spans have remote parent', async () => {
416
+ // This is the critical test: when all spans have parentSpanId,
417
+ // localRootSpan must be correctly identified as the handler (span with remote parent)
418
+ // not as a child that happened to end first
419
+ const tailSampler = vi.fn((traceInfo) => {
420
+ // Tail sampler accesses localRootSpan.spanContext()
421
+ // This would crash if localRootSpan is undefined
422
+ expect(traceInfo.localRootSpan).toBeDefined();
423
+ expect(traceInfo.localRootSpan.spanContext()).toBeDefined();
424
+ return true;
425
+ });
426
+
427
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, tailSampler);
428
+
429
+ // All spans have parentSpanId (span1 has remote parent, others have local parent)
430
+ // Spans end in this order: child2 -> child1 -> handler
431
+ const span3 = createMockSpan('trace-1', 'span-3', 'span-2'); // grandchild
432
+ const span2 = createMockSpan('trace-1', 'span-2', 'span-1'); // child
433
+ const span1 = createMockSpan('trace-1', 'span-1', 'remote-parent-id'); // handler (local root)
434
+
435
+ processor.onEnd(span3); // Ends first, but has local parent (span-1)
436
+ processor.onEnd(span2); // Ends second, but has local parent (span-1)
437
+ processor.onEnd(span1); // Ends last, has remote parent → this is local root
438
+
439
+ // Explicitly flush (simulates instrument.ts behavior)
440
+ await processor.forceFlush('trace-1');
441
+ await new Promise(resolve => setTimeout(resolve, 0));
442
+
443
+ // Tail sampler should have been called without crashing
444
+ expect(tailSampler).toHaveBeenCalledTimes(1);
445
+
446
+ // localRootSpan should be span-1 (handler with remote parent), NOT span-3 (first to end)
447
+ const traceInfo = tailSampler.mock.calls[0][0];
448
+ expect(traceInfo.localRootSpan.spanContext().spanId).toBe('span-1');
449
+
450
+ // All spans should be exported
451
+ expect(exportedSpans).toHaveLength(3);
452
+ });
453
+
454
+ it('should check handler error status, not child status, in distributed trace', async () => {
455
+ // Critical scenario: handler has error, child is OK
456
+ // Tail sampler must check localRootSpan (handler), not first-to-end child
457
+ const defaultTailSampler = (traceInfo: any) => {
458
+ const localRootSpan = traceInfo.localRootSpan;
459
+ const ctx = localRootSpan.spanContext();
460
+ // Default: keep if sampled or error
461
+ return (ctx.traceFlags & 1) === 1 || localRootSpan.status.code === 2; // SAMPLED | ERROR
462
+ };
463
+
464
+ processor = new TailSamplingSpanProcessor(mockExporter, undefined, defaultTailSampler);
465
+
466
+ // Child ends first with OK status
467
+ const child = createMockSpan('trace-1', 'span-2', 'span-1');
468
+ child.status = { code: SpanStatusCode.OK }; // Child is fine
469
+
470
+ // Handler ends second with ERROR status and remote parent
471
+ const handler = createMockSpan('trace-1', 'span-1', 'remote-parent-id');
472
+ handler.status = { code: SpanStatusCode.ERROR }; // Handler has error
473
+
474
+ processor.onEnd(child); // Child ends first (OK status)
475
+ processor.onEnd(handler); // Handler ends second (ERROR status, remote parent)
476
+
477
+ // Explicitly flush
478
+ await processor.forceFlush('trace-1');
479
+ await new Promise(resolve => setTimeout(resolve, 0));
480
+
481
+ // Trace should be KEPT because handler (localRootSpan) has ERROR
482
+ // If we incorrectly used child as localRootSpan, trace would be dropped (OK status)
483
+ expect(exportedSpans).toHaveLength(2);
484
+ expect(exportedSpans).toContain(child);
485
+ expect(exportedSpans).toContain(handler);
486
+ });
487
+ });
488
+ });
489
+
490
+ /**
491
+ * Helper to create mock ReadableSpan
492
+ */
493
+ function createMockSpan(
494
+ traceId: string,
495
+ spanId: string,
496
+ parentSpanId?: string
497
+ ): ReadableSpan {
498
+ return {
499
+ name: `span-${spanId}`,
500
+ spanContext: () => ({
501
+ traceId,
502
+ spanId,
503
+ traceFlags: 1, // SAMPLED
504
+ traceState: undefined,
505
+ }),
506
+ parentSpanId,
507
+ startTime: [Date.now(), 0],
508
+ endTime: [Date.now(), 0],
509
+ status: { code: SpanStatusCode.OK },
510
+ attributes: {},
511
+ links: [],
512
+ events: [],
513
+ duration: [0, 0],
514
+ ended: true,
515
+ resource: {} as any,
516
+ instrumentationLibrary: { name: 'test', version: '1.0.0' },
517
+ droppedAttributesCount: 0,
518
+ droppedEventsCount: 0,
519
+ droppedLinksCount: 0,
520
+ } as ReadableSpan;
521
+ }