Skip to content

feat(node): use diagnostics_channel for redis >= 5.12.0#20573

Open
logaretm wants to merge 6 commits intodevelopfrom
feat/redis-diagnostic-channels
Open

feat(node): use diagnostics_channel for redis >= 5.12.0#20573
logaretm wants to merge 6 commits intodevelopfrom
feat/redis-diagnostic-channels

Conversation

@logaretm
Copy link
Copy Markdown
Member

@logaretm logaretm commented Apr 28, 2026

Builds on #20510 and adds tracing channels subscribers via node:diagnostics_channel.

The module patchers are narrowed to >=5.0.0 <5.12.0 while the subscriber path runs unconditionally. it will be inert in older releases anyways and will activate when version 5.12 publishes the events.

Verified that it works on Node and Bun equally as well, while IITM fails on Bun.

Copilot AI review requested due to automatic review settings April 28, 2026 20:36
@logaretm logaretm marked this pull request as draft April 28, 2026 20:36
Comment thread packages/node/src/integrations/tracing/redis/redis-dc-subscriber.ts Outdated
Comment thread packages/node/src/integrations/tracing/redis/redis-dc-subscriber.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds diagnostics_channel-based Redis tracing for upcoming node-redis (>= 5.12.0) while narrowing the existing IITM patching to avoid double instrumentation once diagnostics events are available.

Changes:

  • Restrict vendored node-redis IITM patcher to >=5.0.0 <5.12.0.
  • Add a diagnostics_channel subscriber which creates Sentry spans from node-redis:* tracing channels.
  • Ensure @sentry/opentelemetry/* subpath imports stay external in the Node rollup build.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
packages/node/src/integrations/tracing/redis/vendored/redis-instrumentation.ts Narrows the v5 IITM instrumentation version range to stop before 5.12.0.
packages/node/src/integrations/tracing/redis/redis-dc-subscriber.ts New diagnostics_channel subscriber which creates/ends spans for command/batch/connect events and runs the responseHook.
packages/node/src/integrations/tracing/redis/index.ts Wires the subscriber into Redis instrumentation (deferred to next tick).
packages/node/rollup.npm.config.mjs Externalizes @sentry/opentelemetry/* imports so subpath entrypoints aren’t bundled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +120 to +124
// node-redis >= 5.12.0 publishes via diagnostics_channel. The subscriber uses
// `@sentry/opentelemetry/tracing-channel`, which needs the Sentry OTel context manager
// to be registered before it can `bindStore`. `initOpenTelemetry()` runs after integration
// `setupOnce`, so defer to the next tick.
Promise.resolve().then(() => subscribeRedisDiagnosticChannels(cacheResponseHook));
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diagnostics_channel subscriber is deferred via a microtask. This means node-redis >=5.12.0 operations performed immediately after Sentry.init() (in the same synchronous tick) can run before the subscriber is attached and will not be traced. Consider hooking this up from the OpenTelemetry init path (after the context manager is registered) or otherwise ensuring subscription happens synchronously before user code continues.

Suggested change
// node-redis >= 5.12.0 publishes via diagnostics_channel. The subscriber uses
// `@sentry/opentelemetry/tracing-channel`, which needs the Sentry OTel context manager
// to be registered before it can `bindStore`. `initOpenTelemetry()` runs after integration
// `setupOnce`, so defer to the next tick.
Promise.resolve().then(() => subscribeRedisDiagnosticChannels(cacheResponseHook));
// node-redis >= 5.12.0 publishes via diagnostics_channel. Subscribe synchronously so
// operations performed immediately after `Sentry.init()` are traced without a race window.
subscribeRedisDiagnosticChannels(cacheResponseHook);

Copilot uses AI. Check for mistakes.
Comment thread packages/node/src/integrations/tracing/redis/redis-dc-subscriber.ts
Comment thread packages/node/src/integrations/tracing/redis/redis-dc-subscriber.ts
Comment thread packages/node/src/integrations/tracing/redis/redis-dc-subscriber.ts
Comment on lines +120 to +124
// node-redis >= 5.12.0 publishes via diagnostics_channel. The subscriber uses
// `@sentry/opentelemetry/tracing-channel`, which needs the Sentry OTel context manager
// to be registered before it can `bindStore`. `initOpenTelemetry()` runs after integration
// `setupOnce`, so defer to the next tick.
Promise.resolve().then(() => subscribeRedisDiagnosticChannels(cacheResponseHook));
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a new diagnostics_channel-based instrumentation path, but there are no unit tests covering span creation/end, error handling, or that the responseHook is invoked for command events. Since this package already has Redis tracing tests under packages/node/test/integrations/tracing/redis, it would be good to add a focused test suite for subscribeRedisDiagnosticChannels() using node:diagnostics_channel tracing channels.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 28, 2026

size-limit report 📦

Path Size % Change Change
@sentry/browser 26.3 kB - -
@sentry/browser - with treeshaking flags 24.78 kB - -
@sentry/browser (incl. Tracing) 44.17 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 46.39 kB - -
@sentry/browser (incl. Tracing, Profiling) 49.14 kB - -
@sentry/browser (incl. Tracing, Replay) 83.55 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 73.01 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 88.23 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 100.84 kB - -
@sentry/browser (incl. Feedback) 43.44 kB - -
@sentry/browser (incl. sendFeedback) 31.11 kB - -
@sentry/browser (incl. FeedbackAsync) 36.19 kB - -
@sentry/browser (incl. Metrics) 27.6 kB - -
@sentry/browser (incl. Logs) 27.73 kB - -
@sentry/browser (incl. Metrics & Logs) 28.43 kB - -
@sentry/react 28.04 kB -0.01% -1 B 🔽
@sentry/react (incl. Tracing) 46.4 kB - -
@sentry/vue 31.18 kB - -
@sentry/vue (incl. Tracing) 46.02 kB - -
@sentry/svelte 26.32 kB - -
CDN Bundle 28.91 kB - -
CDN Bundle (incl. Tracing) 46.94 kB - -
CDN Bundle (incl. Logs, Metrics) 30.34 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 48.04 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 69.4 kB - -
CDN Bundle (incl. Tracing, Replay) 84.07 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 85.15 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 89.89 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 90.97 kB - -
CDN Bundle - uncompressed 84.88 kB - -
CDN Bundle (incl. Tracing) - uncompressed 140.44 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 89.08 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 143.9 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 212.99 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 258.24 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 261.69 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 271.94 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 275.38 kB - -
@sentry/nextjs (client) 48.9 kB - -
@sentry/sveltekit (client) 44.64 kB - -
@sentry/node-core 59.81 kB +0.02% +10 B 🔺
@sentry/node 164.69 kB +0.78% +1.27 kB 🔺
@sentry/node - without tracing 72.28 kB +0.02% +8 B 🔺
@sentry/aws-serverless 106.95 kB +0.01% +5 B 🔺
@sentry/cloudflare (withSentry) - minified 168.38 kB - -
@sentry/cloudflare (withSentry) 424.9 kB - -

View base workflow run

@isaacs isaacs force-pushed the isaacs/vendor-redis branch 3 times, most recently from d150996 to de731ed Compare May 4, 2026 19:17
Base automatically changed from isaacs/vendor-redis to develop May 4, 2026 19:58
logaretm added 2 commits May 4, 2026 13:02
node-redis 5.12.0 publishes command, batch, and connect events via
node:diagnostics_channel. Subscribe to those channels via
@sentry/opentelemetry/tracing-channel to produce spans without IITM-
based monkey-patching, which lets the redis integration work on
runtimes that don't support IITM (Bun, Deno, Cloudflare Workers).

The existing OTel patcher is narrowed to '<5.12.0' so it does not
double-instrument when both paths are present. The DC subscription is
deferred to the next microtask so it runs after initOpenTelemetry()
sets up the Sentry context manager (required for bindStore).
@isaacs isaacs force-pushed the feat/redis-diagnostic-channels branch 2 times, most recently from fdbf2b6 to 75744f4 Compare May 4, 2026 20:35
@logaretm logaretm requested a review from isaacs May 4, 2026 20:40
@logaretm logaretm marked this pull request as ready for review May 4, 2026 20:40
@logaretm logaretm requested a review from a team as a code owner May 4, 2026 20:40
@isaacs isaacs force-pushed the feat/redis-diagnostic-channels branch from 75744f4 to 22acdcd Compare May 4, 2026 22:01
Comment thread packages/node/src/integrations/tracing/redis/redis-dc-subscriber.ts Outdated
@isaacs isaacs force-pushed the feat/redis-diagnostic-channels branch from 22acdcd to 38623e1 Compare May 4, 2026 22:44
@isaacs isaacs force-pushed the feat/redis-diagnostic-channels branch from 38623e1 to f88ffb0 Compare May 4, 2026 23:07
Comment thread packages/node/src/integrations/tracing/redis/redis-dc-subscriber.ts
error?: Error;
}

interface BatchData {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l/q: Are these types also vendored? If so it might be nicer if they live in the vendored folder

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not vendored from OTEL. the those are exported by redis, shame we can't use it as a dev dep here.

"prisma": "6.15.0",
"proxy": "^2.1.1",
"redis-4": "npm:redis@^4.6.14",
"redis-5": "npm:redis@^5.12.0",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: I think it would make sense to add 2 tests. One for redis 5 with tracing channels and another without them. Then we would know if we break something.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are subtle differences between span counts with 5.12 and <5.12 as a result of some decisions in the redis tracing channel PR.

But yea we should have both to make sure we capture correctly.

// `@sentry/opentelemetry/tracing-channel`, which needs the Sentry OTel context manager
// to be registered before it can `bindStore`. `initOpenTelemetry()` runs after integration
// `setupOnce`, so defer to the next tick.
void Promise.resolve().then(() => subscribeRedisDiagnosticChannels(cacheResponseHook));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: Would this work the same with CJS and ESM in Node? I wonder if there is a better way, than defer it to the next tick, like a "afterSetup" hook or so

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, it works the same. We have an afterAllSetup but that could be much later than we like, or too early. I have not tried tbh.

Let me give afterAllSetup a go.

logaretm added 2 commits May 5, 2026 13:42
node-redis sanitizeArgs replaces MGET key arguments with '?' in
the diagnostics_channel payload, so cache prefix detection cannot
match — MGET remains a plain db.redis span.
Comment thread packages/node/src/integrations/tracing/redis/redis-dc-subscriber.ts
if (!span) return;
// Same slice: strip command name from args before passing to the response hook.
runResponseHook(span, data.command, data.args.slice(1), data.result);
span.end();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command error causes response hook on ended span

Medium Severity

Per the Node.js TracingChannel contract, when a traced promise rejects, both the error and asyncEnd events fire (in that order). The error handler sets the error status and ends the span, but then asyncEnd still runs runResponseHook on the already-ended span with a potentially undefined data.result, and calls span.end() again. The asyncEnd handler lacks a guard against the error-already-handled case, so cacheResponseHook may incorrectly set cache.hit: false (from calculateCacheItemSize(undefined) returning 0) on error spans.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 992feac. Configure here.

The hook unconditionally set origin to 'auto.db.otel.redis', clobbering
the diagnostic_channel origin on all DC-sourced spans. Remove the
override so each instrumentation path keeps its own origin.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a57c3bf. Configure here.

response: unknown,
) => {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.redis');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IITM-path redis spans lose sentry.origin attribute

High Severity

The removal of sentry.origin from cacheResponseHook means ioredis and redis module spans (versions <5.12.0) no longer set sentry.origin. These auto-instrumented spans now incorrectly default to origin: 'manual' instead of auto.db.otel.redis, causing integration tests to fail.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit a57c3bf. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do this already in the integration inside the IITM path, so this was affecting both while being redundant for IITM.

Tests will prove that I think...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants