Observability Exporters
Runtime observability exporters for Runra — Axiom, OpenTelemetry, JSONL, Postgres, and custom exporters. Event schema, configuration, and filtering
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
| 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 |
Axiom
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
| 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)
['runra-events']
| where type == "exec.completed"
| summarize avg_duration = avg(data.durationMs) by bin(_time, 1m)
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
| 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.
JSONL
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
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
# Parse and filter with jq
cat logs/runra-events.jsonl | jq 'select(.type == "exec.completed")'
# Count events by type
cat logs/runra-events.jsonl | jq -r '.type' | sort | uniq -c
# Calculate average execution duration
cat logs/runra-events.jsonl \
| jq -r 'select(.type == "exec.completed") | .data.durationMs' \
| awk '{sum+=$1; count++} END {print sum/count "ms"}'
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)
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
-- Average execution time by sandbox
SELECT
sandbox_id,
AVG((data->>'durationMs')::numeric) AS avg_duration_ms
FROM runra_events
WHERE type = 'exec.completed'
GROUP BY sandbox_id;
-- Command error rate
SELECT
data->>'command' AS command,
COUNT(*) AS executions,
SUM(CASE WHEN (data->>'exitCode')::int != 0 THEN 1 ELSE 0 END) AS failures
FROM runra_events
WHERE type = 'exec.completed'
GROUP BY data->>'command'
HAVING COUNT(*) >= 10
ORDER BY failures DESC;
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
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
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 destination
filter: {
types: ["exec.completed"],
condition: (e) => e.data.exitCode !== 0,
}
// Send everything except file operations
filter: {
types: ["*"],
condition: (e) => !e.type.startsWith("file."),
}
// Only sandbox lifecycle events
filter: {
types: ["sandbox.*"],
}
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
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 use
Runra.registerObservabilityExporter("my-exporter", () => new MyCustomExporter());
Next Steps
- Runtime Architecture — full event system overview
- SDK Reference — programmatic event handling
- Configuration Reference — all observability options