Data Model
Tael normalizes incoming OTLP into four kinds of records: spans, logs, metrics, and trace comments. Every record carries a trace_id where one exists, which is what makes cross-signal correlation a single query rather than a manual join.
Spans
A span is one unit of work — a request handler, a job, an LLM call, a tool invocation. This is the primary record you query in Tael.
- Name
trace_id- Type
- string
- Description
Groups all spans, logs, and metrics for one logical operation.
- Name
span_id / parent_span_id- Type
- string
- Description
Position within the trace tree. The root span has no parent.
- Name
service- Type
- string
- Description
From
OTEL_SERVICE_NAME/service.name.
- Name
operation- Type
- string
- Description
The span name (e.g.
http.request,host.prompt).
- Name
start_time / end_time / duration_ms- Type
- timestamp / ms
- Description
Wall-clock timing. Use span duration for timing, not a separate metric.
- Name
status- Type
- ok | error | unset
- Description
Set
erroron failure and Tael surfaces it in--status errorqueries.
- Name
kind- Type
- enum
- Description
INTERNAL(default),CLIENT,SERVER,LLM, orTOOL.
- Name
attributes- Type
- JSON map
- Description
Arbitrary key/value facts. This is where wide events live.
- Name
events- Type
- array
- Description
Timestamped events within the span, including recorded exceptions.
LLM spans
When a span's kind is LLM, Tael extracts the OpenTelemetry gen_ai.* semantic conventions into typed, queryable columns instead of leaving them buried in the attributes map:
- Name
provider / model- Type
- string
- Description
e.g.
anthropic/claude-opus-4-7. Fromgen_ai.request.model.
- Name
operation- Type
- chat | completion | embedding | tool
- Description
The kind of model call.
- Name
input_tokens / output_tokens / total_tokens- Type
- int
- Description
From
gen_ai.usage.input_tokens/gen_ai.usage.output_tokens.
- Name
cost_usd- Type
- float
- Description
Cost of the call, when reported.
- Name
ttft_ms / inter_token_ms- Type
- ms
- Description
Time to first token and mean inter-token latency.
- Name
prompt_sha256 / completion_sha256- Type
- string
- Description
Hashes of the prompt and completion blobs, so payloads are deduplicated and full-text searchable.
- Name
finish_reason / temperature- Type
- string / float
- Description
stop,length,content_filter, etc., and the sampling temperature.
Because these are real columns, you can ask things like "slow prompt calls last hour" or "total cost by model today" directly with tael query traces or tael query sql.
Logs
- Name
timestamp / observed_timestamp- Type
- timestamp
- Description
- Name
severity- Type
- trace | debug | info | warn | error | fatal
- Description
- Name
body- Type
- string
- Description
The log message. Oversized bodies are stored as blobs.
- Name
service- Type
- string
- Description
- Name
trace_id / span_id- Type
- string
- Description
Set when the log was emitted inside an active span — this is what links logs to traces.
- Name
attributes- Type
- JSON map
- Description
Metrics
- Name
metric_name- Type
- string
- Description
- Name
metric_type- Type
- gauge | counter | histogram | summary | unknown
- Description
Prometheus remote-write v1 loses type information, so those points land as
unknown.
- Name
value- Type
- float
- Description
- Name
timestamp- Type
- timestamp
- Description
- Name
labels- Type
- map
- Description
- Name
service- Type
- string
- Description
Trace comments
Annotations attached to a trace (optionally to a specific span) that persist across sessions. See Trace Comments for the full model and the structured conventions agents use.
Querying it all
These four record types are exposed as SQL tables — spans, logs, metrics, and trace_comments — through tael query sql:
tael query sql "SELECT service, COUNT(*) AS n FROM spans WHERE status = 'error' GROUP BY service ORDER BY n DESC"