Getting insights from SQLite tracer
Run Stario with SqliteTracer so finished spans land in a SQLite file you can query with ad hoc SQL—handy for local debugging and quick operational questions. The file is not a distributed trace store; treat it as a local sink (Bundled tracers, SqliteTracer).
STARIO_TRACER=sqlite STARIO_TRACERS_SQLITE=traces.sqlite3 stario serve main:bootstrapWith STARIO_TRACER=sqlite, the CLI builds a SqliteTracer from STARIO_TRACERS_SQLITE*. Unset environment variables leave constructor defaults in place:
| Variable | When unset | Meaning |
|---|---|---|
STARIO_TRACERS_SQLITE | stario-traces.sqlite3 in cwd | SQLite file path |
STARIO_TRACERS_SQLITE_FLUSH_INTERVAL | 0.25 | Writer flush interval in seconds |
STARIO_TRACERS_SQLITE_MAX_PENDING_SPANS | 65536 | Pending queue capacity before finished spans are dropped |
STARIO_TRACERS_SQLITE_MAX_BATCH_SPANS | 1024 | Spans written per writer batch |
Only variables you set override those defaults. The same pattern applies to STARIO_TRACERS_JSON_* when you set STARIO_TRACER=json.
Stario 4 uses a SQLite schema with tables spans, span_events, and span_links. Delete old trace files if you still have pre-v4 databases — there is no in-place migration. HTTP metadata lives in attrs_json, not dedicated columns.
Example for a mounted data volume:
STARIO_TRACER=sqlite STARIO_TRACERS_SQLITE=/data/traces.sqlite3 stario serve main:bootstrapFor path and tuning outside these variables, set STARIO_TRACER to a custom tracer callable (module:callable) that returns a configured SqliteTracer.
What gets stored
The v4 schema (see stario.telemetry.sqlite) has three tables:
spans— one row per finished span:span_id,trace_id,parent_id,name,start_ns,end_ns,duration_ns,status(okorerror),errortext when failed,attrs_jsonfor attributes.span_events— named events on a span (name,time_ns,attrs_json,body). Exception records usename = 'exception'.span_links— cross-span references fromSpan.link(name, span_id, …).
HTTP request spans record attributes such as request.method, request.path, and response.status_code inside attrs_json—not as top-level columns. Keys are flat dotted strings in JSON (not nested objects), so SQL must quote them: attrs_json ->> 'request.path' or json_extract(attrs_json, '$."request.path"'). Root spans are not only HTTP requests (server.startup, server.shutdown, and other roots may lack request.path).
Query HTTP traffic with attrs_json ->> 'request.path' IS NOT NULL. Use parent_id IS NULL for trace roots when you want top-level work units.
Example queries
Open the file with sqlite3, the SQLite extension in your editor, or any SQL client:
sqlite3 traces.sqlite3Queries below read HTTP fields from attrs_json (request.method, request.path, response.status_code).
Average request duration (HTTP root spans)
Restrict to HTTP traffic so startup and other non-request roots do not skew the average. HTTP spans are usually trace roots (parent_id IS NULL) with a request.path attribute.
SELECT ROUND(AVG(duration_ns) / 1e6, 3) AS avg_ms, COUNT(*) AS nFROM spansWHERE parent_id IS NULL AND attrs_json ->> 'request.path' IS NOT NULL;Approximate p95 latency (HTTP root spans)
Percentiles are not built into older SQLite builds; this uses an ordered offset—good enough for rough SLO checks when you have enough samples:
SELECT duration_ns / 1e6 AS approx_p95_msFROM spansWHERE parent_id IS NULL AND attrs_json ->> 'request.path' IS NOT NULLORDER BY duration_ns ASCLIMIT 1 OFFSET ( SELECT CASE WHEN COUNT(*) = 0 THEN 0 ELSE MAX(0, CAST((COUNT(*) - 1) * 0.95 AS INTEGER)) END FROM spans AS s2 WHERE s2.parent_id IS NULL AND s2.attrs_json ->> 'request.path' IS NOT NULL);Most recent HTTP requests
SELECT attrs_json ->> 'request.method' AS method, attrs_json ->> 'request.path' AS path, attrs_json ->> 'response.status_code' AS status, duration_ns / 1e6 AS dur_ms, datetime(end_ns / 1e9, 'unixepoch') AS ended_utcFROM spansWHERE attrs_json ->> 'request.path' IS NOT NULLORDER BY end_ns DESCLIMIT 20;Slowest HTTP requests (by duration)
SELECT attrs_json ->> 'request.method' AS method, attrs_json ->> 'request.path' AS path, attrs_json ->> 'response.status_code' AS status, duration_ns / 1e6 AS dur_ms, datetime(end_ns / 1e9, 'unixepoch') AS ended_utcFROM spansWHERE attrs_json ->> 'request.path' IS NOT NULLORDER BY duration_ns DESCLIMIT 20;HTTP 5xx responses
SELECT attrs_json ->> 'request.method' AS method, attrs_json ->> 'request.path' AS path, attrs_json ->> 'response.status_code' AS status, duration_ns / 1e6 AS dur_ms, datetime(end_ns / 1e9, 'unixepoch') AS ended_utcFROM spansWHERE CAST(attrs_json ->> 'response.status_code' AS INTEGER) >= 500ORDER BY end_ns DESCLIMIT 50;Spans marked failed (status = 'error')
SELECT name, error, attrs_json ->> 'request.path' AS path, datetime(end_ns / 1e9, 'unixepoch') AS ended_utcFROM spansWHERE status = 'error'ORDER BY end_ns DESCLIMIT 50;Exception events (with request path when present)
SELECT e.name AS event_name, s.attrs_json ->> 'request.path' AS path, e.body AS detail, datetime(e.time_ns / 1e9, 'unixepoch') AS event_utcFROM span_events AS eJOIN spans AS s ON s.span_id = e.span_idWHERE e.name = 'exception'ORDER BY e.time_ns DESCLIMIT 50;Span links (cross-trace references)
SELECT s.name AS from_span, l.name AS link_name, l.target_span_id, datetime(s.end_ns / 1e9, 'unixepoch') AS ended_utcFROM span_links AS lJOIN spans AS s ON s.span_id = l.span_idORDER BY s.end_ns DESCLIMIT 50;Tracer health (drops and writer errors)
v4 does not persist tracer health in SQLite. Read counters from a SqliteTracer you construct in tests or from your own wiring in production (tracer.stats() → TelemetryStats: dropped_spans, writer_error_count, serialization_error_count, last_writer_error, …). TestClient uses TestTracer, whose stats() does not reflect SQLite sink health.
Custom and remote tracers
If you need Datadog, OpenTelemetry, or another sink, implement the Tracer protocol and wire it from the CLI or Server — see Telemetry — Custom tracers.
Related
Telemetry reference — tracer env vars and schema
Telemetry —
SqliteTracer,JsonTracer, and span modelTelemetry design — why spans look the way they do
Testing — asserting on spans in tests