autotel-terminal
v17.0.8
Published
Terminal dashboard for autotel traces using react-ink
Maintainers
Readme
autotel-terminal
Terminal trace viewer for autotel : Ink (React-for-CLI) powered dashboard for live trace inspection during development. Zero setup, trace-first, autotel-only.
Overview
autotel-terminal is a terminal-native trace viewer for autotel, built with Ink (React for CLIs). It runs in the same Node.js process as your app, wiring directly to the Autotel tracer provider. It streams OpenTelemetry spans from your app and shows them as traces: recent traces list, span tree per trace, search, and a simple waterfall. Built for local development: no browser, no extra backends.
If you need a standalone, multi-signal TUI that receives OTLP/Zipkin/Prometheus/Datadog traffic over the network (traces, metrics, logs), see tools like otel-tui. autotel-terminal stays Autotel-native and process-local: you call renderTerminal() from your Node app and get an Ink dashboard for that process.
Features
- ✅ Trace-first UI : Recent traces list; open a trace to see its span tree (parent/child)
- ✅ Real-time streaming : Spans appear as they complete; optional pause/resume
- ✅ Search : Filter by span name (
/); combine with error-only filter (e) - ✅ Span details : Key attributes first (e.g.
http.route,db.operation); full list + waterfall for selected trace - ✅ Relative time & errors : "2s ago" labels; error badge and new-error indicator
- ✅ Help overlay :
?shows all shortcuts - ✅ Simple setup : Add
StreamingSpanProcessorto autotel and callrenderTerminal()
Installation
npm install autotel-terminal autotel
# or
pnpm add autotel-terminal autotel
# or
yarn add autotel-terminal autotelStandalone CLI (second terminal)
You can run the dashboard as a standalone OTLP receiver that accepts traces, logs, and metrics:
npx autotel-terminalThen run your app in another terminal with OTLP JSON pointed at the dashboard:
OTEL_EXPORTER_OTLP_PROTOCOL=http/json \
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4319 \
node app.jsPort 4319 is used by default to avoid clashing with the standard OTLP port (4318).
CLI Options
-p, --port <port> Port to listen on (default: 4319, env: AUTOTEL_TERMINAL_PORT)
-H, --host <host> Host to bind to (default: 127.0.0.1, env: AUTOTEL_TERMINAL_HOST)
-t, --title <title> Dashboard title (env: AUTOTEL_TERMINAL_TITLE)
-h, --help Show help message
-v, --version Show version numberOTLP Endpoints
| Endpoint | Signal | Description |
|----------|--------|-------------|
| POST /v1/traces | Traces | Receives OTLP JSON spans, streamed into the TUI |
| POST /v1/logs | Logs | Receives OTLP JSON logs, shown in the Logs view (l) |
| POST /v1/metrics | Metrics | Accepts OTLP JSON metrics (acknowledged and counted) |
| GET /healthz | — | Health check |
Quick Start
Recommended Usage
Create a StreamingSpanProcessor and pass it to init(), then use renderTerminal() with the stream:
import { init, trace } from 'autotel';
import {
renderTerminal,
StreamingSpanProcessor,
createTerminalSpanStream,
} from 'autotel-terminal';
// Create streaming processor for the terminal dashboard
const streamingProcessor = new StreamingSpanProcessor(null);
// Initialize autotel with the streaming processor
init({
service: 'my-app',
endpoint: 'http://localhost:4318',
spanProcessors: [streamingProcessor],
});
// Create the stream and launch the dashboard
const terminalStream = createTerminalSpanStream(streamingProcessor);
renderTerminal({ title: 'My App Traces' }, terminalStream);
// Your traced code will now appear in the dashboard
const myFunction = trace((ctx) => async () => {
ctx.setAttribute('example', 'value');
// ... your code
});
await myFunction();With Backend Export
To stream to the terminal AND export to a backend (e.g., Jaeger, OTLP collector):
import { init } from 'autotel';
import {
StreamingSpanProcessor,
createTerminalSpanStream,
renderTerminal,
} from 'autotel-terminal';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
// Create exporter and base processor for your backend
const exporter = new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' });
const batchProcessor = new BatchSpanProcessor(exporter);
// Create streaming processor that wraps your batch processor
// Spans are forwarded to both the dashboard AND the backend
const streamingProcessor = new StreamingSpanProcessor(batchProcessor);
// Initialize autotel
init({
service: 'my-app',
spanProcessors: [streamingProcessor],
});
// Create stream and launch dashboard
const stream = createTerminalSpanStream(streamingProcessor);
renderTerminal({ title: 'My App Traces' }, stream);With Custom Options
renderTerminal(
{
title: 'My App Traces',
showStats: true,
maxSpans: 200,
colors: true, // Auto-detected if TTY
},
stream,
);Dashboard Controls
Once the dashboard is running, use these keyboard controls:
- ↑/↓ - Navigate traces, spans, or logs
- Enter - Open selected trace (trace view) to see span tree
- Esc - Back to trace list or exit search
t- Toggle trace view / span listl- Toggle logs view (when a log stream is hooked up)v- Toggle service summary viewE- Toggle errors viewS- Filter spans to selected service (fromservice.name)R- Filter spans to selected route (fromhttp.route)H- Cycle status filter (all → 2xx → 4xx → 5xx)x- Clear filtersJ- Export selected trace as JSON (printed to stdout)/- Search by span name or log message (type to filter)p- Pause/resume live updatesr- Record snapshot (clears buffers and records until a limit, then pauses)e- Toggle error-only filterc- Clear all spans?- Show help overlay- Ctrl+C - Exit dashboard
Dashboard Features
Trace View (default)
The left panel shows recent traces (grouped by trace ID):
- Root span name, duration, trace ID (short), relative time ("2s ago")
- Error badge when any span in the trace failed
- Enter to open a trace and see its span tree (ASCII parent-child: ├──, └──)
- Esc to go back to the trace list
Span List (toggle with t)
Flat list of recent spans with:
- Span name (truncated), duration (color-coded: green < 500ms, yellow > 500ms, red = error)
- Relative time
- Selection indicator (cyan
›)
Search
Press / to filter by span name. Type to narrow; Esc to clear.
Span Details
The right panel shows detailed information for the selected span:
- Name, status, duration (with "Nx avg" when slower than average for that span name)
- Trace ID, Span ID, Parent Span ID
- Span kind (INTERNAL, SERVER, CLIENT, etc.)
- Key attributes (http.route, db.operation, code.function, etc.) first
- Remaining attributes
- Waterfall for the selected trace (when a trace is open): horizontal bars by duration
Statistics Bar
When enabled, shows:
- Total spans
- Error count
- Average duration
- P95 latency
API Reference
renderTerminal(options?, stream?)
Render the terminal dashboard.
Parameters:
options- Optional dashboard configurationstream- The terminal span stream (fromcreateTerminalSpanStream)
Options:
interface TerminalOptions {
/** Dashboard title (default: 'Autotel Trace Inspector') */
title?: string;
/** Show statistics bar (default: true) */
showStats?: boolean;
/** Maximum number of spans to display (default: 100) */
maxSpans?: number;
/** Enable colors (default: true if TTY) */
colors?: boolean;
}Example:
renderTerminal(
{
title: 'API Server Traces',
showStats: true,
maxSpans: 200,
},
stream,
);StreamingSpanProcessor
A span processor that emits completed spans to subscribers. Can wrap another processor or work standalone.
import { StreamingSpanProcessor } from 'autotel-terminal';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
// Standalone (no forwarding)
const streamingProcessor = new StreamingSpanProcessor(null);
// Or wrap an existing processor (spans forwarded to both)
const batchProcessor = new BatchSpanProcessor(exporter);
const streamingProcessor = new StreamingSpanProcessor(batchProcessor);
// Subscribe to spans directly (alternative to using createTerminalSpanStream)
const unsubscribe = streamingProcessor.subscribe((span) => {
console.log('Span ended:', span.name);
});
// Later, unsubscribe
unsubscribe();Constructor:
new StreamingSpanProcessor(wrappedProcessor?: SpanProcessor | null)wrappedProcessor- Optional processor to wrap. If provided, spans are forwarded to it. Ifnull, spans are only emitted to subscribers.
Methods:
subscribe(callback: (span: ReadableSpan) => void): () => void- Subscribe to span end events. Returns unsubscribe function.forceFlush(): Promise<void>- Flush wrapped processor (if any).shutdown(): Promise<void>- Shutdown processor and clear subscribers.
createTerminalSpanStream(processor)
Create a terminal-compatible stream from a StreamingSpanProcessor.
import { createTerminalSpanStream, StreamingSpanProcessor } from 'autotel-terminal';
const processor = new StreamingSpanProcessor(null);
const stream = createTerminalSpanStream(processor);
// Subscribe to span events
stream.onSpanEnd((event) => {
console.log('Span:', event.name, event.durationMs + 'ms');
});Returns: TerminalSpanStream with onSpanEnd(callback) method.
Event Format:
interface TerminalSpanEvent {
name: string;
spanId: string;
traceId: string;
parentSpanId?: string;
startTime: number;
endTime: number;
durationMs: number;
status: 'OK' | 'ERROR' | 'UNSET';
kind?: string;
attributes?: Record<string, unknown>;
}Use Cases
Development Debugging
Perfect for debugging during development - see traces in real-time without leaving your terminal:
import { init } from 'autotel';
import {
StreamingSpanProcessor,
createTerminalSpanStream,
renderTerminal,
} from 'autotel-terminal';
const streamingProcessor = new StreamingSpanProcessor(null);
init({
service: 'dev-server',
spanProcessors: [streamingProcessor],
});
const stream = createTerminalSpanStream(streamingProcessor);
renderTerminal({ title: 'Dev Server Traces' }, stream);Testing
Use the streaming processor to assert on spans in tests:
import { StreamingSpanProcessor, createTerminalSpanStream } from 'autotel-terminal';
import { init } from 'autotel';
const processor = new StreamingSpanProcessor(null);
const stream = createTerminalSpanStream(processor);
init({
service: 'test',
spanProcessors: [processor],
});
// Collect spans
const spans: TerminalSpanEvent[] = [];
stream.onSpanEnd((span) => spans.push(span));
// Run your code
await myFunction();
// Assert on spans
expect(spans).toHaveLength(1);
expect(spans[0].name).toBe('myFunction');Custom Dashboards
Build your own dashboard using the streaming processor:
import { StreamingSpanProcessor, createTerminalSpanStream } from 'autotel-terminal';
const processor = new StreamingSpanProcessor(null);
const stream = createTerminalSpanStream(processor);
stream.onSpanEnd((event) => {
// Send to your custom dashboard
myCustomDashboard.addSpan(event);
});Integration with autotel
autotel-terminal is built for autotel. It auto-wires to the global tracer provider and expects StreamingSpanProcessor in your spanProcessors array:
import { init } from 'autotel';
import {
StreamingSpanProcessor,
createTerminalSpanStream,
renderTerminal,
} from 'autotel-terminal';
const streamingProcessor = new StreamingSpanProcessor(null);
init({
service: 'my-app',
endpoint: 'http://localhost:4318',
spanProcessors: [streamingProcessor],
});
const stream = createTerminalSpanStream(streamingProcessor);
renderTerminal({}, stream);Wiring logs into the dashboard
The terminal dashboard can show logs in the Logs view (l) if you emit TerminalLogEvents into the global log stream.
In your app (or adapter), call getTerminalLogStream().emit(...) and include traceId / spanId when possible for correlation:
import { getTerminalLogStream } from 'autotel-terminal';
const logStream = getTerminalLogStream();
logStream.emit({
time: Date.now(),
level: 'info',
message: 'request completed',
traceId: '...', // optional but recommended
spanId: '...', // optional but recommended
attributes: {
'service.name': 'my-service',
'http.route': '/users/:id',
'http.status_code': 200,
},
});This is intentionally Autotel-native: you can wire this from request logger/canonical log lines, producing one coherent log snapshot per request.
Requirements
- Node.js 22+
- Terminal with TTY support (for colors and interactivity)
autotelpackage (peer dependency)
Bundle Size
- Core dashboard: ~25KB minified
- With dependencies (ink, react): ~150KB total
- Development tool - not intended for production bundles
Limitations
- Development only : Not designed for production use
- TTY required : Colors and interactivity require a terminal
- Memory : Keeps spans in memory (limited by
maxSpansoption) - Single instance : Only one dashboard can run at a time
Manual verification
When testing the dashboard (e.g. with example-terminal or your app), you can verify:
- Trace list : Trigger some traced work; recent traces appear with root span name, duration, relative time
- Trace tree : Select a trace and press Enter; span tree shows with indented children
- Search : Press
/, type a span name; list filters; Esc clears - Errors : Press
efor error-only filter; traces with failed spans show an error badge - Help : Press
?to see all shortcuts - Waterfall : With a trace open, the details panel shows a simple duration waterfall
- Key attributes : In span details,
http.route,db.operation,code.function, etc. appear first
Examples
See the autotel examples directory for complete working examples:
apps/example-terminal- Terminal dashboard with live trace viewing
License
MIT © Jag Reehal
