LLM Observability Tools — The $4k/month Token Leak We Caught at 3am
Stop guessing why your LLM costs are exploding.
20+ years shipping production ML systems and the infrastructure behind them. Lessons pulled from things that broke in production.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- Token Tracking Without per-request token accounting, you're flying blind on costs. We found a 40% token waste in a single misconfigured retry loop.
- Latency Breakdown LLM calls aren't just model inference. Prompt serialization, embedding lookups, and context window management can add 800ms of hidden latency.
- Cost Attribution Tag every span with
user_id,model, andprompt_templateto trace cost spikes to specific features or tenants. - Error Budgets Rate limits and context length errors are the new 503s. Track them with custom metrics and alert on error budget depletion.
- Span Linkage A single user request can spawn 10+ LLM calls, 3 vector DB queries, and 2 re-ranking steps. Distributed tracing is non-negotiable.
- Prompt Drift The same prompt template can produce wildly different token counts after a model update. Monitor token usage per template version.
Think of LLM observability like having a fuel gauge and a mechanic for your chatbot. Without it, you're driving blind — you don't know how much each conversation costs, which parts are slow, or why the car suddenly stalls when too many people ask the same question. This article gives you the dashboard and the diagnostic tools.
Three weeks ago, our recommendation engine started burning through $4,000/month in OpenAI API costs with no change in traffic. The p99 latency jumped from 2s to 8s. Users complained of timeouts. Our Grafana dashboard showed a flat line — no spikes, no errors. The system was 'working', just slower and more expensive. That's the lie LLM observability is supposed to catch.
Most tutorials hand you a tracing library and call it a day. They show you a pretty waterfall chart of one LLM call and tell you to instrument your app. They don't tell you that the real problems live in the gaps: retry storms from rate limits, token waste from prompt caching, cost attribution to a specific user who's gaming your system. They don't tell you that OpenTelemetry spans are only useful if you tag them with the right metadata.
This guide covers what I wish I'd known before that 3am page. We'll walk through production-grade LLM observability using OpenTelemetry and OpenLIT, with real code for tracing, metrics, and cost tracking. I'll show you the exact config that caught our token leak, the debug steps when your dashboard shows nothing useful, and the one metric you must alert on before your next bill arrives.
How LLM Observability Actually Works Under the Hood
LLM observability is not just API monitoring. A single user request can trigger a chain: prompt serialization, embedding lookup in a vector DB, context window management, multiple LLM calls (for reasoning, tool use, or re-ranking), and post-processing. Each step has its own latency, cost, and failure modes.
The core abstraction is the span. OpenTelemetry defines a span as a single operation with a start and end time, plus attributes. For LLM apps, you need spans at multiple levels: the user request, each LLM call, each vector DB query, and each tool invocation. The tricky part is linking them — you need a trace ID that propagates across service boundaries.
OpenLIT auto-instruments popular LLM libraries (OpenAI, LangChain, Anthropic) by monkey-patching the client's __call__ method. It creates a span for each LLM request, adds attributes like model, prompt_tokens, completion_tokens, and total_tokens, and exports them via OTLP. But auto-instrumentation only gets you so far. You still need to manually instrument your business logic: the prompt assembly, the retry logic, the caching layer.
What the docs don't tell you: span attributes are the difference between a useless waterfall chart and a cost-attribution dashboard. Tag every span with user_id, prompt_template, feature_name, and tenant_id. Without those, you can't answer 'which user is costing me $500/day?'
Practical Implementation: Setting Up OpenTelemetry + OpenLIT for Production
Let's walk through a production-ready setup. We'll use OpenLIT for auto-instrumentation of OpenAI and LangChain, then add manual instrumentation for business logic. We'll export traces and metrics to Grafana Cloud via OTLP.
First, install dependencies. Use pinned versions to avoid breakage. We learned this the hard way when OpenLIT 0.4.0 broke our LangChain integration.
Next, configure the OpenTelemetry SDK. The key decision is the exporter endpoint. For Grafana Cloud, you need the OTLP endpoint and a token. Never hardcode credentials — use environment variables.
Then, initialize OpenLIT. It will automatically patch openai.ChatCompletion.create and LangChain's LLMChain.run. But you still need to wrap your main request handler in a trace to link all spans together.
Finally, add custom metrics. The auto-instrumentation gives you latency histograms and token counts, but you need business metrics: requests per user, cost per template, error rate by model.
OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS environment variables. This makes it easy to switch between dev, staging, and production.OTEL_EXPORTER_OTLP_ENDPOINT environment variable. The SDK silently defaulted to localhost:4317, so all traces went to a non-existent collector. We lost 3 days of data. Add a startup check: if not os.getenv('OTEL_EXPORTER_OTLP_ENDPOINT'): raise RuntimeError('OTLP endpoint not set').When NOT to Use OpenTelemetry for LLM Observability
OpenTelemetry is the standard, but it's not always the right choice. Here are three scenarios where you should consider alternatives.
1. You need real-time cost tracking at the sub-second level. OpenTelemetry's batch span processor exports every 5 seconds by default. If you need to enforce a per-user token budget in real time, you need a streaming approach. Consider using a middleware that sends token counts to a Redis counter or a streaming platform like Kafka.
2. You're running LLMs on a massive scale (10k+ requests/second). The OpenTelemetry collector can become a bottleneck. We saw the collector's CPU spike to 80% at 5k req/s. Consider sampling: use the tail-based sampler to keep only traces with errors or high latency.
3. You need deep prompt-level debugging. OpenTelemetry spans are not designed to store full prompts and responses. If you need to replay a specific conversation for debugging, store the prompts and responses in a separate store (e.g., S3 or a database) and link them to the trace via a trace ID.
For most teams, OpenTelemetry is the right choice. But know its limits.