0
# Span Exporters
1
2
Span exporters handle the final output of span data, sending it to various destinations such as console output, in-memory storage, or external tracing systems. OpenTelemetry SDK Trace Base provides built-in exporters and interfaces for creating custom exporters.
3
4
## Capabilities
5
6
### Core SpanExporter Interface
7
8
All span exporters implement the base SpanExporter interface for consistent export behavior.
9
10
```typescript { .api }
11
/**
12
* Interface for span export implementations
13
*/
14
interface SpanExporter {
15
/**
16
* Export a batch of spans
17
* @param spans - Array of ReadableSpan objects to export
18
* @param resultCallback - Callback to invoke when export completes
19
*/
20
export(
21
spans: ReadableSpan[],
22
resultCallback: (result: ExportResult) => void
23
): void;
24
25
/**
26
* Shutdown the exporter and release resources
27
* @returns Promise that resolves when shutdown is complete
28
*/
29
shutdown(): Promise<void>;
30
31
/**
32
* Force flush any pending export operations (optional)
33
* @returns Promise that resolves when flush is complete
34
*/
35
forceFlush?(): Promise<void>;
36
}
37
38
/**
39
* Result of an export operation
40
*/
41
interface ExportResult {
42
code: ExportResultCode;
43
error?: Error;
44
}
45
46
/**
47
* Export result codes indicating success or failure
48
*/
49
enum ExportResultCode {
50
SUCCESS = 0,
51
FAILED = 1
52
}
53
```
54
55
### ReadableSpan Interface
56
57
Spans provided to exporters implement the ReadableSpan interface with complete span data.
58
59
```typescript { .api }
60
/**
61
* Interface representing a completed span for export
62
*/
63
interface ReadableSpan {
64
/** Name of the span */
65
readonly name: string;
66
67
/** Kind of the span (CLIENT, SERVER, etc.) */
68
readonly kind: SpanKind;
69
70
/** Function that returns the span context */
71
readonly spanContext: () => SpanContext;
72
73
/** Parent span context if this span has a parent */
74
readonly parentSpanContext?: SpanContext;
75
76
/** High-resolution start time */
77
readonly startTime: HrTime;
78
79
/** High-resolution end time */
80
readonly endTime: HrTime;
81
82
/** Status of the span (OK, ERROR, UNSET) */
83
readonly status: SpanStatus;
84
85
/** Attributes attached to the span */
86
readonly attributes: Attributes;
87
88
/** Links to other spans */
89
readonly links: Link[];
90
91
/** Events that occurred during the span */
92
readonly events: TimedEvent[];
93
94
/** Calculated duration of the span */
95
readonly duration: HrTime;
96
97
/** Whether the span has ended */
98
readonly ended: boolean;
99
100
/** Resource associated with the span */
101
readonly resource: Resource;
102
103
/** Instrumentation scope that created the span */
104
readonly instrumentationScope: InstrumentationScope;
105
106
/** Number of attributes dropped due to limits */
107
readonly droppedAttributesCount: number;
108
109
/** Number of events dropped due to limits */
110
readonly droppedEventsCount: number;
111
112
/** Number of links dropped due to limits */
113
readonly droppedLinksCount: number;
114
}
115
116
/**
117
* Timed event within a span
118
*/
119
interface TimedEvent {
120
/** High-resolution time when event occurred */
121
time: HrTime;
122
123
/** Name of the event */
124
name: string;
125
126
/** Attributes associated with the event */
127
attributes?: Attributes;
128
129
/** Number of attributes dropped from the event */
130
droppedAttributesCount?: number;
131
}
132
```
133
134
### ConsoleSpanExporter
135
136
Exporter that outputs span information to the console, useful for development and debugging.
137
138
```typescript { .api }
139
/**
140
* Span exporter that prints spans to console for diagnostic purposes
141
*/
142
class ConsoleSpanExporter implements SpanExporter {
143
/**
144
* Export spans by printing them to console
145
* @param spans - Array of spans to print
146
* @param resultCallback - Called with SUCCESS after printing
147
*/
148
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
149
150
/**
151
* Shutdown the exporter (no-op for console exporter)
152
* @returns Promise that resolves immediately
153
*/
154
shutdown(): Promise<void>;
155
156
/**
157
* Force flush (no-op for console exporter)
158
* @returns Promise that resolves immediately
159
*/
160
forceFlush(): Promise<void>;
161
}
162
```
163
164
**Output Format:**
165
The console exporter outputs structured span information including:
166
- Span name, trace ID, span ID, and parent span ID
167
- Start time, end time, and duration
168
- Span kind and status
169
- Attributes, events, and links
170
- Resource and instrumentation scope information
171
172
**Usage Examples:**
173
174
```typescript
175
import { BasicTracerProvider, BatchSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base';
176
177
// Basic console output setup
178
const provider = new BasicTracerProvider({
179
spanProcessors: [
180
new BatchSpanProcessor(new ConsoleSpanExporter())
181
]
182
});
183
184
// Development setup with immediate console output
185
const devProvider = new BasicTracerProvider({
186
spanProcessors: [
187
new SimpleSpanProcessor(new ConsoleSpanExporter())
188
]
189
});
190
191
// Conditional console output
192
const provider = new BasicTracerProvider({
193
spanProcessors: [
194
...(process.env.DEBUG === 'true' ? [
195
new SimpleSpanProcessor(new ConsoleSpanExporter())
196
] : []),
197
new BatchSpanProcessor(new ConsoleSpanExporter()) // Replace with external exporter as needed
198
]
199
});
200
```
201
202
### InMemorySpanExporter
203
204
Exporter that stores spans in memory, useful for testing, debugging, and collecting metrics.
205
206
```typescript { .api }
207
/**
208
* Span exporter that stores spans in memory for testing purposes
209
*/
210
class InMemorySpanExporter implements SpanExporter {
211
/**
212
* Export spans by storing them in memory
213
* @param spans - Array of spans to store
214
* @param resultCallback - Called with SUCCESS after storing
215
*/
216
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
217
218
/**
219
* Shutdown the exporter and mark as stopped
220
* @returns Promise that resolves immediately
221
*/
222
shutdown(): Promise<void>;
223
224
/**
225
* Force flush (no-op for in-memory exporter)
226
* @returns Promise that resolves immediately
227
*/
228
forceFlush(): Promise<void>;
229
230
/**
231
* Clear all stored spans
232
*/
233
reset(): void;
234
235
/**
236
* Retrieve all stored spans
237
* @returns Array of all spans that have been exported
238
*/
239
getFinishedSpans(): ReadableSpan[];
240
241
/** Whether the exporter has been stopped */
242
protected _stopped: boolean;
243
}
244
```
245
246
**Usage Examples:**
247
248
```typescript
249
import { BasicTracerProvider, BatchSpanProcessor, InMemorySpanExporter } from '@opentelemetry/sdk-trace-base';
250
251
// Basic in-memory storage
252
const exporter = new InMemorySpanExporter();
253
const provider = new BasicTracerProvider({
254
spanProcessors: [
255
new BatchSpanProcessor(exporter)
256
]
257
});
258
259
// Create some spans
260
const tracer = provider.getTracer('test-service');
261
const span1 = tracer.startSpan('operation1');
262
span1.end();
263
const span2 = tracer.startSpan('operation2');
264
span2.end();
265
266
// Force flush to ensure spans are exported
267
await provider.forceFlush();
268
269
// Retrieve stored spans for analysis
270
const spans = exporter.getFinishedSpans();
271
console.log(`Captured ${spans.length} spans`);
272
spans.forEach(span => {
273
console.log(`Span: ${span.name}, Duration: ${span.duration}`);
274
});
275
276
// Clear stored spans
277
exporter.reset();
278
console.log(`Spans after reset: ${exporter.getFinishedSpans().length}`);
279
```
280
281
### Testing with InMemorySpanExporter
282
283
The in-memory exporter is particularly useful for testing tracing behavior.
284
285
**Usage Examples:**
286
287
```typescript
288
import { BasicTracerProvider, BatchSpanProcessor, InMemorySpanExporter } from '@opentelemetry/sdk-trace-base';
289
290
describe('Tracing Tests', () => {
291
let provider: BasicTracerProvider;
292
let exporter: InMemorySpanExporter;
293
let tracer: Tracer;
294
295
beforeEach(() => {
296
exporter = new InMemorySpanExporter();
297
provider = new BasicTracerProvider({
298
spanProcessors: [new BatchSpanProcessor(exporter)]
299
});
300
tracer = provider.getTracer('test-tracer');
301
});
302
303
afterEach(async () => {
304
await provider.shutdown();
305
});
306
307
it('should capture span attributes', async () => {
308
const span = tracer.startSpan('test-operation');
309
span.setAttribute('user.id', '12345');
310
span.setAttribute('operation.type', 'read');
311
span.end();
312
313
await provider.forceFlush();
314
315
const spans = exporter.getFinishedSpans();
316
expect(spans).toHaveLength(1);
317
expect(spans[0].attributes['user.id']).toBe('12345');
318
expect(spans[0].attributes['operation.type']).toBe('read');
319
});
320
321
it('should capture span events', async () => {
322
const span = tracer.startSpan('test-operation');
323
span.addEvent('processing-started');
324
span.addEvent('data-loaded', { recordCount: 100 });
325
span.end();
326
327
await provider.forceFlush();
328
329
const spans = exporter.getFinishedSpans();
330
expect(spans[0].events).toHaveLength(2);
331
expect(spans[0].events[0].name).toBe('processing-started');
332
expect(spans[0].events[1].name).toBe('data-loaded');
333
expect(spans[0].events[1].attributes?.recordCount).toBe(100);
334
});
335
336
it('should track span hierarchy', async () => {
337
const parentSpan = tracer.startSpan('parent-operation');
338
const childSpan = tracer.startSpan('child-operation', {
339
parent: parentSpan.spanContext()
340
});
341
342
childSpan.end();
343
parentSpan.end();
344
345
await provider.forceFlush();
346
347
const spans = exporter.getFinishedSpans();
348
expect(spans).toHaveLength(2);
349
350
const child = spans.find(s => s.name === 'child-operation');
351
const parent = spans.find(s => s.name === 'parent-operation');
352
353
expect(child?.parentSpanContext?.spanId).toBe(parent?.spanContext().spanId);
354
});
355
});
356
```
357
358
### Custom Exporter Implementation
359
360
You can create custom exporters by implementing the SpanExporter interface.
361
362
**Usage Examples:**
363
364
```typescript
365
import { SpanExporter, ReadableSpan, ExportResult, ExportResultCode } from '@opentelemetry/sdk-trace-base';
366
367
// Custom exporter that sends spans to a webhook
368
class WebhookSpanExporter implements SpanExporter {
369
constructor(private webhookUrl: string, private apiKey: string) {}
370
371
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
372
const payload = {
373
spans: spans.map(span => ({
374
name: span.name,
375
traceId: span.spanContext().traceId,
376
spanId: span.spanContext().spanId,
377
parentSpanId: span.parentSpanContext?.spanId,
378
startTime: span.startTime,
379
endTime: span.endTime,
380
attributes: span.attributes,
381
status: span.status
382
}))
383
};
384
385
fetch(this.webhookUrl, {
386
method: 'POST',
387
headers: {
388
'Content-Type': 'application/json',
389
'Authorization': `Bearer ${this.apiKey}`
390
},
391
body: JSON.stringify(payload)
392
})
393
.then(response => {
394
if (response.ok) {
395
resultCallback({ code: ExportResultCode.SUCCESS });
396
} else {
397
resultCallback({
398
code: ExportResultCode.FAILED,
399
error: new Error(`HTTP ${response.status}`)
400
});
401
}
402
})
403
.catch(error => {
404
resultCallback({ code: ExportResultCode.FAILED, error });
405
});
406
}
407
408
async shutdown(): Promise<void> {
409
// Cleanup resources if needed
410
}
411
412
async forceFlush(): Promise<void> {
413
// Force any pending operations if needed
414
}
415
}
416
417
// Custom exporter that filters spans
418
class FilteredSpanExporter implements SpanExporter {
419
constructor(
420
private baseExporter: SpanExporter,
421
private filter: (span: ReadableSpan) => boolean
422
) {}
423
424
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
425
const filteredSpans = spans.filter(this.filter);
426
427
if (filteredSpans.length === 0) {
428
resultCallback({ code: ExportResultCode.SUCCESS });
429
return;
430
}
431
432
this.baseExporter.export(filteredSpans, resultCallback);
433
}
434
435
async shutdown(): Promise<void> {
436
return this.baseExporter.shutdown();
437
}
438
439
async forceFlush(): Promise<void> {
440
return this.baseExporter.forceFlush?.();
441
}
442
}
443
444
// Use custom exporters
445
const provider = new BasicTracerProvider({
446
spanProcessors: [
447
new BatchSpanProcessor(
448
new WebhookSpanExporter('https://my-api.com/traces', 'api-key-123')
449
),
450
new BatchSpanProcessor(
451
new FilteredSpanExporter(
452
new ConsoleSpanExporter(),
453
span => span.status.code === 2 // ERROR status code
454
)
455
)
456
]
457
});
458
```
459
460
### Error Handling
461
462
Exporters should handle errors gracefully and report them through the result callback.
463
464
**Usage Examples:**
465
466
```typescript
467
class RobustSpanExporter implements SpanExporter {
468
constructor(private backendUrl: string, private retryAttempts = 3) {}
469
470
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
471
this.exportWithRetry(spans, this.retryAttempts, resultCallback);
472
}
473
474
private async exportWithRetry(
475
spans: ReadableSpan[],
476
attemptsLeft: number,
477
resultCallback: (result: ExportResult) => void
478
): Promise<void> {
479
try {
480
const response = await fetch(this.backendUrl, {
481
method: 'POST',
482
headers: { 'Content-Type': 'application/json' },
483
body: JSON.stringify({ spans })
484
});
485
486
if (response.ok) {
487
resultCallback({ code: ExportResultCode.SUCCESS });
488
} else if (response.status >= 500 && attemptsLeft > 1) {
489
// Retry on server errors
490
setTimeout(() => {
491
this.exportWithRetry(spans, attemptsLeft - 1, resultCallback);
492
}, 1000);
493
} else {
494
resultCallback({
495
code: ExportResultCode.FAILED,
496
error: new Error(`Export failed: ${response.status}`)
497
});
498
}
499
} catch (error) {
500
if (attemptsLeft > 1) {
501
setTimeout(() => {
502
this.exportWithRetry(spans, attemptsLeft - 1, resultCallback);
503
}, 1000);
504
} else {
505
resultCallback({
506
code: ExportResultCode.FAILED,
507
error: error as Error
508
});
509
}
510
}
511
}
512
513
async shutdown(): Promise<void> {
514
// Cleanup resources
515
}
516
}
517
```