Observability Exporters
Runra Runtime emits structured events for every operation. Observability exporters send these events to your monitoring stack. Configure multiple exporters simultaneously for different use cases.
Built-in Exporters
Section titled “Built-in Exporters”| Exporter | Key | Best For |
|---|---|---|
| Axiom | axiom | Real-time structured logging and dashboards |
| OpenTelemetry | otel | Standard observability pipelines |
| JSONL | jsonl | Local file-based logging |
| Postgres | postgres | Long-term storage and SQL analytics |
| Console | console | Development and debugging |
| Custom | custom | Bring your own destination |
Send events to Axiom for real-time search, dashboards, and alerts:
import { Runra } from "@runra/runtime";
const runra = new Runra({ observability: { provider: "axiom", config: { token: process.env.AXIOM_TOKEN, dataset: "runra-events", batchSize: 100, flushIntervalMs: 5000, }, }, // ... sandbox and agent config});Configuration
Section titled “Configuration”| Option | Type | Default | Description |
|---|---|---|---|
token | string | AXIOM_TOKEN env | Axiom API or ingest token |
dataset | string | "runra-events" | Target dataset name |
url | string | "https://cloud.axiom.co" | Axiom deployment URL |
batchSize | number | 100 | Max events per ingest request |
flushIntervalMs | number | 5000 | Max interval between flushes |
onError | function | — | Error handler callback |
Example Axiom Query (APL)
Section titled “Example Axiom Query (APL)”['runra-events']| where type == "exec.completed"| summarize avg_duration = avg(data.durationMs) by bin(_time, 1m)OpenTelemetry
Section titled “OpenTelemetry”Export events as OpenTelemetry spans and logs to any OTLP-compatible backend (Jaeger, Grafana Tempo, Honeycomb, Datadog):
const runra = new Runra({ observability: { provider: "otel", config: { endpoint: "https://otel-collector.internal:4318/v1/traces", protocol: "http/protobuf", // or "grpc" headers: { "X-API-Key": process.env.OTEL_API_KEY, }, serviceName: "runra-runtime", serviceVersion: "1.2.0", spanProcessor: "batch", // or "simple" batchConfig: { maxQueueSize: 2048, maxExportBatchSize: 512, scheduleDelayMs: 5000, }, }, },});Event-to-Span Mapping
Section titled “Event-to-Span Mapping”| Runtime Event | OTel Span |
|---|---|
sandbox.created | Span: sandbox.create |
exec.completed | Span: sandbox.execute |
file.created | Span: sandbox.file.write |
file.read | Span: sandbox.file.read |
agent.tool_call | Span: agent.tool_call |
sandbox.paused | Span: sandbox.pause |
sandbox.terminated | Span: sandbox.terminate |
Each span includes attributes from the event’s data field and links to the parent runId.
Write events to newline-delimited JSON files. Great for local debugging, CI pipelines, and offline analysis:
const runra = new Runra({ observability: { provider: "jsonl", config: { filePath: "./logs/runra-events.jsonl", append: true, // Append to existing file rotateSizeBytes: 100 * 1024 * 1024, // 100 MB file rotation rotateFiles: 10, // Keep 10 rotated files gzip: true, // Gzip rotated files }, },});Output Format
Section titled “Output Format”Each line is a JSON object:
{"id":"evt_abc123","timestamp":"2026-06-15T10:30:00.000Z","type":"sandbox.created","sandboxId":"sb_x7k2m9p4q1","runId":"run_a1b2c3","data":{"image":"node:22","cpu":2,"memoryMb":4096}}{"id":"evt_def456","timestamp":"2026-06-15T10:30:05.000Z","type":"exec.completed","sandboxId":"sb_x7k2m9p4q1","runId":"run_a1b2c3","data":{"command":"node --version","exitCode":0,"durationMs":45}}Analyzing JSONL Files
Section titled “Analyzing JSONL Files”# Parse and filter with jqcat logs/runra-events.jsonl | jq 'select(.type == "exec.completed")'
# Count events by typecat logs/runra-events.jsonl | jq -r '.type' | sort | uniq -c
# Calculate average execution durationcat logs/runra-events.jsonl \ | jq -r 'select(.type == "exec.completed") | .data.durationMs' \ | awk '{sum+=$1; count++} END {print sum/count "ms"}'Postgres
Section titled “Postgres”Store events in PostgreSQL for long-term analytics and SQL querying:
const runra = new Runra({ observability: { provider: "postgres", config: { connectionString: process.env.DATABASE_URL, table: "runra_events", schema: "public", batchSize: 50, flushIntervalMs: 2000, createTable: true, // Auto-create table useJsonColumn: true, // Store data as JSONB ssl: true, }, },});Table Schema (Auto-Created)
Section titled “Table Schema (Auto-Created)”CREATE TABLE IF NOT EXISTS runra_events ( id TEXT PRIMARY KEY, timestamp TIMESTAMPTZ NOT NULL, type TEXT NOT NULL, sandbox_id TEXT NOT NULL, run_id TEXT NOT NULL, data JSONB NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW());
CREATE INDEX idx_events_type ON runra_events (type);CREATE INDEX idx_events_sandbox ON runra_events (sandbox_id);CREATE INDEX idx_events_run ON runra_events (run_id);CREATE INDEX idx_events_timestamp ON runra_events (timestamp DESC);Example Queries
Section titled “Example Queries”-- Average execution time by sandboxSELECT sandbox_id, AVG((data->>'durationMs')::numeric) AS avg_duration_msFROM runra_eventsWHERE type = 'exec.completed'GROUP BY sandbox_id;
-- Command error rateSELECT data->>'command' AS command, COUNT(*) AS executions, SUM(CASE WHEN (data->>'exitCode')::int != 0 THEN 1 ELSE 0 END) AS failuresFROM runra_eventsWHERE type = 'exec.completed'GROUP BY data->>'command'HAVING COUNT(*) >= 10ORDER BY failures DESC;Console
Section titled “Console”Simple console output for development. Supports JSON and pretty-print modes:
// Pretty-print (default)const runra = new Runra({ observability: { provider: "console", config: { format: "pretty", // "pretty" or "json" level: "all", // "all", "sandbox", "exec", "agent", "error" includeTimestamp: true, colorize: true, }, },});
// JSON output (parseable)const runra = new Runra({ observability: { provider: "console", config: { format: "json" }, },});Multiple Exporters
Section titled “Multiple Exporters”Use multiple exporters simultaneously for different needs:
const runra = new Runra({ observability: { exporters: [ { provider: "axiom", config: { token: "...", dataset: "runra-events" }, filter: { types: ["*"] }, // Send everything }, { provider: "jsonl", config: { filePath: "./logs/runra.jsonl" }, filter: { types: ["exec.*", "sandbox.*"] }, // Only sandbox and exec events }, { provider: "postgres", config: { connectionString: "..." }, filter: { types: ["*"] }, }, { provider: "console", config: { format: "pretty", level: "error" }, filter: { types: ["exec.completed"], condition: (e) => e.data.exitCode !== 0, // Only failed commands }, }, ], },});Event Filtering
Section titled “Event Filtering”Each exporter can filter which events it receives:
interface ExportFilter { /** Event type patterns to include (supports "*" wildcard) */ types?: string[];
/** Custom condition function */ condition?: (event: RuntimeEvent) => boolean;
/** Minimum level */ minLevel?: "debug" | "info" | "warn" | "error";}Examples:
// Only send errors to a specific destinationfilter: { types: ["exec.completed"], condition: (e) => e.data.exitCode !== 0,}
// Send everything except file operationsfilter: { types: ["*"], condition: (e) => !e.type.startsWith("file."),}
// Only sandbox lifecycle eventsfilter: { types: ["sandbox.*"],}Event Schema
Section titled “Event Schema”interface RuntimeEvent { /** Unique event ID (UUID v4) */ id: string;
/** ISO 8601 timestamp */ timestamp: string;
/** Event type discriminator */ type: | "sandbox.created" | "sandbox.started" | "sandbox.paused" | "sandbox.resumed" | "sandbox.terminated" | "sandbox.error" | "exec.started" | "exec.completed" | "exec.streamed" | "exec.error" | "file.created" | "file.read" | "file.deleted" | "file.error" | "port.exposed" | "port.closed" | "agent.thinking" | "agent.tool_call" | "agent.tool_result" | "agent.error" | "cost.runtime" | "cost.llm";
/** Sandbox context */ sandboxId: string;
/** Agent run context */ runId: string;
/** Type-specific payload */ data: Record<string, unknown>;}Custom Exporter
Section titled “Custom Exporter”Implement the ObservabilityExporter interface:
import type { ObservabilityExporter, RuntimeEvent } from "@runra/runtime";
class MyCustomExporter implements ObservabilityExporter { readonly id = "my-exporter";
private buffer: RuntimeEvent[] = []; private endpoint: string;
async initialize(config: Record<string, unknown>): Promise<void> { this.endpoint = config.endpoint as string; }
async export(events: RuntimeEvent[]): Promise<void> { // Send events to your system const response = await fetch(this.endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ events }), });
if (!response.ok) { throw new Error(`Export failed: ${response.statusText}`); } }
async flush(): Promise<void> { // Flush any buffered events }
async dispose(): Promise<void> { // Cleanup }}
// Register and useRunra.registerObservabilityExporter("my-exporter", () => new MyCustomExporter());Next Steps
Section titled “Next Steps”- Runtime Architecture — full event system overview
- SDK Reference — programmatic event handling
- Configuration Reference — all observability options