On Node.js, "error tracking" and "observability" are no longer the same thing. To see which Prisma query slowed a request, or which outbound API call timed out, you need the trace/log/metric trio — not just Sentry's classic error-capture mode. This post covers a Node.js OpenTelemetry + Sentry observability setup end to end, with real code: the OpenTelemetry core inside @sentry/node 11.x, the Next.js instrumentation.ts split, Prisma spans, sampling, and alert strategy.
💡 Pro Tip: Before flipping the enableOpenTelemetrySetup flag, check whether the project already has an OTel pipeline — leaving both on at the same time produces two separate spans per operation, and the traces never merge.Table of Contents
- The 2026 Observability Minimum (Trace + Log + Metric)
- Why This Changed Now
- The OpenTelemetry Core Inside @sentry/node 11
- The Split Between instrumentation.ts and instrumentation-client.ts
- Wiring Existing OTel Spans to Sentry
- Wiring Prisma/Postgres Queries to Spans
- Sampling and Cost Control
- Setting Up Alerts: What to Alarm On, What Never To
- Setting It Up in a Self-Hosted Scenario
- FAQ
- How do you use OpenTelemetry together with Sentry in Node.js?
- How do I send my existing OTel traces to Sentry?
- How do you set up the instrumentation file in Next.js?
- Sentry or an OTel collector — which do you need?
- Which Node.js version is required?
- Conclusion
- Sources
The 2026 Observability Minimum (Trace + Log + Metric)
Being "observable" in a modern Node.js service means collecting three signals together: traces (a request's journey through the system, made of spans), logs (structured event records), and metrics (counters, histograms, time-series data). Sentry historically started with error capture; OpenTelemetry has collected these three signals under a vendor-neutral standard from the start. In 2026 the intersection of these two worlds is essentially mandatory: an error message alone doesn't show why a request was slow — the span tree does.
Why This Changed Now
As of 2026-09-23, the npm latest tag for @sentry/node is 11.0.0, confirmed by the 11.0.0 release page on the getsentry/sentry-javascript GitHub repo. In this major release, OpenTelemetry is no longer a hidden background detail in the SDK — it's a layer you configure directly: the orchestrion-based channel instrumentation that was opt-in in v10 (instead of import-in-the-middle) is now the default, turning on instrumentation at build and run time — why tracing now works on targets like Vercel/Netlify. Span streaming is now default, and the minimum supported Node version rose to 20.19.0 (the engines field of @sentry/node@11.0.0: >=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0). Check your own version with node -v; if the output falls in one of these ranges, you're compatible.
By contrast, @opentelemetry/sdk-node is still on 0.222.0, within the 0.x band — even though OpenTelemetry JS's trace API/SDK spec is considered stable, the package version hasn't crossed 1.0. This matters: while Sentry jumped a major version (10 to 11), OTel packages are still numbered 0.x — don't read that as a mismatch; the two projects simply move on separate cadences.
The OpenTelemetry Core Inside @sentry/node 11
The biggest architectural change in @sentry/node 11.0.0 is that OpenTelemetry is no longer a hidden implementation detail — it's a selectable mode. The SDK supports three scenarios:
Approach | What It Does | When To Use It |
|---|---|---|
Sentry only (default) | OTel never gets involved; Sentry does its own error/performance capture | No other tracing system exists in the project |
enableOpenTelemetrySetup: true | Sentry sets up its own OTel-compatible tracer provider; spans opened with @opentelemetry/api turn into native Sentry spans | You'll only use Sentry's dashboard, but want to instrument with the OTel API |
enableOpenTelemetrySetup: false + your own pipeline | You set up your own NodeTracerProvider/NodeSDK; Sentry only attaches errors/logs to the active span | You're already sending traces to an OTel collector like Grafana/Datadog/Jaeger |
The migration doc clearly separates these three under "Choosing an OpenTelemetry setup" — decide based on the project's existing infrastructure, and don't leave both on at once.
In the simple scenario (using Sentry's own OTel-compatible setup), the install starts like this:
1npm install @sentry/node --save1// sentry.server.config.ts2import * as Sentry from "@sentry/node";3 4Sentry.init({5 dsn: process.env.SENTRY_DSN,6 enableOpenTelemetrySetup: true,7 tracesSampleRate: 0.2,8 dataCollection: {9 // explicitly turn this category off to preserve v10 behavior10 databaseQueryData: false,11 },12});Watch dataCollection.databaseQueryData. Per the migration doc, sendDefaultPii was removed; dataCollection replaces it, setting each category individually — left unset, it collects broader data than v10: databaseQueryData defaulted to false in v10, true in v11. Since query content (potential PII) is now collected by default, I set the field to false above to preserve the old behavior.
The Split Between instrumentation.ts and instrumentation-client.ts
The most confusing part of setting up Sentry in Next.js is knowing which file runs in which runtime. The official setup tool separates this automatically:
1npx @sentry/wizard@latest -i nextjsThe wizard creates three separate init files and wires them into a single root file:
instrumentation-client.ts: Runs in the browser, initializes the client-side Sentry SDK.sentry.server.config.ts: Runs in the Node.js runtime, sets up server-side error/trace capture.sentry.edge.config.ts: Runs in the edge runtime (middleware, edge routes).instrumentation.ts: Next.js's own framework hook — the root file that imports the three files above depending on the runtime.
Per the migration doc, enableOpenTelemetrySetup defaults to false in most server SDKs, and true in @sentry/nextjs and @sentry/sveltekit. Moving to your own OTel pipeline in Next.js requires explicitly setting this flag to false — otherwise Sentry registers its own tracer provider, and you land in the second setup instead of the third.
Wiring Existing OTel Spans to Sentry
If you already run your own OpenTelemetry pipeline (your own NodeTracerProvider), add Sentry not as a parallel tracing system, but as an error/log source attached to that pipeline. The official way: getOtlpTracesEndpoint(), which derives Sentry's OTLP address and auth headers from your DSN and returns the config for OTLPTraceExporter — not a URL string:
1npm install @opentelemetry/sdk-trace-node @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http1// otel-setup.ts2import * as Sentry from "@sentry/node";3import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";4import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";5import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";6 7const dsn = process.env.SENTRY_DSN!;8const endpoint = Sentry.getOtlpTracesEndpoint(dsn);9 10if (!endpoint) {11 throw new Error("Could not parse the Sentry DSN");12}13 14const provider = new NodeTracerProvider({15 spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter(endpoint))],16});17provider.register();18 19Sentry.init({20 dsn,21 enableOpenTelemetrySetup: false,22 integrations: [Sentry.openTelemetryIntegration()],23});In this scenario we don't set tracesSampleRate — the sampling decision is now the responsibility of the OTel sampler; Sentry just attaches errors/logs to spans that are already being produced.
Wiring Prisma/Postgres Queries to Spans
Seeing that a request is slow isn't enough — you need to know which Prisma query caused it. On the Sentry-native path (setups one and two), Prisma is instrumented out of the box via prismaIntegration; the setup below only applies to your own OTel pipeline, using Prisma's official OTel integration, @prisma/instrumentation:
1npm install @prisma/instrumentation1// otel-setup.ts (in addition to the provider setup above)2import { PrismaInstrumentation, registerInstrumentations } from "@prisma/instrumentation";3 4registerInstrumentations({5 instrumentations: [new PrismaInstrumentation()],6});With this active, the span tree produces: prisma:client:operation (a method you call, like prisma.user.findMany()) opens prisma:engine:db_query (the actual SQL query) beneath it (with spans like prisma:engine:query in between). Prisma's docs note this instrumentation had to be enabled as a preview feature via previewFeatures = ["tracing"] for versions 4.2.0–6.1.0, and works directly in later versions — check the project's Prisma version in package.json and set the schema flag accordingly.
Sampling and Cost Control
Sentry has three separate sampling concepts, and mixing them up either sends too much data and inflates cost, or leaves you seeing no traces at all:
Setting | What It Samples | Default | Note |
|---|---|---|---|
sampleRate | Error events | 1 (all errors) | Works independently of traces |
tracesSampleRate | Performance traces | Off if unset | No trace data is sent at all if unset |
tracesSampler | Performance traces (conditional) | — | Dynamic decision based on operation name/context |
With your own OTel pipeline (the enableOpenTelemetrySetup: false setup above), don't set tracesSampleRate — sampling happens at the OTel Sampler layer, before data reaches the OTLP exporter. Mixing up the two mechanisms and applying limits on both sides makes it untraceable where data actually gets cut. Keep it simple: Sentry-native path → Sentry's tracesSampleRate is the sole authority; own OTel pipeline → the OTel sampler is.
On the Sentry-native path, for a conditional decision instead of a fixed rate, use tracesSampler. A common pattern excludes noisy endpoints like health checks entirely:
1import * as Sentry from "@sentry/node";2 3Sentry.init({4 dsn: process.env.SENTRY_DSN,5 enableOpenTelemetrySetup: true,6 tracesSampler: ({ attributes, inheritOrSampleWith }) =>7 inheritOrSampleWith(attributes?.["url.path"] === "/health" ? 0 : 0.2),8});The trap is specific to v11: since tracesSampler runs before the span starts, Express hasn't matched the route yet — the http.server span's name is just GET, not GET /health. As the migration doc warns, name-based rules fail silently: no error, no warning, just unexpected quota usage. Check the url.path attribute, not the name.
Setting Up Alerts: What to Alarm On, What Never To
After collecting traces and spans, the next step is turning that data into a real alert. Sentry's alert model has three parts: a trigger (when the condition is checked), a filter (which data it applies to), and an action (what happens when met — email, Slack, webhook). Official example scenarios include downtime detection, traffic spikes, and error-threshold breaches.
The practical rule: alarm on thresholds that affect users, not every span. An instant alert for one slow Prisma query is noise; instead, trigger on a crossed p95 latency threshold or a sustained error rate. Don't alarm on one-off, self-correcting anomalies — like a brief cold-start delay during a deploy. Add a time window (say, 5 minutes) and a minimum occurrence count to filter that noise out.
Setting It Up in a Self-Hosted Scenario
Running Sentry on your own server (not SaaS)? The one thing to check before using @sentry/node 11.0.0 is your self-hosted Sentry version: the migration doc states this SDK requires self-hosted Sentry 26.4.2 or later. Pairing an older self-hosted version with the 11.x SDK can cause span/data schema mismatches.
Setup steps don't differ from SaaS — the only difference is that dsn points to your own server's ingest endpoint, and getOtlpTracesEndpoint() uses this same custom DSN:
1const dsn = "https://<public_key>@sentry.your-own-server.com/<project_id>";2 3Sentry.init({4 dsn,5 enableOpenTelemetrySetup: true,6 tracesSampleRate: 0.1,7});On self-host, verifying the Sentry server version before upgrading is the cheapest check to prevent broken data flow after you update the SDK:
1docker compose exec web sentry --versionIf the output is below 26.4.2, update your self-hosted Sentry setup first — otherwise the data schema @sentry/node 11.x sends may not be fully processed server-side.
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
Reader Reward
We put together a short checklist you can review before taking this setup to production. Check off each item one by one to make sure your setup is free of the three critical mistakes: double pipeline, sampling confusion, and PII leakage.
FAQ
How do you use OpenTelemetry together with Sentry in Node.js?
Sentry v11 offers three approaches: Sentry alone (default, OTel never involved), Sentry setting up its own lightweight OTel-compatible tracer provider with enableOpenTelemetrySetup: true, or your own OTel pipeline (NodeTracerProvider/NodeSDK) with errors/logs attached via Sentry.openTelemetryIntegration(). Which one depends on whether the project already has an OTel collector.
How do I send my existing OTel traces to Sentry?
Add a BatchSpanProcessor to your own NodeTracerProvider; give its OTLPTraceExporter the URL + auth header config returned by Sentry.getOtlpTracesEndpoint(dsn), call provider.register(), then run Sentry.init({ dsn, enableOpenTelemetrySetup: false, integrations: [Sentry.openTelemetryIntegration()] }). Don't set tracesSampleRate — the sampling decision stays with the OTel sampler.
How do you set up the instrumentation file in Next.js?
Run npx @sentry/wizard@latest -i nextjs; the wizard creates instrumentation-client.ts (browser), sentry.server.config.ts (Node.js), and sentry.edge.config.ts (edge runtime) files, and the root file that wires them into Next.js is instrumentation.ts. In the Next.js SDK, enableOpenTelemetrySetup defaults to true.
Sentry or an OTel collector — which do you need?
They aren't rivals — they're complementary. Sentry gives you error/log/cron tracking plus a ready-made dashboard; OpenTelemetry is a vendor-neutral standard for producing traces/metrics/logs. Sentry-only projects don't need OTel (the default mode). Projects already sending data to an OTel collector (Grafana, Datadog, Jaeger) should add Sentry as an error/log source attached to the existing trace via openTelemetryIntegration(), not as a parallel pipeline — leaving both on in tracing mode produces two spans per operation that never merge.
Which Node.js version is required?
The minimum supported version for @sentry/node 11.0.0 is 20.19.0. The package's engines field specifies the range >=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0 — Node versions outside this range may produce warnings during setup or behave unexpectedly.
Conclusion
Setting up observability in Node.js is no longer just calling init on one SDK; your chosen integration path brings three different architectural decisions with it. On Next.js, Next.js 16.3 Removes runtime='edge': The Return to Node helps you understand which runtimes instrumentation.ts runs in; for caching and navigation, see Next.js 16.3 Instant Navigations and Cache Components Guide.
Backend framework choice affects this too; in a serverless setup, Hono.js: Serverless Web Framework Production Guide 2026 is a good start for instrumentation points fitting your architecture. For the error/logging contract in API design, see REST API Design Principles: Resource, Error, Pagination. For the same discipline on mobile, iOS Performance Monitoring & Observability Guide and iOS Crash Reporting and Analytics: Production Quality Guide show the mobile counterpart.
Once you finish the setup, trace a single request end to end and confirm you see the expected span count in the trace view — that's the most reliable proof the setup actually works.
Sources
- npm: @sentry/node — current version and
enginesinfo (dist-tagslatest) - npm: @opentelemetry/sdk-node — current version info (dist-tags
latest) - GitHub: sentry-javascript 11.0.0 Release — official release notes
- Sentry MIGRATION.md — v10 to v11 migration guide, OpenTelemetry setup choices
- Sentry Docs: Node.js OpenTelemetry — general integration guide
- Sentry Docs: Custom OpenTelemetry Setup —
getOtlpTracesEndpointusage - Sentry Docs: Next.js —
instrumentation.tsand file separation - OpenTelemetry JS Docs — official language guide
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
Your subscription starts when you open the link in the confirmation email and press “Confirm my subscription”. The newsletter keeps open/click statistics; you can unsubscribe anytime with one click. Privacy
