Skip to content

feat(node): Add nodeRuntimeMetricsIntegration#19923

Open
chargome wants to merge 9 commits intodevelopfrom
feat/node-runtime-metrics
Open

feat(node): Add nodeRuntimeMetricsIntegration#19923
chargome wants to merge 9 commits intodevelopfrom
feat/node-runtime-metrics

Conversation

@chargome
Copy link
Member

@chargome chargome commented Mar 20, 2026

Adds nodeRuntimeMetricsIntegration to @sentry/node and @sentry/node-core. When enabled, the integration periodically collects Node.js runtime health metrics and emits them to Sentry via the metrics pipeline.

Usage

import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: '...',
  integrations: [
    Sentry.nodeRuntimeMetricsIntegration(),
  ],
});

Default metrics (8)

Emitted every 30 seconds out of the box:

Metric Type Unit Description
node.runtime.mem.rss gauge byte Resident Set Size — actual process memory footprint
node.runtime.mem.heap_used gauge byte V8 heap currently in use — tracks GC pressure and leaks
node.runtime.mem.heap_total gauge byte Total V8 heap allocated — paired with heap_used to see headroom
node.runtime.cpu.utilization gauge CPU time / wall-clock time ratio (can exceed 1.0 on multi-core)
node.runtime.event_loop.delay.p50 gauge second Median event loop delay — baseline latency
node.runtime.event_loop.delay.p99 gauge second 99th percentile event loop delay — tail latency / spikes
node.runtime.event_loop.utilization gauge Fraction of time the event loop was active
node.runtime.process.uptime counter second Cumulative uptime — useful for detecting restarts / crashes

Opt-in metrics (off by default)

Sentry.nodeRuntimeMetricsIntegration({
  collect: {
    cpuTime: true,           // node.runtime.cpu.user + node.runtime.cpu.system (raw seconds)
    memExternal: true,       // node.runtime.mem.external + node.runtime.mem.array_buffers
    eventLoopDelayMin: true,
    eventLoopDelayMax: true,
    eventLoopDelayMean: true,
    eventLoopDelayP90: true,
  },
})

Any default metric can also be turned off:

Sentry.nodeRuntimeMetricsIntegration({
  collect: {
    uptime: false,
    eventLoopDelayP50: false,
  },
})

Collection interval

Sentry.nodeRuntimeMetricsIntegration({
  collectionIntervalMs: 60_000, // default: 30_000
})

Serverless (Next.js on Vercel, AWS Lambda, etc.)

In serverless environments the process is short-lived, so periodic intervals may never fire. The integration handles this automatically:

  • A beforeExit listener fires at the end of every invocation, collects a final snapshot of all metrics, and calls flushIfServerless() — which is a no-op in long-running servers but triggers an immediate flush on serverless platforms.
  • The setInterval is unref()-ed so it never prevents the process from exiting between invocations.
  • No special configuration needed — the same nodeRuntimeMetricsIntegration() call works in both environments.

Runtime compatibility

This integration is Node.js only. Bun and Deno will be addressed in separate integrations that use their respective native APIs.

Closes #19881

@chargome chargome self-assigned this Mar 20, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Deps

  • Bump mongodb-memory-server-global from 10.1.4 to 11.0.1 by dependabot in #19888
  • Bump stacktrace-parser from 0.1.10 to 0.1.11 by dependabot in #19887

Other

  • (node) Add nodeRuntimeMetricsIntegration by chargome in #19923

Bug Fixes 🐛

Cloudflare

  • Send correct events in local development by JPeer264 in #19900
  • Forward ctx argument to Workflow.do user callback by Lms24 in #19891

Core

  • Truncate content array format in Vercel by nicohrubec in #19911
  • Send internal_error as span status for Vercel error spans by nicohrubec in #19921
  • Do not overwrite user provided conversation id in Vercel by nicohrubec in #19903
  • Return same value from startSpan as callback returns by s1gr1d in #19300

Deps

  • Update lockfile to resolve [email protected] by chargome in #19933
  • Bump fast-xml-parser to 5.5.8 in @azure/core-xml chain by chargome in #19918
  • Bump next to 15.5.14 in nextjs-15 and nextjs-15-intl E2E test apps by chargome in #19917
  • Bump socket.io-parser to 4.2.6 to fix CVE-2026-33151 by chargome in #19880

Other

  • (craft) Add missing mainDocsUrl for @sentry/effect SDK by bc-sentry in #19860
  • (nestjs) Add node to nest metadata by chargome in #19875
  • (serverless) Add node to metadata by nicohrubec in #19878

Internal Changes 🔧

Deps Dev

  • Bump effect from 3.19.19 to 3.20.0 by dependabot in #19926
  • Bump qunit-dom from 3.2.1 to 3.5.0 by dependabot in #19546
  • Bump @react-router/node from 7.13.0 to 7.13.1 by dependabot in #19544

Nuxt

  • Extract core logic for storage/database to prepare for Nuxt v5 by s1gr1d in #19920
  • Extract handler patching to extra plugin for Nitro v2/v3 by s1gr1d in #19915

Other

  • (astro) Re-enable server island tracing e2e test in Astro 6 by Lms24 in #19872
  • (ci) Fix "Gatbsy" typo in issue package label workflow by chargome in #19905
  • (claude) Enable Claude Code Intelligence (LSP) by s1gr1d in #19930
  • (lint) Resolve oxlint warnings by isaacs in #19893
  • (node-integration-tests) Remove unnecessary file-type dependency by Lms24 in #19824
  • (remix) Replace glob with native recursive fs walk by roli-lpci in #19531
  • (sveltekit) Replace recast + @babel/parser with acorn by roli-lpci in #19533
  • Add external contributor to CHANGELOG.md by javascript-sdk-gitflow in #19925
  • Add external contributor to CHANGELOG.md by javascript-sdk-gitflow in #19909

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 25.69 kB +0.2% +49 B 🔺
@sentry/browser - with treeshaking flags 24.17 kB +0.14% +33 B 🔺
@sentry/browser (incl. Tracing) 42.67 kB +0.13% +54 B 🔺
@sentry/browser (incl. Tracing, Profiling) 47.33 kB +0.12% +55 B 🔺
@sentry/browser (incl. Tracing, Replay) 81.48 kB +0.08% +57 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 71.06 kB +0.1% +69 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 86.17 kB +0.06% +50 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 98.41 kB +0.04% +36 B 🔺
@sentry/browser (incl. Feedback) 42.48 kB +0.08% +30 B 🔺
@sentry/browser (incl. sendFeedback) 30.35 kB +0.15% +43 B 🔺
@sentry/browser (incl. FeedbackAsync) 35.4 kB +0.12% +39 B 🔺
@sentry/browser (incl. Metrics) 26.96 kB +0.15% +38 B 🔺
@sentry/browser (incl. Logs) 27.1 kB +0.12% +32 B 🔺
@sentry/browser (incl. Metrics & Logs) 27.78 kB +0.15% +39 B 🔺
@sentry/react 27.45 kB +0.22% +58 B 🔺
@sentry/react (incl. Tracing) 45.01 kB +0.14% +60 B 🔺
@sentry/vue 30.13 kB +0.16% +46 B 🔺
@sentry/vue (incl. Tracing) 44.52 kB +0.09% +39 B 🔺
@sentry/svelte 25.7 kB +0.16% +40 B 🔺
CDN Bundle 28.35 kB +0.27% +75 B 🔺
CDN Bundle (incl. Tracing) 43.57 kB +0.15% +62 B 🔺
CDN Bundle (incl. Logs, Metrics) 29.22 kB +0.27% +77 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 44.43 kB +0.17% +75 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 68.29 kB +0.13% +85 B 🔺
CDN Bundle (incl. Tracing, Replay) 80.41 kB +0.1% +73 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 81.31 kB +0.1% +76 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 85.97 kB +0.12% +103 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.86 kB +0.1% +86 B 🔺
CDN Bundle - uncompressed 82.7 kB +0.1% +77 B 🔺
CDN Bundle (incl. Tracing) - uncompressed 128.62 kB +0.05% +64 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 85.57 kB +0.1% +77 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 131.49 kB +0.05% +64 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 209.22 kB +0.05% +102 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed 245.5 kB +0.04% +89 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 248.35 kB +0.04% +89 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 258.41 kB +0.04% +89 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 261.26 kB +0.04% +89 B 🔺
@sentry/nextjs (client) 47.4 kB +0.08% +37 B 🔺
@sentry/sveltekit (client) 43.12 kB +0.12% +51 B 🔺
@sentry/node-core 56.42 kB +0.13% +73 B 🔺
@sentry/node 173.47 kB +0.19% +314 B 🔺
@sentry/node - without tracing 96.43 kB +0.1% +87 B 🔺
@sentry/aws-serverless 113.44 kB +0.09% +100 B 🔺

View base workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 8,924 - 9,155 -3%
GET With Sentry 1,709 19% 1,709 -
GET With Sentry (error only) 6,018 67% 6,171 -2%
POST Baseline 1,126 - 1,221 -8%
POST With Sentry 550 49% 600 -8%
POST With Sentry (error only) 987 88% 1,065 -7%
MYSQL Baseline 3,266 - 3,310 -1%
MYSQL With Sentry 485 15% 487 -0%
MYSQL With Sentry (error only) 2,659 81% 2,672 -0%

View base workflow run

@chargome chargome changed the title [WIP] feat(node): Add nodeRuntimeMetricsIntegration feat(node): Add nodeRuntimeMetricsIntegration Mar 24, 2026
@chargome chargome marked this pull request as ready for review March 24, 2026 15:54
@chargome chargome requested a review from Lms24 March 24, 2026 15:54
@chargome chargome requested a review from JPeer264 March 24, 2026 15:54
Copy link

@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.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: process.once prevents serverless flush after first invocation
    • Changed process.once to process.on so the beforeExit listener fires on every serverless invocation, not just the first one.

Create PR

Or push these changes by commenting:

@cursor push 152bf6699a
Preview (152bf6699a)
diff --git a/packages/node-core/src/integrations/nodeRuntimeMetrics.ts b/packages/node-core/src/integrations/nodeRuntimeMetrics.ts
--- a/packages/node-core/src/integrations/nodeRuntimeMetrics.ts
+++ b/packages/node-core/src/integrations/nodeRuntimeMetrics.ts
@@ -202,7 +202,7 @@
 
       // Collect and flush at the end of every invocation. In non-serverless environments
       // flushIfServerless is a no-op, so this is safe to call unconditionally.
-      process.once('beforeExit', () => {
+      process.on('beforeExit', () => {
         collectMetrics();
         void flushIfServerless();
       });

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

process.once('beforeExit', () => {
collectMetrics();
void flushIfServerless();
});
Copy link

Choose a reason for hiding this comment

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

process.once prevents serverless flush after first invocation

High Severity

The beforeExit listener uses process.once instead of process.on, so it only fires on the first invocation. In serverless warm starts (e.g., AWS Lambda reusing the process), subsequent invocations never trigger the beforeExit flush. The comment on line 203 explicitly says "at the end of every invocation," and every other beforeExit listener in the codebase (e.g., processSession.ts, client.ts) uses process.on.

Fix in Cursor Fix in Web

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.

Runtime Metrics: Node.js implementation

1 participant