Skip to content

OpenTelemetry Collector

SideSeat includes a built-in OpenTelemetry collector optimized for local AI development workflows. It receives OTLP traces via HTTP and gRPC, stores them locally (DuckDB + SQLite), and provides real-time streaming via SSE.

  • OTLP-compatible: Receives traces via standard OpenTelemetry protocol (HTTP JSON/Protobuf, gRPC)
  • Framework detection: Automatically detects Strands, LangGraph, Vercel AI, Google ADK, and other AI frameworks
  • GenAI field extraction: Extracts token usage, model info, and other GenAI-specific fields
  • Bounded memory: Configurable buffer limits prevent memory exhaustion
  • FIFO storage: Automatic cleanup when storage limits are reached
  • Real-time streaming: SSE endpoint for live trace updates
  • Efficient storage: DuckDB + SQLite with indexed columns for fast queries
EndpointMethodContent-TypeDescription
/otel/{project_id}/v1/tracesPOSTapplication/jsonOTLP JSON traces
/otel/{project_id}/v1/tracesPOSTapplication/x-protobufOTLP Protobuf traces
localhost:4317gRPCProtobufOTLP gRPC endpoint
EndpointMethodDescription
/api/v1/project/{project_id}/otel/tracesGETList traces with filtering
/api/v1/project/{project_id}/otel/traces/filter-optionsGETGet available filter options
/api/v1/project/{project_id}/otel/traces/{trace_id}GETGet single trace details
/api/v1/project/{project_id}/otel/traces/{trace_id}DELETEDelete a trace and all associated data
/api/v1/project/{project_id}/otel/traces/{trace_id}/spansGETGet spans for a trace
/api/v1/project/{project_id}/otel/spansGETQuery spans with GenAI fields
/api/v1/project/{project_id}/otel/traces/{trace_id}/spans/{span_id}GETGet span detail with events
/api/v1/project/{project_id}/otel/traces/{trace_id}/spans/{span_id}/messagesGETGet normalized span messages
/api/v1/project/{project_id}/otel/sessionsGETList sessions with filtering
/api/v1/project/{project_id}/otel/sessions/{session_id}GETGet single session details
/api/v1/project/{project_id}/otel/sessionsDELETEDelete sessions (batch)
/api/v1/project/{project_id}/otel/sessions/filter-optionsGETGet available filter options
EndpointMethodDescription
/api/v1/project/{project_id}/otel/sseGETSSE stream of trace events

All OTel settings are under the otel key in your config file:

{
"otel": {
"grpc": {
"enabled": true,
"port": 4317
},
"retention": {
"max_age_minutes": 10080,
"max_spans": 5000000
}
}
}

See Config Manager for the full configuration reference.

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
# Configure exporter to send to SideSeat
exporter = OTLPSpanExporter(endpoint="http://localhost:5388/otel/default/v1/traces")
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
# Create traces
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("my-agent-operation"):
# Your agent code here
pass
from strands import Agent
from strands.models import BedrockModel
from strands.telemetry import StrandsTelemetry
# Configure telemetry to export to SideSeat
telemetry = StrandsTelemetry()
telemetry.setup_otlp_exporter(endpoint="http://localhost:5388/otel/default/v1/traces")
# Create agent with optional trace attributes
model = BedrockModel(model_id="anthropic.claude-sonnet-4-5-20250929-v1:0")
agent = Agent(
name="my-agent",
model=model,
trace_attributes={
"session.id": "my-session-123",
"user.id": "user-456",
},
)
# Don't forget to flush telemetry before exit
# telemetry.tracer_provider.force_flush()
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const exporter = new OTLPTraceExporter({
url: 'http://localhost:5388/otel/default/v1/traces',
});
const provider = new NodeTracerProvider();
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register();

For higher throughput, use the gRPC endpoint:

from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
exporter = OTLPSpanExporter(endpoint="localhost:4317", insecure=True)

SideSeat automatically detects and normalizes spans from popular AI frameworks:

FrameworkDetection MethodExtracted Fields
StrandsScope name, resource attrsCycle ID, agent info
Vercel AIScope name, attributesModel, tokens, telemetry
LangGraphScope name, attributesNode, edge, state
LangChainScope name, attributesChain type, run ID
CrewAIScope name, attributesCrew, agent, task
AutoGenScope name, attributesAgent name, chat round
Google ADKScope name, attributesAgent name, model
OpenAI AgentsLogfire span typeAgent name, model
OpenInferenceAttribute prefixSession ID, user ID
Generic GenAIgen_ai.* attributesModel, tokens, system

The collector extracts and normalizes GenAI-specific fields:

FieldDescription
gen_ai_systemAI provider (openai, anthropic, etc.)
gen_ai_request_modelRequested model name
gen_ai_response_modelActual model used
gen_ai_operation_nameOperation type (chat, completion)
gen_ai_agent_nameAgent name (for agent frameworks)
gen_ai_tool_nameTool name (for tool calls)
usage_input_tokensInput/prompt tokens
usage_output_tokensOutput/completion tokens
usage_total_tokensTotal tokens (computed if not provided)
usage_cache_read_tokensCache read tokens (Anthropic)
usage_cache_write_tokensCache write tokens (Anthropic)
time_to_first_token_msTime to first token (TTFT)
request_duration_msTotal request duration
session_idSession/conversation ID

Span events (messages, tool calls, choices) are automatically categorized:

Event TypeRoleDescription
user_messageuserUser input messages
assistant_messageassistantModel responses
system_messagesystemSystem prompts
tool_callassistantTool/function calls
tool_resulttoolTool execution results
choiceassistantCompletion choices with finish_reason

Events include content_preview (first 500 chars) and tool correlation fields (tool_name, tool_call_id).

Trace data is stored locally with DuckDB for analytics and SQLite for app metadata. Full span data is preserved as JSON for complete access to all fields.

Storage is managed with optional retention limits:

  • Time-based: If retention.max_age_minutes is set, data older than that is deleted. No default (disabled unless configured).
  • Volume-based: retention.max_spans limits the number of stored spans. Default: 5,000,000. Oldest spans are deleted first.

Subscribe to span events via Server-Sent Events:

const eventSource = new EventSource(
'http://localhost:5388/api/v1/project/default/otel/sse'
);
eventSource.addEventListener('span', (event) => {
const payload = JSON.parse(event.data);
console.log('Span event:', payload);
});
  • Maximum connections: 100 (configurable)
  • Connection timeout: 1 hour (configurable)
  • Keepalive interval: 30 seconds (configurable)

Use the filters query parameter on list endpoints to filter by attributes and fields. Pass a JSON array of filter objects (URL-encoded).

The attribute filter API supports these operators:

OperatorDescriptionExample
eqEquals{"key":"env","op":"eq","value":"prod"}
neNot equals{"key":"env","op":"ne","value":"dev"}
containsContains substring{"key":"user_id","op":"contains","value":"admin"}
starts_withStarts with{"key":"session_id","op":"starts_with","value":"sess_"}
inIn list{"key":"env","op":"in","value":["prod","staging"]}
gt, lt, gte, lteNumeric comparison{"key":"latency","op":"gt","value":1000}
is_nullAttribute not present{"key":"error","op":"is_null","value":null}
is_not_nullAttribute present{"key":"user_id","op":"is_not_null","value":null}
Terminal window
curl http://localhost:5388/api/v1/project/default/otel/traces
Terminal window
# Filter traces where environment=production
curl "http://localhost:5388/api/v1/project/default/otel/traces?filters=%5B%7B%22key%22%3A%22environment%22%2C%22op%22%3A%22eq%22%2C%22value%22%3A%22production%22%7D%5D"
# Decoded: filters=[{\"key\":\"environment\",\"op\":\"eq\",\"value\":\"production\"}]
Terminal window
# Filter by environment AND user_id
curl "http://localhost:5388/api/v1/project/default/otel/traces?filters=%5B%7B%22key%22%3A%22environment%22%2C%22op%22%3A%22eq%22%2C%22value%22%3A%22production%22%7D%2C%7B%22key%22%3A%22user_id%22%2C%22op%22%3A%22eq%22%2C%22value%22%3A%22user-123%22%7D%5D"
Terminal window
curl http://localhost:5388/api/v1/project/default/otel/traces/abc123def456

Discover available filter values for building UI dropdowns:

Terminal window
curl http://localhost:5388/api/v1/project/default/otel/traces/filter-options

Response:

{
"services": ["my-agent", "my-service"],
"frameworks": ["langchain", "openai"],
"attributes": [
{
"key": "environment",
"key_type": "string",
"entity_type": "trace",
"sample_values": ["production", "staging", "development"]
}
]
}
  1. Check OTel is enabled: "otel": { "enabled": true }
  2. Verify endpoint URL matches your exporter configuration
  3. Check server logs for ingestion errors

Reduce buffer sizes in config:

{
"otel": {
"ingestion": {
"channel_capacity": 500,
"buffer_max_spans": 500,
"buffer_max_bytes": 5242880
}
}
}
  1. Reduce retention.max_age_minutes for a shorter retention period
  2. The local database will be cleaned up automatically based on retention settings