LLM observability is the practice of capturing a structured record of every AI API request — model, prompt and completion tokens, latency, cost, and error type — so you can see what your models actually cost, how fast they respond, and where they fail. It combines three things: request tracing, time-series metrics, and token-level cost accounting.
Without it, every question about your AI spend is a guess. With it, questions like “which feature is burning the budget” and “did last night’s prompt change make responses slower” become queries you run in seconds.
Why AI APIs are harder to observe than normal endpoints
A conventional web request is easy to monitor: status code, duration, payload size. An LLM call breaks all three assumptions. It returns HTTP 200 even when the answer is wrong. Its duration depends on how many tokens it chose to generate, not on how much work your server did. And its cost is variable — the same endpoint can bill you a fraction of a cent or several cents depending on what the model decided to write.
That last point is the one that catches teams out. With a normal API, a traffic spike and a cost spike are the same event. With an LLM API, cost is driven by token volume, which is driven by prompt design, retrieved context size, and how verbose the model decides to be. A feature that is barely used can dominate the bill. Observability is how you find out before the invoice does.
The five signals worth capturing
You can log everything, but only a handful of fields change decisions. Capture these on every call:
| Signal | What it tells you | Typical use |
|---|---|---|
| Model + provider | Which endpoint actually answered | Routing audits, A/B comparisons |
| Input / output tokens | Volume, and therefore cost | Budgets, per-feature attribution |
| Time to first token (TTFT) | Perceived responsiveness | UX tuning, streaming health |
| Total latency | Wall-clock duration | Timeouts, retry policy tuning |
| Status / error class | 429, 5xx, timeout, content filter | Reliability and capacity planning |
Two more fields are cheap to add and disproportionately useful: a request ID so you can trace one user action across multiple model calls, and a feature or route tag so you can attribute spend to the part of the product that caused it. Without the tag, all your cost data collapses into one undifferentiated number.
Instrumenting a call
The good news is that OpenAI-compatible APIs return token usage in the response, so instrumentation is a wrapper rather than a rewrite. Here is the minimal version that captures everything in the table above:
import time, logging, json
log = logging.getLogger("llm")
def observed_call(client, *, feature, model, messages, **kw):
t0 = time.perf_counter()
ttft = None
try:
resp = client.chat.completions.create(model=model, messages=messages, **kw)
ttft = time.perf_counter() - t0 # non-streaming: TTFT ~= total
u = resp.usage
log.info(json.dumps({
"event": "llm_call",
"feature": feature, # cost attribution tag
"model": resp.model, # what actually answered
"in_tokens": u.prompt_tokens,
"out_tokens": u.completion_tokens,
"ttft_ms": round(ttft * 1000, 1),
"total_ms": round((time.perf_counter() - t0) * 1000, 1),
"status": "ok",
}))
return resp
except Exception as e:
log.warning(json.dumps({
"event": "llm_call",
"feature": feature,
"model": model,
"total_ms": round((time.perf_counter() - t0) * 1000, 1),
"status": type(e).__name__, # RateLimitError, APITimeoutError, ...
}))
raise
Two details make this worth more than a naive log line. First, the model is read from the response, not from your request — a relay or gateway may route to a different model than you asked for, and you want your cost data to reflect reality. Second, failures are logged with the same shape as successes, so error rates and latency percentiles come from one dataset instead of two.
Latency: measure TTFT, not just the average
Average latency is the most misleading metric in AI products. A chatbot that streams its first token in 400 ms and finishes in 6 seconds has an average of over 3 seconds, yet users perceive it as fast. A batch summarizer that takes 9 seconds and shows nothing until it is done has the same average and feels broken.
Split the metric in two. TTFT governs perceived speed and should be tracked at p50, p95, and p99. Total duration governs throughput, timeouts, and retry behavior. Track them separately and you will stop “optimizing” latency in ways that make the product feel worse.
Streaming also changes what you can measure. When the response arrives as Server-Sent Events, the final usage block may only be present if you explicitly request it, so instrument the stream itself rather than waiting for a single response object. Our streaming and SSE guide covers the wire format and where the usage counts appear.
Token tracking and cost dashboards
Cost is the metric that gets executive attention, so build it properly. Never hard-code prices into your dashboard; store tokens and multiply by a price table you update in one place. Prices change, and a dashboard that silently reports stale numbers is worse than no dashboard.
| Dashboard panel | Breakdown | Question it answers |
|---|---|---|
| Spend over time | Daily, by feature | Are we trending toward a runaway? |
| Cost per request | p50 / p95 by model | Is a prompt change inflating prompts? |
| Token mix | Input vs output tokens | Are we paying for context we do not need? |
| Latency | TTFT and total, p95 | Did the last deploy make us slower? |
| Error rate | By class (429, 5xx, timeout) | Are we capacity-limited or buggy? |
| Top consumers | By user or API key | Who is generating the volume? |
If you build only two of these panels, build spend over time by feature and token mix. The first finds the runaway. The second explains it: input tokens climbing while output stays flat almost always means retrieved context is growing — the classic RAG leak. Our AI API cost reduction guide covers the fixes, from prompt compression to caching and routing.
Error telemetry: 429s are a capacity signal
Error class matters more than error count. A rising 429 rate is not a bug in your code — it is your provider telling you that you are exceeding a rate or quota limit, and it usually precedes a user-visible outage. A rising timeout rate often means a specific model has degraded or your own retry logic is amplifying load. A rising content-filter rate is a product signal about what your users are sending.
Tag each class separately, and alert on the rate of change rather than the absolute number. The handling patterns — exponential backoff with jitter, fallback chains, and request queues — are covered in our guide to AI API rate limits and 429 errors.
Tracing multi-step and RAG requests
Single calls are easy. The interesting failures happen in chains: retrieve documents, re-rank, summarize, then answer. When that pipeline gets slow or expensive, an aggregate metric cannot tell you which stage is responsible.
The fix is a trace: one trace ID per user action, one span per model call, with parent-child relationships. A trace view turns “the assistant got slow” into “the re-ranking call grew from 4 retrieved chunks to 40.” You do not need a heavyweight platform to start — a correlation ID in your logs, plus a table of spans, gets you 80% of the value on day one.
Prompts, privacy, and retention
There is a version of this that goes wrong: a team logs full prompts and completions to make debugging easier, and three months later discovers customer data sitting in a log index with no retention policy. Observability and data protection have to be designed together.
A workable split is to always store metadata and to sample content. Metadata — tokens, latency, model, status, feature tag — carries almost all the operational value and contains no user data. Prompt and completion text is what you need for a narrow class of debugging, so store it for a short window, on a sample, or behind an explicit flag that is off by default. Hash or drop anything that looks like an identifier before it leaves your process.
Retention follows from the same logic. Cost and latency aggregates should live for a year or more, because they are how you spot slow drift. Raw payloads should expire in days. Teams that separate the two find they can answer nearly every operational question without ever keeping a customer’s conversation on disk.
A minimal event schema you can standardise on
Whichever backend you use, agree on one event shape. It makes dashboards reusable, makes switching providers a config change, and stops the slow drift into five incompatible log formats:
{
"event": "llm_call",
"trace_id": "a41f9c2e", // one per user action
"span_id": "span-3", // one per model call in the chain
"feature": "chat.support", // cost attribution tag
"model": "gpt-4o-mini", // from the response, not the request
"provider": "relay", // who actually served it
"in_tokens": 1840,
"out_tokens": 212,
"ttft_ms": 412,
"total_ms": 2317,
"status": "ok", // or RateLimitError, APITimeoutError
"streamed": true,
"ts": "2026-09-16T15:55:02Z"
}
Six of those fields drive every chart in this article. The other six exist so that when something goes wrong at 3 a.m. you can go from an alert to the exact request without guesswork. Build the schema once, keep it boring, and resist the urge to add fields that only one dashboard reads.
Alerts that actually catch runaways
- Daily spend above a rolling baseline. Compare today to the trailing seven-day median, not to a fixed number — traffic grows.
- Cost per request drifting up. A prompt or context change that doubles average input tokens is invisible until you chart it per request.
- p95 TTFT regression after a deploy. Attach the deploy marker to your charts and the correlation is immediate.
- 429 rate above a threshold. Treat it as a capacity warning, not an error to be retried silently.
- Any single API key exceeding a share of total volume. Usually a runaway script, a leaked key, or an accidental loop.
All five are cheap to compute from the log line above. None of them require a dedicated observability vendor, which matters because the fastest path to LLM observability is usually structured logs plus one dashboard, not a new platform.
Build or buy?
Start with structured logs and one dashboard. That covers cost attribution, latency percentiles, and error classes — the three questions that come up in every review. Reach for a dedicated tracing platform when you have multi-step agents, need prompt-level diffing across versions, or want evaluators wired into the same view as traces.
Whichever route you take, keep the schema yours. If your log fields are generic — feature, model, tokens, latency, status — you can swap backends without re-instrumenting, and you can point the same pipeline at a new provider by changing a base URL. That portability is the point: a unified, OpenAI-compatible endpoint means observability data from every model lands in one place, with one schema. qoraapi.com is one such relay, exposing many models behind a single OpenAI-compatible API.
Frequently asked questions
What is the difference between LLM observability and LLM monitoring?
Monitoring watches numbers you already decided matter — error rate, uptime, spend. Observability is the ability to ask new questions of the raw data, such as why one feature costs five times more than another. Monitoring is a dashboard; observability is the trace and log detail behind it.
How do I track token usage if I stream responses?
Count what you can measure directly: measure TTFT from the first chunk and total duration at the end, and record usage when the stream emits its final usage block. If your endpoint does not return usage on streamed responses, estimate with a tokenizer and reconcile against the provider’s monthly usage report to catch drift.
Do I need a third-party observability platform?
Not to start. A structured JSON log line per call, shipped to whatever log store you already run, plus one dashboard panel for spend-by-feature, answers most questions. Adopt a platform when you have multi-step traces or need prompt versioning and evaluation in the same view.
How do I attribute cost to individual users?
Tag every call with your own user or API key identifier, then aggregate tokens by that tag and multiply by your price table. This is also your best abuse detector: a single key whose share of total tokens jumps overnight is usually a leaked credential or an infinite loop, not organic growth.
Conclusion
LLM observability comes down to one discipline: log every call with the same five fields, keep prices in one updatable table, and chart cost and latency by feature rather than in aggregate. Do that and you gain the ability to explain your AI bill instead of merely paying it — plus an early-warning system for the prompt changes, context leaks, and rate limits that turn a healthy product into an expensive one.
Start with the log wrapper above, add the two dashboard panels that matter most, and read the cost reduction guide alongside the 429 handling guide once your data starts pointing at a problem.
Related reading
- Evaluating and Benchmarking AI Models Before You Ship
- Metering and Billing AI Usage Per User: A Practical SaaS Guide
- Load Testing LLM Apps: Throughput, TTFT, and Concurrency
- Prompt Management and Versioning in Production
- Text-to-SQL: Letting Users Query Your Database with AI
- How to Choose a Vector Database for RAG
- Managing the Context Window: Truncation, Summarization, and Sliding Windows









