An LLM agent is a distributed system whose components are a stochastic model, a set of tools that may call anything, and a state that evolves with each call. Debugging without traces is guesswork. The difference between teams that ship reliable agents and teams that don't is usually visible in whether they log every model call and tool call as a span from day one.
This page is what to log, how to structure it, and which tools are worth adopting.
Agents need the same three layers as any distributed system — metrics, logs, traces — but the semantics differ.
| Layer | HTTP service | LLM agent |
|---|---|---|
| Metrics | req/s, error rate, p50/p95 latency | task success rate, cost per task, tool validity rate, token throughput |
| Logs | structured app logs | structured app logs + full LLM inputs/outputs (sampled) |
| Traces | one span per service hop | one span per LLM call and one per tool dispatch, nested under a per-task trace |
Metrics tell you that something's wrong. Traces tell you what and where. Logs contain the prompts you need to reproduce. You need all three.
One trace per agent task, with spans at every step. Minimum span shape:
{
"trace_id": "task_4f2e...",
"span_id": "step_3_llm",
"parent_span_id": "step_3",
"name": "model.chat",
"start_ts": ...,
"end_ts": ...,
"attributes": {
"agent_version": "v2.3.1",
"model": "claude-sonnet-4-6",
"input_tokens": 2451,
"output_tokens": 312,
"cached_tokens": 2100,
"cost_usd": 0.0091,
"temperature": 0.0,
"stop_reason": "tool_use",
"tool_calls": ["cancel_subscription"],
"validation_ok": true
}
}
Every tool call gets its own span, nested under the step:
{
"trace_id": "task_4f2e...",
"span_id": "step_3_tool_cancel",
"parent_span_id": "step_3",
"name": "tool.cancel_subscription",
"attributes": {
"tool_input_hash": "...",
"tool_output_hash": "...",
"tool_duration_ms": 412,
"tool_status": "ok"
}
}
The attribute set matters more than the trace format. You will query this data constantly; design for the queries you'll actually run.
Work backwards from these:
If any of these takes more than a minute to answer with your current telemetry, you have an observability gap.
LLM call logs are verbose. A 20-step agent with 30k-token contexts produces ~600k tokens of span data per task. At 1 task/s, that's 2 TB/month. You'll want to sample.
Reasonable policy:
Raise the success-task sampling rate to 100% during incidents, then drop it back.
LLM cost is a primary dimension, not a footnote. Log per-call:
Aggregate to dashboards:
See LlmTokenEconomicsAndPricing for the accounting specifics.
| Tool | Strengths | When to pick |
|---|---|---|
| LangSmith | Deep LangChain/LangGraph integration, excellent UI for trace exploration, built-in eval workflow | You're on LangChain; you want the lowest-friction option |
| Langfuse | Open source, model-agnostic, OpenTelemetry-ish, self-hostable | You want self-hosted or you're not on LangChain |
| OpenLLMetry | OpenTelemetry instrumentation for LLM frameworks; exports to your existing OTel stack | You already run Datadog/Honeycomb/Jaeger and want LLM data in the same pane |
| Arize Phoenix | Open source, eval-centric, strong on embedding drift detection | You care about eval-in-prod more than trace browsing |
| Homegrown Postgres | Total control, simple queries, no vendor lock-in | Small team, simple needs, doesn't mind building the UI yourself |
Strong opinion: start with Langfuse or OpenLLMetry. Both are open source and model-agnostic. The commercial tools add polish but lock you in; the self-hosted ones give you the data.
Real alerts, pager-worthy:
Nice-to-have:
A user reports the agent did something wrong. Ideal path:
Without telemetry this cycle is "I can't reproduce it." That's the gap observability closes.
Full-prompt logging captures everything users said. That has compliance implications:
Every LLM call: input, output, model, tokens, cost, latency, cache stats
Every tool call: name, input, output, duration, error, idempotency key
Every task: user_id, goal, final_state, total_steps, total_cost, terminal_reason
Sampled full transcripts: 1-5% of successes, 100% of failures
One trace ID: correlates everything above
Three hours of work; years of saved debugging time.