Migrate from Langfuse · 2026
Spanlens covers the same observability surface as Langfuse: traces, generations, prompts, evals, datasets. The integration is one line at the SDK level, so you can switch a single service in under 30 minutes. This page walks the swap end to end and is honest about the four spots where the two products diverge.
Why teams switch
- Drop-in proxy. Spanlens does not require a callback handler on every chain. Point the OpenAI / Anthropic / Gemini base URL at the proxy and every call is logged, even from code paths you do not own (third-party libraries, background workers, MCP servers).
- One key per project. No public / secret key split. The same
sl_live_*key authenticates the proxy and the REST API. - Provider keys stored server-side. Your real OpenAI / Anthropic key is AES-256-GCM encrypted in our database and never lives in client code or env files. Langfuse stores them in your app.
- Simpler pricing for SaaS. Spanlens bills on logged requests and does not charge separately for evals or observations.
See the full feature comparison on spanlens vs langfuse.
Step 1. Install the Spanlens SDK
pnpm add @spanlens/sdk
# or: npm install @spanlens/sdktsStep 2. Swap the client constructor
The Langfuse OpenAI / Anthropic wrappers become a single Spanlens helper per provider. Same method signatures, same response types, drop-in.
OpenAI
Before (Langfuse):
import { OpenAI } from 'openai'
import { observeOpenAI } from 'langfuse'
const openai = observeOpenAI(new OpenAI())tsAfter (Spanlens):
import { createOpenAI } from '@spanlens/sdk/openai'
const openai = createOpenAI() // reads SPANLENS_API_KEY from envtsAnthropic
Before (Langfuse, manual span wrapping):
import Anthropic from '@anthropic-ai/sdk'
import { Langfuse } from 'langfuse'
const langfuse = new Langfuse()
const anthropic = new Anthropic()
const generation = langfuse.generation({ name: 'reply', model: 'claude-haiku-4-5' })
const msg = await anthropic.messages.create({ ... })
generation.end({ output: msg.content })tsAfter (Spanlens):
import { createAnthropic } from '@spanlens/sdk/anthropic'
const anthropic = createAnthropic()
const msg = await anthropic.messages.create({ ... })
// logged automatically, no end() calltsLangChain / LangGraph
Replace the Langfuse callback handler with the Spanlens one. The contract is the same.
// Before
import { CallbackHandler } from 'langfuse-langchain'
const handler = new CallbackHandler()
// After
import { SpanlensClient } from '@spanlens/sdk'
import { createSpanlensCallbackHandler } from '@spanlens/sdk/langchain'
const client = new SpanlensClient()
const handler = createSpanlensCallbackHandler({ client })
await chain.invoke({ input }, { callbacks: [handler] })tsSee LangGraph integration for the full graph-topology mapping (chain.* spans become the LangGraph node tree).
Step 3. Update environment variables
| Langfuse | Spanlens | Notes |
|---|---|---|
LANGFUSE_PUBLIC_KEY | none | Spanlens uses a single secret key. |
LANGFUSE_SECRET_KEY | SPANLENS_API_KEY | Issued per project from /projects. Format sl_live_*. |
LANGFUSE_HOST | not needed on cloud | Self-hosting? Pass baseURL to createOpenAI(). |
OPENAI_API_KEY | register in dashboard | Add it once under + Add provider key. It stays server-side. |
Step 4. Data model mapping
Most concepts map 1:1. Two diverge: Spanlens does not have a separate generation entity (LLM calls are logged as requests), and Spanlens does not have first-class sessions; conversations are grouped via the x-spanlens-session header instead.
| Langfuse | Spanlens | Notes |
|---|---|---|
| Trace | Trace | Same shape. name, metadata, status fields all map. |
| Observation (Span / Tool / Retrieval) | Span | Spanlens span types are llm, tool, retrieval, embedding, custom. |
| Observation type Generation | Request (LLM call) + Span (link) | LLM calls live in their own column store (ClickHouse) for fast aggregation. Spans link via request_id. |
| Score | Eval result | Spanlens has runs (one execution) and results (one score). Same 0..1 normalization. |
| Session | x-spanlens-session header | No separate Session table. Use the header on each call and filter by session_id in /requests. |
| User | x-spanlens-user header | Same pattern. The /users view aggregates per user_id. |
| Prompt | Prompt + Prompt Version | Versions are immutable. Linked via x-spanlens-prompt-version header (use withPromptVersion() helper). |
| Dataset / Item | Dataset / Dataset Item | 1:1. Items accept either variables or messages shape. |
Step 5. Run both side-by-side during the cutover
Spanlens does not conflict with Langfuse: they instrument independently. The safest rollout is to leave the Langfuse callback in place, add Spanlens for a single service, and compare for a day or two.
// Both active
import { createOpenAI } from '@spanlens/sdk/openai'
import { observeOpenAI } from 'langfuse'
const openai = observeOpenAI(createOpenAI())
// Every call is now logged to BOTH backends. Pick whichever dashboard
// you trust, then remove the other when you are confident.tsOnce Spanlens looks right in your dashboard for a representative slice of traffic, delete the Langfuse wrapper, drop the env vars, and uninstall the SDK.
What does not migrate 1:1
- Public-key client-side ingestion. Spanlens does not have a publishable key. All ingest authenticates with the project secret key. If you were sending events directly from a browser, route them through your backend.
- Self-hosted historical data. Spanlens does not import Langfuse history. Existing traces stay in Langfuse for read-only access; new traces flow into Spanlens from cutover onwards.
- Prompt template variables in
{{var}}syntax. Spanlens prompts store the raw template and a separatevariablesJSON list. The template syntax is whatever you choose to render in your app.
Verify the cutover
- Make any call from the migrated service.
- Open /requests. A new row should appear within a few seconds with model, tokens, cost, latency, full bodies.
- For traced workflows, check /traces. The span tree should mirror what Langfuse showed, with the LangGraph node topology preserved if you use the callback handler.
- Compare token counts and cost against Langfuse for one day. Differences greater than 1% usually indicate a model name not in the price table; email support@spanlens.io and we will seed it same day.
Next: Data model for the full schema, or LangGraph integration for graph-aware tracing.