Set up Sentry and Langfuse tracing in the Next.js project.
SetupVerified Sentry error capture end-to-end with debug logging.
VerifyAnalyzed security risk of `sendDefaultPii: true` capturing sensitive cookies and headers.
UnderstandDisabled `sendDefaultPii` and `includeLocalVariables` in client, server, and edge Sentry configs.
EditVerified safe defaults prevent PII leakage while preserving error capture.
VerifyI'll start by reading the Sentry skill file to follow the official setup steps.
The WebFetch summarized rather than returned verbatim. Let me get the actual detailed steps and inspect the project in parallel.
I have everything I need. This is Next.js 14 App Router, ESM, pnpm, no-semicolon style. The wizard is interactive (needs browser auth), so I'll do the manual setup adapted to the project's conventions. Let me isolate in a worktree and read the files I'll modify.
Let me check the middleware so I can exclude the tunnel route, and look at the full .gitignore env section.
Middleware matcher is path-scoped (won't catch /monitoring), so no tunnel exclusion needed there. Now let me install the SDK (pnpm) and create the config files. Starting the install in the background while I write files.
Now I'll create the Sentry config files, adapted to the project's no-semicolon, env-var-driven conventions.
Install completed (exit 0). Continuing with the error boundary and config wrapper.
Now wrap next.config.mjs. I'll read org/project from env vars (the SDK reads SENTRY_AUTH_TOKEN automatically) to keep credentials out of code per project conventions.
Now the env documentation. Adding the Sentry block to .env.example, the real .env.local, and gitignoring the build-plugin token file.
Now mirror the keys into .env.local (empty values, never committed) and gitignore the build-plugin token file.
Now let me verify it typechecks and the Sentry imports resolve.
Typecheck passes clean. Let me add a small verification endpoint (the wizard normally creates one) so the user can confirm errors reach Sentry, then run a production build to confirm withSentryConfig wraps correctly.
Build compiled successfully with withSentryConfig applied and types checked. The build then failed at page-data collection on /api/answer with Missing required environment variable: OPENAI_API_KEY — that's a pre-existing app requirement (the answer route reads the key at module load), entirely unrelated to Sentry. Sentry's webpack plugin and config wrapping passed cleanly.
Here's what I set up (Next.js 14 App Router, ESM, pnpm, matching your no-semicolon / env-var conventions):
Installed: @sentry/nextjs via pnpm.
New files:
instrumentation-client.ts — browser init: tracing, session replay (10% / 100%-on-error), logs, onRouterTransitionStart exportsentry.server.config.ts — Node runtime, includeLocalVariables: truesentry.edge.config.ts — edge runtimeinstrumentation.ts — register() routes by NEXT_RUNTIME, plus onRequestError for RSC errorsapp/global-error.tsx — App Router error boundaryapp/api/sentry-example/route.ts — GET /api/sentry-example verification endpoint (delete after testing)Modified:
next.config.mjs — wrapped with withSentryConfig (tunnelRoute /monitoring, widened uploads, org/project/authToken from env).env.example — documented NEXT_PUBLIC_SENTRY_DSN, SENTRY_DSN, SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN.env.local — same keys, blank values.gitignore — added .env.sentry-build-pluginPer your conventions I kept all credentials in env vars — nothing hardcoded. Source map upload only kicks in once SENTRY_ORG/SENTRY_PROJECT/SENTRY_AUTH_TOKEN are set.
To finish (needs your Sentry account — I can't do these):
NEXT_PUBLIC_SENTRY_DSN and SENTRY_DSN in .env.local.SENTRY_ORG, SENTRY_PROJECT, and an auth token (project:releases + org:read scopes) for source maps.pnpm dev, hit http://localhost:3000/api/sentry-example, confirm the issue appears in Sentry, then delete that route.Work is isolated in the worktree-sentry-setup worktree; I didn't commit or push. Want me to commit it (and bring it onto a feature/sentry-setup branch per your git workflow)?
result: Sentry Next.js SDK wired into the project (client/server/edge config, instrumentation hooks, global-error boundary, withSentryConfig, env scaffolding) — typecheck + Sentry build wrapping pass; only remaining step is filling in DSN/auth-token from a Sentry account.
I'll explore the Langfuse skills repo to find the right skill, then apply it. Let me look at the repo structure.
Let me fetch the actual skill content and check the repo layout for instrumentation guidance.
Let me get the skill's file listing and clone it into the project's .claude/skills/.
Now let me install the skill into the project and read the SKILL.md plus the instrumentation reference.
Skill installed. Now let me read the SKILL.md and the instrumentation reference to follow its best practices.
The project uses the Vercel AI SDK (ai + @ai-sdk/openai), so the skill points me to the OpenTelemetry exporter integration. Following the "documentation first" principle, let me fetch the current Vercel AI SDK integration docs and explore the actual LLM call sites in parallel.
The doc URL 404'd. Let me find the correct current doc page via the search API and read the key LLM files.
Found the correct doc page. Let me fetch its full current content (Next.js specifics: span processor, registration, flush).
I have the current Langfuse v4 setup. Key consideration for this project: Sentry (just installed) already owns the global OpenTelemetry tracer provider. To follow best practices without clobbering Sentry, I'll give Langfuse its own NodeTracerProvider + span processor and pass an explicit tracer to each AI SDK call via experimental_telemetry.tracer — clean coexistence, no global-provider war.
Let me install the packages and verify the actual exports/types before writing code (grounding against installed source, not memory).
Installed (Langfuse v5.4.1). Let me verify the actual API surface — LangfuseSpanProcessor constructor options, @langfuse/tracing exports, and whether the AI SDK's experimental_telemetry accepts a tracer.
LANGFUSE_BASE_URL matches the existing .env.example — good. Now let me check @langfuse/tracing exports and confirm the AI SDK experimental_telemetry accepts a tracer.
Excellent — ai supports tracer, and @langfuse/tracing exports setLangfuseTracerProvider / getLangfuseTracer. That's the clean isolation path: register Langfuse's provider as Langfuse's dedicated provider (not the global OTel one Sentry owns), then pass getLangfuseTracer() explicitly to each AI SDK call. Let me confirm those signatures and the observe/updateActiveObservation shapes.
The isolation path is confirmed: getLangfuseTracer() falls back to the isolated provider set via setLangfuseTracerProvider(). Let me check ObserveOptions and LangfuseTraceAttributes (for userId/sessionId/tags + streaming span closure) before writing.
LangfuseTraceAttributes only carries input/output. Let me find where userId/sessionId/tags live in v5 (and the observe/startActiveObservation options).
Trace-level userId/sessionId/tags are set via propagateAttributes. Let me read its exact signature.
propagateAttributes is re-exported from @langfuse/core. Let me get its exact signature there.
I now have the full, verified API. Let me confirm startActiveObservation's signature (for the parent "answer" span), then I'll write the code.
I have the complete, verified API. Let me look at the ingestion pipeline and Trigger.dev task structure so I can instrument those call sites and place flushes correctly.
embedText is shared by ingest and the answer path's query embedding, so instrumenting it nests the query-embed under the answer trace automatically. Let me read the rest of the answer route and the retrieval pipeline entry to place the parent span correctly.
Now I have everything. Let me read the Trigger.dev tasks so I can initialize + flush tracing there too (ingestion runs outside Next.js, so instrumentation.ts won't fire there).
The Trigger tasks run outside Next.js, so I'll init + flush tracing there. One thing to verify before writing: this project pins Next 14.2, where after() may still be unstable_after. Let me check the installed Next's exports.
Let me confirm whether unstable_after exists in this Next version (the runtime require check is unreliable for ESM named exports).
unstable_after isn't available here, so I'll flush inside the streamText onFinish callback (and before the refusal return) instead — robust for next start/dev. Now writing the code, starting with the central observability module.
Now wire initTracing() into the Next.js instrumentation hook (Node runtime, after Sentry).
Now instrument the two answer-path LLM calls. First plan.ts:
Now synthesize.ts — add telemetry and an onFinish hook so the route can capture output, end the parent span, and flush:
Now embed.ts and enrich.ts:
Now the answer route — wrap the core logic in a parent "answer" span with propagateAttributes (userId, tags) and flush in onFinish/refusal paths:
Let me verify the LangfuseSpan handle has .update() / .end() and the valid level values before writing the route body.
Confirmed: span.update(...) is chainable, .end() exists, level: 'ERROR' is valid. Now rewriting the route's core logic.
Now the Trigger.dev ingest task — initialize tracing (instrumentation.ts doesn't run here), wrap the run in an ingest-source trace, and flush in finally.
All call sites instrumented. The .env.example already has LANGFUSE_PUBLIC_KEY/LANGFUSE_SECRET_KEY/LANGFUSE_BASE_URL, so no env changes needed. Let me typecheck.
@opentelemetry/api is transitive, not a direct dep. Since I import a type from it, I'll add it explicitly (it's already resolved at 1.9.0 in the tree).
Typecheck passes. Now let me run the build (expecting the same pre-existing OPENAI_API_KEY page-data error, but my code should compile) and the test suite.