Foundational tracing SDK components for OpenTelemetry JavaScript providing manual instrumentation capabilities
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
All span exporters implement the base SpanExporter interface for consistent export behavior.
/**
* Interface for span export implementations
*/
interface SpanExporter {
/**
* Export a batch of spans
* @param spans - Array of ReadableSpan objects to export
* @param resultCallback - Callback to invoke when export completes
*/
export(
spans: ReadableSpan[],
resultCallback: (result: ExportResult) => void
): void;
/**
* Shutdown the exporter and release resources
* @returns Promise that resolves when shutdown is complete
*/
shutdown(): Promise<void>;
/**
* Force flush any pending export operations (optional)
* @returns Promise that resolves when flush is complete
*/
forceFlush?(): Promise<void>;
}
/**
* Result of an export operation
*/
interface ExportResult {
code: ExportResultCode;
error?: Error;
}
/**
* Export result codes indicating success or failure
*/
enum ExportResultCode {
SUCCESS = 0,
FAILED = 1
}Spans provided to exporters implement the ReadableSpan interface with complete span data.
/**
* Interface representing a completed span for export
*/
interface ReadableSpan {
/** Name of the span */
readonly name: string;
/** Kind of the span (CLIENT, SERVER, etc.) */
readonly kind: SpanKind;
/** Function that returns the span context */
readonly spanContext: () => SpanContext;
/** Parent span context if this span has a parent */
readonly parentSpanContext?: SpanContext;
/** High-resolution start time */
readonly startTime: HrTime;
/** High-resolution end time */
readonly endTime: HrTime;
/** Status of the span (OK, ERROR, UNSET) */
readonly status: SpanStatus;
/** Attributes attached to the span */
readonly attributes: Attributes;
/** Links to other spans */
readonly links: Link[];
/** Events that occurred during the span */
readonly events: TimedEvent[];
/** Calculated duration of the span */
readonly duration: HrTime;
/** Whether the span has ended */
readonly ended: boolean;
/** Resource associated with the span */
readonly resource: Resource;
/** Instrumentation scope that created the span */
readonly instrumentationScope: InstrumentationScope;
/** Number of attributes dropped due to limits */
readonly droppedAttributesCount: number;
/** Number of events dropped due to limits */
readonly droppedEventsCount: number;
/** Number of links dropped due to limits */
readonly droppedLinksCount: number;
}
/**
* Timed event within a span
*/
interface TimedEvent {
/** High-resolution time when event occurred */
time: HrTime;
/** Name of the event */
name: string;
/** Attributes associated with the event */
attributes?: Attributes;
/** Number of attributes dropped from the event */
droppedAttributesCount?: number;
}Exporter that outputs span information to the console, useful for development and debugging.
/**
* Span exporter that prints spans to console for diagnostic purposes
*/
class ConsoleSpanExporter implements SpanExporter {
/**
* Export spans by printing them to console
* @param spans - Array of spans to print
* @param resultCallback - Called with SUCCESS after printing
*/
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
/**
* Shutdown the exporter (no-op for console exporter)
* @returns Promise that resolves immediately
*/
shutdown(): Promise<void>;
/**
* Force flush (no-op for console exporter)
* @returns Promise that resolves immediately
*/
forceFlush(): Promise<void>;
}Output Format: The console exporter outputs structured span information including:
Usage Examples:
import { BasicTracerProvider, BatchSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base';
// Basic console output setup
const provider = new BasicTracerProvider({
spanProcessors: [
new BatchSpanProcessor(new ConsoleSpanExporter())
]
});
// Development setup with immediate console output
const devProvider = new BasicTracerProvider({
spanProcessors: [
new SimpleSpanProcessor(new ConsoleSpanExporter())
]
});
// Conditional console output
const provider = new BasicTracerProvider({
spanProcessors: [
...(process.env.DEBUG === 'true' ? [
new SimpleSpanProcessor(new ConsoleSpanExporter())
] : []),
new BatchSpanProcessor(new ConsoleSpanExporter()) // Replace with external exporter as needed
]
});Exporter that stores spans in memory, useful for testing, debugging, and collecting metrics.
/**
* Span exporter that stores spans in memory for testing purposes
*/
class InMemorySpanExporter implements SpanExporter {
/**
* Export spans by storing them in memory
* @param spans - Array of spans to store
* @param resultCallback - Called with SUCCESS after storing
*/
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
/**
* Shutdown the exporter and mark as stopped
* @returns Promise that resolves immediately
*/
shutdown(): Promise<void>;
/**
* Force flush (no-op for in-memory exporter)
* @returns Promise that resolves immediately
*/
forceFlush(): Promise<void>;
/**
* Clear all stored spans
*/
reset(): void;
/**
* Retrieve all stored spans
* @returns Array of all spans that have been exported
*/
getFinishedSpans(): ReadableSpan[];
/** Whether the exporter has been stopped */
protected _stopped: boolean;
}Usage Examples:
import { BasicTracerProvider, BatchSpanProcessor, InMemorySpanExporter } from '@opentelemetry/sdk-trace-base';
// Basic in-memory storage
const exporter = new InMemorySpanExporter();
const provider = new BasicTracerProvider({
spanProcessors: [
new BatchSpanProcessor(exporter)
]
});
// Create some spans
const tracer = provider.getTracer('test-service');
const span1 = tracer.startSpan('operation1');
span1.end();
const span2 = tracer.startSpan('operation2');
span2.end();
// Force flush to ensure spans are exported
await provider.forceFlush();
// Retrieve stored spans for analysis
const spans = exporter.getFinishedSpans();
console.log(`Captured ${spans.length} spans`);
spans.forEach(span => {
console.log(`Span: ${span.name}, Duration: ${span.duration}`);
});
// Clear stored spans
exporter.reset();
console.log(`Spans after reset: ${exporter.getFinishedSpans().length}`);The in-memory exporter is particularly useful for testing tracing behavior.
Usage Examples:
import { BasicTracerProvider, BatchSpanProcessor, InMemorySpanExporter } from '@opentelemetry/sdk-trace-base';
describe('Tracing Tests', () => {
let provider: BasicTracerProvider;
let exporter: InMemorySpanExporter;
let tracer: Tracer;
beforeEach(() => {
exporter = new InMemorySpanExporter();
provider = new BasicTracerProvider({
spanProcessors: [new BatchSpanProcessor(exporter)]
});
tracer = provider.getTracer('test-tracer');
});
afterEach(async () => {
await provider.shutdown();
});
it('should capture span attributes', async () => {
const span = tracer.startSpan('test-operation');
span.setAttribute('user.id', '12345');
span.setAttribute('operation.type', 'read');
span.end();
await provider.forceFlush();
const spans = exporter.getFinishedSpans();
expect(spans).toHaveLength(1);
expect(spans[0].attributes['user.id']).toBe('12345');
expect(spans[0].attributes['operation.type']).toBe('read');
});
it('should capture span events', async () => {
const span = tracer.startSpan('test-operation');
span.addEvent('processing-started');
span.addEvent('data-loaded', { recordCount: 100 });
span.end();
await provider.forceFlush();
const spans = exporter.getFinishedSpans();
expect(spans[0].events).toHaveLength(2);
expect(spans[0].events[0].name).toBe('processing-started');
expect(spans[0].events[1].name).toBe('data-loaded');
expect(spans[0].events[1].attributes?.recordCount).toBe(100);
});
it('should track span hierarchy', async () => {
const parentSpan = tracer.startSpan('parent-operation');
const childSpan = tracer.startSpan('child-operation', {
parent: parentSpan.spanContext()
});
childSpan.end();
parentSpan.end();
await provider.forceFlush();
const spans = exporter.getFinishedSpans();
expect(spans).toHaveLength(2);
const child = spans.find(s => s.name === 'child-operation');
const parent = spans.find(s => s.name === 'parent-operation');
expect(child?.parentSpanContext?.spanId).toBe(parent?.spanContext().spanId);
});
});You can create custom exporters by implementing the SpanExporter interface.
Usage Examples:
import { SpanExporter, ReadableSpan, ExportResult, ExportResultCode } from '@opentelemetry/sdk-trace-base';
// Custom exporter that sends spans to a webhook
class WebhookSpanExporter implements SpanExporter {
constructor(private webhookUrl: string, private apiKey: string) {}
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
const payload = {
spans: spans.map(span => ({
name: span.name,
traceId: span.spanContext().traceId,
spanId: span.spanContext().spanId,
parentSpanId: span.parentSpanContext?.spanId,
startTime: span.startTime,
endTime: span.endTime,
attributes: span.attributes,
status: span.status
}))
};
fetch(this.webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: JSON.stringify(payload)
})
.then(response => {
if (response.ok) {
resultCallback({ code: ExportResultCode.SUCCESS });
} else {
resultCallback({
code: ExportResultCode.FAILED,
error: new Error(`HTTP ${response.status}`)
});
}
})
.catch(error => {
resultCallback({ code: ExportResultCode.FAILED, error });
});
}
async shutdown(): Promise<void> {
// Cleanup resources if needed
}
async forceFlush(): Promise<void> {
// Force any pending operations if needed
}
}
// Custom exporter that filters spans
class FilteredSpanExporter implements SpanExporter {
constructor(
private baseExporter: SpanExporter,
private filter: (span: ReadableSpan) => boolean
) {}
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
const filteredSpans = spans.filter(this.filter);
if (filteredSpans.length === 0) {
resultCallback({ code: ExportResultCode.SUCCESS });
return;
}
this.baseExporter.export(filteredSpans, resultCallback);
}
async shutdown(): Promise<void> {
return this.baseExporter.shutdown();
}
async forceFlush(): Promise<void> {
return this.baseExporter.forceFlush?.();
}
}
// Use custom exporters
const provider = new BasicTracerProvider({
spanProcessors: [
new BatchSpanProcessor(
new WebhookSpanExporter('https://my-api.com/traces', 'api-key-123')
),
new BatchSpanProcessor(
new FilteredSpanExporter(
new ConsoleSpanExporter(),
span => span.status.code === 2 // ERROR status code
)
)
]
});Exporters should handle errors gracefully and report them through the result callback.
Usage Examples:
class RobustSpanExporter implements SpanExporter {
constructor(private backendUrl: string, private retryAttempts = 3) {}
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
this.exportWithRetry(spans, this.retryAttempts, resultCallback);
}
private async exportWithRetry(
spans: ReadableSpan[],
attemptsLeft: number,
resultCallback: (result: ExportResult) => void
): Promise<void> {
try {
const response = await fetch(this.backendUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ spans })
});
if (response.ok) {
resultCallback({ code: ExportResultCode.SUCCESS });
} else if (response.status >= 500 && attemptsLeft > 1) {
// Retry on server errors
setTimeout(() => {
this.exportWithRetry(spans, attemptsLeft - 1, resultCallback);
}, 1000);
} else {
resultCallback({
code: ExportResultCode.FAILED,
error: new Error(`Export failed: ${response.status}`)
});
}
} catch (error) {
if (attemptsLeft > 1) {
setTimeout(() => {
this.exportWithRetry(spans, attemptsLeft - 1, resultCallback);
}, 1000);
} else {
resultCallback({
code: ExportResultCode.FAILED,
error: error as Error
});
}
}
}
async shutdown(): Promise<void> {
// Cleanup resources
}
}Install with Tessl CLI
npx tessl i tessl/npm-opentelemetry--sdk-trace-base