Spanlens
About
Open source LLM observability and monitoring. Drop-in proxy for OpenAI, Anthropic, and Gemini with request logging, cost tracking, and agent tracing. Self-host with one Docker command. MIT.
Details
- Author
- spanlens
- GitHub stars
- 9
- Downloads
- 472
- Categories
- Developer Tools
Jump to
- Helicone was acquired and its roadmap is uncertain
- Langfuse is powerful but complex to set up and expensive to scale
- The audit log (Settings → Audit log) records every membership / role / invitation event with actor + timestamp
- Dashboard: http://localhost:3000
- API / proxy: http://localhost:3001
Setting up with Highlight
This MCP is not yet compatible with Highlight’s one-click setup. However, you can still use it with Highlight by following these steps:
- Download and install Highlight from highlightai.com/download
- Navigate to the plugins tab and select "Add Custom Plugin"
-
Configure the plugin with the settings below
Plugin Name
SpanlensCommand (node, npx, python, etc.)Please refer to the README for specific instructions on how to obtain API keys or other required environment variables.
- Enable "Start Automatically" if you want the plugin to start when Highlight launches
From the repository
The README includes setup instructions such as npx @spanlens/cli init.
Claude Desktop / Cursor
Paste into your MCP client config file to install this server.
{
"mcpServers": {
"spanlens": {
"Spanlens": {
"command": "npx",
"args": [
"@spanlens/cli",
"init"
]
}
}
}
}
McpServers
{
"Spanlens": {
"command": "npx",
"args": [
"@spanlens/cli",
"init"
]
}
}
Spanlens
Open-source LLM observability and monitoring. Record every OpenAI / Anthropic / Gemini / Mistral / OpenRouter / Groq / DeepSeek / xAI / Cohere / Azure OpenAI / Ollama call with one line of code. Plugs into Vercel AI SDK, LangChain, and LlamaIndex too. Query the same data directly from Cursor, Claude Desktop, or Continue via the bundled MCP server. Get cost, latency, tokens, traces, anomalies, PII scan, and model-swap suggestions out of the box. Self-hostable. MIT.
> ⭐ If Spanlens is useful to you, please star the repo. It takes a second and helps other developers find it.
> Hosted: spanlens.io · npm: @spanlens/sdk · PyPI: spanlens · CLI: @spanlens/cli · MCP: @spanlens/mcp-server · Status: status.spanlens.io · Changelog: spanlens.io/changelog
---

> Live demo (no signup): spanlens.io/demo/requests


---
Why Spanlens?
- Helicone was acquired and its roadmap is uncertain.
- Langfuse is powerful but complex to set up and expensive to scale.
- Spanlens ships the 20% of features that cover 80% of real production needs. You get request log, cost tracking, agent tracing, anomaly detection, PII scanning, and prompt versioning with a clean UI, a two-minute setup, and pricing that doesn't punish growth.
| | Spanlens | Langfuse Pro | Helicone |
|---|---|---|---|
| Open source | ✅ MIT | ✅ MIT | ✅ MIT |
| Self-hostable | ✅ Docker one-liner | ✅ | ✅ |
| Free tier | 50K req/mo | 50K events/mo | 10K req/mo |
| Team plan (1M req/mo) | $149/mo | $271/mo | ~$200/mo |
| Agent tracing | ✅ | ✅ | ⚠️ limited |
| LLM-as-judge evals | ✅ | ✅ | ❌ |
| PII + injection scan | ✅ | ❌ | ❌ |
| Model recommendations | ✅ | ❌ | ❌ |
| Prompt A/B experiments | ✅ | ✅ | ❌ |

Predictable bills, no quota cliff. Free hits a hard 429 at 50K requests so a runaway loop in dev can't cost you money. Paid plans use a soft limit with authorized overage (Pro: +$8 / 100K, Team: +$5 / 100K) up to a hard cap you control, so a traffic spike charges you fairly instead of dropping requests.
Seats: Free 1 · Pro 3 · Team 10 · Enterprise unlimited. Unlimited projects on every paid tier.
---
⚡ Quick start in 30 seconds
TypeScript / JavaScript (Next.js)
npx @spanlens/cli init
The wizard:
1. Installs @spanlens/sdk with your package manager (npm / pnpm / yarn / bun)
2. Writes SPANLENS_API_KEY to .env.local
3. Rewrites every new OpenAI({ apiKey, baseURL }) into createOpenAI()
Paste your Spanlens API key once, confirm two prompts, done. Your LLM calls are now flowing through the Spanlens proxy and visible in www.spanlens.io/requests.
Manual TypeScript setup
import { createOpenAI } from '@spanlens/sdk/openai'
const openai = createOpenAI() // reads SPANLENS_API_KEY, uses Spanlens proxy baseURL
Python
pip install "spanlens[openai]"
from spanlens.integrations.openai import create_openai
client = create_openai() # reads SPANLENS_API_KEY from env
res = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Hello"}],
)
For agent tracing in Python (multi-step, async, tool calls) see the Python SDK README.
Framework integrations
Already using an orchestration framework? Plug Spanlens in as a callback. No code rewrites.
Vercel AI SDK (Next.js / edge friendly)
import { SpanlensClient } from '@spanlens/sdk'
import { createSpanlensTracker } from '@spanlens/sdk/vercel-ai'
const tracker = createSpanlensTracker({
client: new SpanlensClient({ apiKey: process.env.SPANLENS_API_KEY! }),
modelName: 'gpt-4o',
})
await generateText({
model: openai('gpt-4o'),
messages,
onStepFinish: tracker.onStepFinish,
onFinish: tracker.onFinish,
})
LangChain JS / LangGraph
import { createSpanlensCallbackHandler } from '@spanlens/sdk/langchain'
const handler = createSpanlensCallbackHandler({ client })
await chain.invoke({ input }, { callbacks: [handler] }) // LangChain
await graph.invoke({ input }, { callbacks: [handler] }) // LangGraph
LlamaIndex TS
import { Settings } from 'llamaindex'
import { registerSpanlensCallbacks } from '@spanlens/sdk/llamaindex'
const unregister = registerSpanlensCallbacks(Settings, { client })
// ... run queries ... unregister() on shutdown
Python: LangChain — from spanlens.integrations.langchain import SpanlensCallbackHandler. Same BaseCallbackHandler contract, works with chains, LCEL, and LangGraph.
More integrations: AWS Bedrock, CrewAI, Flowise, Instructor, LlamaIndex, OpenAI Assistants, MCP server. Full setup walkthroughs at spanlens.io/docs/integrations.
Ollama (local LLMs) — Ollama runs on your machine, so it does not go through the hosted proxy. Get a ready client with createOllama() and wrap each call with observeOllama() so the span is logged and tagged as Ollama.
import { SpanlensClient } from '@spanlens/sdk'
import { createOllama, observeOllama } from '@spanlens/sdk/ollama'
const spanlens = new SpanlensClient({ apiKey: process.env.SPANLENS_API_KEY! })
const ollama = createOllama() // points at http://localhost:11434/v1
const trace = spanlens.startTrace({ name: 'chat' })
const res = await observeOllama(trace, 'chat', (headers) =>
ollama.chat.completions.create(
{ model: 'llama3.1', messages: [{ role: 'user', content: 'Hello' }] },
{ headers },
),
)
await trace.end({ status: 'completed' })
---
What you see

Every request logged with model, provider, latency, tokens, cost, and full prompt + response body. Filter, search, export. Streaming responses reconstructed automatically.
---
What you get
| Feature | Description |
|---|---|
| Request log | Every LLM call logged with model, tokens, cost, latency, and full request/response body (streaming reconstructed too) |
| Agent tracing | Multi-step workflows as Gantt waterfall span trees with Critical Path highlighted (the longest dependency chain across a fan-out, not just the slowest single span), plus a node-and-edge graph topology view for LangChain / LangGraph callback traces |
| Cost tracking | Per-request cost breakdown with daily rollups and budget alerts. Prompt-cache tokens (cache_read / cache_creation on Anthropic, prompt_tokens_details.cached_tokens on OpenAI) are parsed separately and billed at the discounted rate so you can see actual cache savings, not just sticker price |
| Per-end-user analytics | Tag calls with x-spanlens-user (SDK: withUser() / with_user()) and the /users page shows per-user cost, tokens, errors, models, last seen |
| Anomaly detection | 3σ deviations in latency, cost, or error rate vs. your 7-day baseline, with root-cause hints (token delta, HTTP status breakdown) |
| Alerts | Threshold rules on budget, error rate, and p95 latency. Delivered via Email (Resend), Slack, or Discord webhooks. Evaluated on a 15-minute cron with at-least-once delivery |
| PII + prompt-injection scan | Regex-based detection on request and response bodies; optional per-project blocking (422) for injections; instant alert emails to workspace owner |
| Savings (model recommendations) | The /savings dashboard surfaces calls that match a cheaper model's profile ("Your gpt-4o calls look like classification. Try gpt-4o-mini") with estimated monthly savings. A month-to-date prompt-caching savings card shows the USD you did not pay thanks to discounted cache-read tokens |
| Response caching | Opt in per request with x-spanlens-cache: true (or a TTL in seconds, capped at 24h; SDK: withCache()). An exact-match hit on the same request body returns the stored response without calling the provider, logs the row at zero cost, and is scoped per API key so nothing leaks across keys. Non-streaming, 200-only |
| Email digests & health alerts | A weekly workspace digest (requests, cost with week-over-week change, top models, anomalies) lands every Monday, and a data-silence alert emails admins when a workspace that was sending traffic suddenly goes quiet for 24 hours, so a broken key or dropped env var is caught before it becomes silent churn |
| Prompt versioning + A/B | Register prompt templates, run traffic-split experiments, compare versions side by side on latency / cost / error rate — reported with Welch's t-test on latency and cost plus a z-test on error rate, so you get statistical significance rather than just averages |
| Prompts Playground | Execute any prompt version with variable injection directly in the dashboard to see real cost and response before shipping |
| Datasets | Reusable (input, expected_output) test sets you can rerun against any prompt version or model. Upload CSV / JSONL files directly from the dashboard or POST programmatically. Powers offline evals and regression checks |
| Evals & Experiments | Build LLM-as-judge evaluators (judge with OpenAI, Anthropic, or Gemini — pick the cheapest/best for the criterion) with rubric anchors and confidence intervals on pass rates. Supports pairwise A vs B mode for head-to-head prompt comparison, agent trajectory mode for scoring whole traces (not just final text), and judge-result caching keyed by (evaluator, response) to skip duplicate LLM calls on re-runs. Human annotation is queued for sampling, with Pearson r (numeric) or Cohen's κ (categorical) measuring judge-human agreement |
| OpenAPI 3.0 spec + Swagger UI | Machine-readable spec at GET /api/v1/openapi.json and interactive explorer at GET /api/v1/docs. A drift test enforces that every router stays documented |
| Saved filters | Pin frequently used request-log queries (model, status, cost range, tags) and share them across the workspace |
| Outbound webhooks | Subscribe to request.created / trace.completed / alert.triggered events. Payloads are HMAC-signed via X-Spanlens-Signature: sha256=… so receivers can verify origin |
| OpenTelemetry / OTLP ingest | POST /v1/traces accepts OTLP/HTTP JSON exports using the gen_ai. semantic conventions, so you can drop in any OTel SDK without writing Spanlens-specific code |
| Provider-key security | Weekly digest emails for stale (unused 90d+) provider keys + daily GitGuardian leak scan against your active keys, with per-key scan history |
| Privacy controls | Per-request x-spanlens-log-body: full \| meta \| none header lets customers shrink what Spanlens stores (drop bodies, drop end-user IDs) without dropping the request itself |
| Data export | CSV or JSON download for requests, traces, anomalies, and flagged security events (GET /api/v1/exports/{requests,traces,anomalies,security}?format=csv). Streamed server-side for 100K+ row pulls so big exports don't OOM |
---
Team & workspaces
Spanlens is multi-user out of the box. Invite teammates, hand out roles, and spin up a separate workspace per client.
- Roles are admin (members + billing), editor (data + settings), and viewer (read-only). The last admin is protected against demotion / removal.
- Email invitations have a 7-day expiry with sha256-hashed tokens. Sent via Resend when RESEND_API_KEY is set; falls back to console-logging the accept URL for local dev.
- The pending-invitation banner surfaces unaccepted invites at the top of the dashboard, even if the recipient never opened the email. Accept joins and auto-switches the active workspace; Decline removes the row.
- Multi-workspace lets you switch between workspaces from the sidebar (sb-ws cookie + hard reload so middleware re-resolves scope). Useful for consultants juggling multiple clients or one team running prod / staging as separate workspaces.
- Two-step onboarding sends new signups to /onboarding: name your workspace, answer two optional survey questions, done. Invitees get a short-circuited variant where Accept skips workspace creation entirely.
- The audit log (Settings → Audit log) records every membership / role / invitation event with actor + timestamp.
---
Monorepo structure
Spanlens/
├── apps/
│ ├── web/ — Next.js 16 dashboard (www.spanlens.io)
│ └── server/ — Hono LLM proxy + REST API (api.spanlens.io)
├── packages/
│ ├── sdk/ — @spanlens/sdk: TypeScript / JavaScript SDK
│ ├── sdk-python/ — spanlens (PyPI): Python SDK
│ ├── cli/ — @spanlens/cli: npx wizard for 1-command setup
│ └── mcp-server/ — @spanlens/mcp-server: MCP server for Cursor / Claude Desktop / Continue
├── clickhouse/
│ ├── migrations/ — ClickHouse schema for the requests log table
│ └── apply.ts — pnpm ch:migrate runner (idempotent)
└── supabase/
├── migrations/ — Postgres schema (orgs, projects, keys, prompts, … — RLS-gated)
└── seeds/ — model_prices.sql etc.
Storage split
Spanlens uses two databases, each for what it's good at.
Supabase (Postgres) handles transactional, relational, RLS-gated data: organizations, projects, members, API + provider keys, prompts, datasets, alerts, billing, audit log.
ClickHouse handles the high-volume append-only requests table (every LLM call). All reads go through apps/server/src/lib/requests-query.ts, which auto-injects the organization_id filter and the per-plan retention window (free=14d / pro=90d / team=365d). If ClickHouse is briefly unreachable, the proxy falls back to a Supabase queue (requests_fallback) that a cron replays every 5 minutes with no log loss.
Projects, unified keys, and headers
- A workspace can hold multiple projects (e.g. dev / staging / prod, or one per app). Each project gets its own quota slice, provider keys, and prompt namespace.
- Unified API keys give you one sl_live_ key per project that is provider-agnostic. Spanlens infers the provider from the request path (/proxy/openai/, /proxy/anthropic/, /proxy/gemini/, /proxy/mistral/, /proxy/openrouter/, /proxy/groq/, /proxy/deepseek/, /proxy/xai/, /proxy/cohere/, /proxy/azure/), so you only need one Spanlens key even if you call multiple model vendors.
- X-Spanlens-* headers (set automatically by the SDK helpers withUser(), withSession(), withPromptVersion(), withLogBody()): tag a request with end-user / session IDs, link it to a prompt-version experiment, or limit how much body Spanlens stores. Full list in /docs/proxy.
- Streaming safety ensures proxy responses are gracefully closed at 290s with a truncated=true flag in the log, so long streams never silently disappear.
---
Local development
Prerequisites: Node 20+, pnpm 10.33.0+, Docker (for local Supabase), Vercel CLI optional.
```bash
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.





