From f4016c9560b893ed0313a8180c30bd5222a7498c Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Wed, 1 Apr 2026 12:13:50 +0200 Subject: [PATCH 1/9] use min value for all runtime metrics --- .../bun/src/integrations/bunRuntimeMetrics.ts | 9 ++++-- .../integrations/bunRuntimeMetrics.test.ts | 27 +++++++++++++++++ .../src/integrations/denoRuntimeMetrics.ts | 4 +-- .../deno/test/deno-runtime-metrics.test.ts | 4 +-- packages/node-core/src/common-exports.ts | 6 +++- .../src/integrations/nodeRuntimeMetrics.ts | 23 ++++++++++++++- .../integrations/nodeRuntimeMetrics.test.ts | 29 +++++++++++++++++++ packages/node/src/index.ts | 1 + 8 files changed, 95 insertions(+), 8 deletions(-) diff --git a/packages/bun/src/integrations/bunRuntimeMetrics.ts b/packages/bun/src/integrations/bunRuntimeMetrics.ts index 7646eb23568b..2130a5cf6d05 100644 --- a/packages/bun/src/integrations/bunRuntimeMetrics.ts +++ b/packages/bun/src/integrations/bunRuntimeMetrics.ts @@ -1,6 +1,6 @@ import { performance } from 'perf_hooks'; import { _INTERNAL_safeDateNow, _INTERNAL_safeUnref, defineIntegration, metrics } from '@sentry/core'; -import type { NodeRuntimeMetricsOptions } from '@sentry/node'; +import { _INTERNAL_normalizeCollectionInterval, type NodeRuntimeMetricsOptions } from '@sentry/node'; const INTEGRATION_NAME = 'BunRuntimeMetrics'; const DEFAULT_INTERVAL_MS = 30_000; @@ -44,7 +44,9 @@ export interface BunRuntimeMetricsOptions { collect?: BunCollectOptions; /** * How often to collect metrics, in milliseconds. + * Minimum allowed value is 1000ms. * @default 30000 + * @minimum 1000 */ collectionIntervalMs?: number; } @@ -62,7 +64,10 @@ export interface BunRuntimeMetricsOptions { * ``` */ export const bunRuntimeMetricsIntegration = defineIntegration((options: BunRuntimeMetricsOptions = {}) => { - const collectionIntervalMs = options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS; + const collectionIntervalMs = _INTERNAL_normalizeCollectionInterval( + options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS, + INTEGRATION_NAME, + ); const collect = { // Default on cpuUtilization: true, diff --git a/packages/bun/test/integrations/bunRuntimeMetrics.test.ts b/packages/bun/test/integrations/bunRuntimeMetrics.test.ts index 6264905db41e..b0ff4ab48443 100644 --- a/packages/bun/test/integrations/bunRuntimeMetrics.test.ts +++ b/packages/bun/test/integrations/bunRuntimeMetrics.test.ts @@ -212,4 +212,31 @@ describe('bunRuntimeMetricsIntegration', () => { expect(countSpy).not.toHaveBeenCalledWith('bun.runtime.process.uptime', expect.anything(), expect.anything()); }); }); + + describe('collectionIntervalMs minimum', () => { + it('enforces minimum of 1000ms and warns', () => { + const warnSpy = spyOn(globalThis.console, 'warn').mockImplementation(() => {}); + + const integration = bunRuntimeMetricsIntegration({ collectionIntervalMs: 100 }); + integration.setup(); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('collectionIntervalMs')); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('1000')); + + // Should fire at minimum 1000ms, not at 100ms + jest.advanceTimersByTime(100); + expect(gaugeSpy).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(900); + expect(gaugeSpy).toHaveBeenCalled(); + }); + + it('falls back to minimum when NaN', () => { + const warnSpy = spyOn(globalThis.console, 'warn').mockImplementation(() => {}); + + bunRuntimeMetricsIntegration({ collectionIntervalMs: NaN }); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('collectionIntervalMs')); + }); + }); }); diff --git a/packages/deno/src/integrations/denoRuntimeMetrics.ts b/packages/deno/src/integrations/denoRuntimeMetrics.ts index 077e920bd5a4..be2fcfb19f36 100644 --- a/packages/deno/src/integrations/denoRuntimeMetrics.ts +++ b/packages/deno/src/integrations/denoRuntimeMetrics.ts @@ -28,7 +28,7 @@ export interface DenoRuntimeMetricsOptions { }; /** * How often to collect metrics, in milliseconds. - * Values below 1000ms are clamped to 1000ms. + * Minimum allowed value is 1000ms. * @default 30000 * @minimum 1000 */ @@ -52,7 +52,7 @@ export const denoRuntimeMetricsIntegration = defineIntegration((options: DenoRun if (!Number.isFinite(rawInterval) || rawInterval < MIN_INTERVAL_MS) { // eslint-disable-next-line no-console console.warn( - `[Sentry] denoRuntimeMetricsIntegration: collectionIntervalMs (${rawInterval}) is below the minimum of ${MIN_INTERVAL_MS}ms. Clamping to ${MIN_INTERVAL_MS}ms.`, + `[Sentry] denoRuntimeMetricsIntegration: collectionIntervalMs (${rawInterval}) is below the minimum of ${MIN_INTERVAL_MS}ms. Using minimum of ${MIN_INTERVAL_MS}ms.`, ); } const collectionIntervalMs = Number.isFinite(rawInterval) ? Math.max(rawInterval, MIN_INTERVAL_MS) : MIN_INTERVAL_MS; diff --git a/packages/deno/test/deno-runtime-metrics.test.ts b/packages/deno/test/deno-runtime-metrics.test.ts index 12b1c72fd985..ee953f01c81e 100644 --- a/packages/deno/test/deno-runtime-metrics.test.ts +++ b/packages/deno/test/deno-runtime-metrics.test.ts @@ -119,7 +119,7 @@ Deno.test('attaches correct sentry.origin attribute', async () => { assertEquals(rss?.attributes?.['sentry.origin']?.value, 'auto.deno.runtime_metrics'); }); -Deno.test('warns and clamps collectionIntervalMs below 1000ms', () => { +Deno.test('warns and enforces minimum collectionIntervalMs', () => { const warnings: string[] = []; const originalWarn = globalThis.console.warn; globalThis.console.warn = (msg: string) => warnings.push(msg); @@ -135,7 +135,7 @@ Deno.test('warns and clamps collectionIntervalMs below 1000ms', () => { assertStringIncludes(warnings[0]!, '1000'); }); -Deno.test('warns and clamps collectionIntervalMs when NaN', () => { +Deno.test('warns and falls back to minimum when collectionIntervalMs is NaN', () => { const warnings: string[] = []; const originalWarn = globalThis.console.warn; globalThis.console.warn = (msg: string) => warnings.push(msg); diff --git a/packages/node-core/src/common-exports.ts b/packages/node-core/src/common-exports.ts index d6d1e070ef85..03cf6addbc13 100644 --- a/packages/node-core/src/common-exports.ts +++ b/packages/node-core/src/common-exports.ts @@ -12,7 +12,11 @@ import * as logger from './logs/exports'; // Node-core integrations (not OTel-dependent) export { nodeContextIntegration } from './integrations/context'; -export { nodeRuntimeMetricsIntegration, type NodeRuntimeMetricsOptions } from './integrations/nodeRuntimeMetrics'; +export { + nodeRuntimeMetricsIntegration, + type NodeRuntimeMetricsOptions, + _INTERNAL_normalizeCollectionInterval, +} from './integrations/nodeRuntimeMetrics'; export { contextLinesIntegration } from './integrations/contextlines'; export { localVariablesIntegration } from './integrations/local-variables'; export { modulesIntegration } from './integrations/modules'; diff --git a/packages/node-core/src/integrations/nodeRuntimeMetrics.ts b/packages/node-core/src/integrations/nodeRuntimeMetrics.ts index c2ae72f04f77..bac79a92efc6 100644 --- a/packages/node-core/src/integrations/nodeRuntimeMetrics.ts +++ b/packages/node-core/src/integrations/nodeRuntimeMetrics.ts @@ -3,8 +3,24 @@ import { _INTERNAL_safeDateNow, _INTERNAL_safeUnref, defineIntegration, metrics const INTEGRATION_NAME = 'NodeRuntimeMetrics'; const DEFAULT_INTERVAL_MS = 30_000; +const MIN_COLLECTION_INTERVAL_MS = 1_000; const EVENT_LOOP_DELAY_RESOLUTION_MS = 10; +/** + * Normalizes a `collectionIntervalMs` value, enforcing a minimum of 1000ms. + * Warns if the value is below the minimum or non-finite (e.g. NaN). + * @internal + */ +export function _INTERNAL_normalizeCollectionInterval(rawInterval: number, integrationName: string): number { + if (!Number.isFinite(rawInterval) || rawInterval < MIN_COLLECTION_INTERVAL_MS) { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] ${integrationName}: collectionIntervalMs (${rawInterval}) is below the minimum of ${MIN_COLLECTION_INTERVAL_MS}ms. Using minimum of ${MIN_COLLECTION_INTERVAL_MS}ms.`, + ); + } + return Number.isFinite(rawInterval) ? Math.max(rawInterval, MIN_COLLECTION_INTERVAL_MS) : MIN_COLLECTION_INTERVAL_MS; +} + export interface NodeRuntimeMetricsOptions { /** * Which metrics to collect. @@ -44,7 +60,9 @@ export interface NodeRuntimeMetricsOptions { }; /** * How often to collect metrics, in milliseconds. + * Minimum allowed value is 1000ms. * @default 30000 + * @minimum 1000 */ collectionIntervalMs?: number; } @@ -62,7 +80,10 @@ export interface NodeRuntimeMetricsOptions { * ``` */ export const nodeRuntimeMetricsIntegration = defineIntegration((options: NodeRuntimeMetricsOptions = {}) => { - const collectionIntervalMs = options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS; + const collectionIntervalMs = _INTERNAL_normalizeCollectionInterval( + options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS, + INTEGRATION_NAME, + ); const collect = { // Default on cpuUtilization: true, diff --git a/packages/node-core/test/integrations/nodeRuntimeMetrics.test.ts b/packages/node-core/test/integrations/nodeRuntimeMetrics.test.ts index fe1de568304a..4488ffb5f154 100644 --- a/packages/node-core/test/integrations/nodeRuntimeMetrics.test.ts +++ b/packages/node-core/test/integrations/nodeRuntimeMetrics.test.ts @@ -329,5 +329,34 @@ describe('nodeRuntimeMetricsIntegration', () => { expect(countSpy).not.toHaveBeenCalledWith('node.runtime.process.uptime', expect.anything(), expect.anything()); }); + + it('enforces minimum collectionIntervalMs of 1000ms and warns', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const integration = nodeRuntimeMetricsIntegration({ collectionIntervalMs: 100 }); + integration.setup(); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('collectionIntervalMs')); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('1000')); + + // Should fire at the minimum 1000ms, not at 100ms + vi.advanceTimersByTime(100); + expect(gaugeSpy).not.toHaveBeenCalled(); + + vi.advanceTimersByTime(900); + expect(gaugeSpy).toHaveBeenCalled(); + + warnSpy.mockRestore(); + }); + + it('falls back to minimum when collectionIntervalMs is NaN', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + nodeRuntimeMetricsIntegration({ collectionIntervalMs: NaN }); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('collectionIntervalMs')); + + warnSpy.mockRestore(); + }); }); }); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 67fe97e59300..ac74638130b9 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -203,4 +203,5 @@ export { cron, NODE_VERSION, validateOpenTelemetrySetup, + _INTERNAL_normalizeCollectionInterval, } from '@sentry/node-core'; From 8d819c93430b172f686d5408ad6f5ec5af411637 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Wed, 1 Apr 2026 14:02:13 +0200 Subject: [PATCH 2/9] bump timeouts --- .../suites/node-runtime-metrics/scenario-all.ts | 4 ++-- .../suites/node-runtime-metrics/scenario-opt-out.ts | 4 ++-- .../suites/node-runtime-metrics/scenario.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario-all.ts b/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario-all.ts index e995482fafbf..94a7161fe00c 100644 --- a/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario-all.ts +++ b/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario-all.ts @@ -8,7 +8,7 @@ Sentry.init({ transport: loggingTransport, integrations: [ Sentry.nodeRuntimeMetricsIntegration({ - collectionIntervalMs: 100, + collectionIntervalMs: 1000, collect: { cpuTime: true, memExternal: true, @@ -22,7 +22,7 @@ Sentry.init({ }); async function run(): Promise { - await new Promise(resolve => setTimeout(resolve, 250)); + await new Promise(resolve => setTimeout(resolve, 1100)); await Sentry.flush(); } diff --git a/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario-opt-out.ts b/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario-opt-out.ts index 423e478ed1f8..e838df6e9408 100644 --- a/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario-opt-out.ts +++ b/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario-opt-out.ts @@ -8,7 +8,7 @@ Sentry.init({ transport: loggingTransport, integrations: [ Sentry.nodeRuntimeMetricsIntegration({ - collectionIntervalMs: 100, + collectionIntervalMs: 1000, collect: { cpuUtilization: false, cpuTime: false, @@ -22,7 +22,7 @@ Sentry.init({ }); async function run(): Promise { - await new Promise(resolve => setTimeout(resolve, 250)); + await new Promise(resolve => setTimeout(resolve, 1100)); await Sentry.flush(); } diff --git a/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario.ts b/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario.ts index b862634c719a..1e174e5c29cc 100644 --- a/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario.ts +++ b/dev-packages/node-integration-tests/suites/node-runtime-metrics/scenario.ts @@ -8,14 +8,14 @@ Sentry.init({ transport: loggingTransport, integrations: [ Sentry.nodeRuntimeMetricsIntegration({ - collectionIntervalMs: 100, + collectionIntervalMs: 1000, }), ], }); async function run(): Promise { // Wait long enough for the collection interval to fire at least once. - await new Promise(resolve => setTimeout(resolve, 250)); + await new Promise(resolve => setTimeout(resolve, 1100)); await Sentry.flush(); } From 04faa9b3a8985899fe774c652ab70cd60db12992 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Wed, 1 Apr 2026 19:04:56 +0200 Subject: [PATCH 3/9] bump bun timeouts --- .../suites/bun-runtime-metrics/scenario-all.ts | 4 ++-- .../suites/bun-runtime-metrics/scenario-opt-out.ts | 4 ++-- .../suites/bun-runtime-metrics/scenario.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario-all.ts b/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario-all.ts index f82385c4c16e..6ec4ca0c1244 100644 --- a/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario-all.ts +++ b/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario-all.ts @@ -9,7 +9,7 @@ Sentry.init({ transport: loggingTransport, integrations: [ bunRuntimeMetricsIntegration({ - collectionIntervalMs: 100, + collectionIntervalMs: 1000, collect: { cpuTime: true, memExternal: true, @@ -19,7 +19,7 @@ Sentry.init({ }); async function run(): Promise { - await new Promise(resolve => setTimeout(resolve, 250)); + await new Promise(resolve => setTimeout(resolve, 1100)); await Sentry.flush(); } diff --git a/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario-opt-out.ts b/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario-opt-out.ts index d3aa0f309893..8987865e277d 100644 --- a/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario-opt-out.ts +++ b/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario-opt-out.ts @@ -9,7 +9,7 @@ Sentry.init({ transport: loggingTransport, integrations: [ bunRuntimeMetricsIntegration({ - collectionIntervalMs: 100, + collectionIntervalMs: 1000, collect: { cpuUtilization: false, cpuTime: false, @@ -21,7 +21,7 @@ Sentry.init({ }); async function run(): Promise { - await new Promise(resolve => setTimeout(resolve, 250)); + await new Promise(resolve => setTimeout(resolve, 1100)); await Sentry.flush(); } diff --git a/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario.ts b/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario.ts index 1948ddfa6c23..92e248cccc6e 100644 --- a/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario.ts +++ b/dev-packages/node-integration-tests/suites/bun-runtime-metrics/scenario.ts @@ -9,13 +9,13 @@ Sentry.init({ transport: loggingTransport, integrations: [ bunRuntimeMetricsIntegration({ - collectionIntervalMs: 100, + collectionIntervalMs: 1000, }), ], }); async function run(): Promise { - await new Promise(resolve => setTimeout(resolve, 250)); + await new Promise(resolve => setTimeout(resolve, 1100)); await Sentry.flush(); } From 6fc3c7a3d01325a781ad4b3daea2bf24cdb6cebb Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 2 Apr 2026 10:51:01 +0200 Subject: [PATCH 4/9] fix(bun): Inline collection interval normalization, remove from node public API Exporting _INTERNAL_normalizeCollectionInterval from @sentry/node triggers the consistent-exports check, requiring all node-dependent packages to re-export it. Inline the logic in bun directly instead. Co-Authored-By: Claude Sonnet 4.6 --- .../bun/src/integrations/bunRuntimeMetrics.ts | 17 ++++++++++++----- packages/node-core/src/common-exports.ts | 6 +----- packages/node/src/index.ts | 1 - 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/bun/src/integrations/bunRuntimeMetrics.ts b/packages/bun/src/integrations/bunRuntimeMetrics.ts index 2130a5cf6d05..39ca00639232 100644 --- a/packages/bun/src/integrations/bunRuntimeMetrics.ts +++ b/packages/bun/src/integrations/bunRuntimeMetrics.ts @@ -1,9 +1,10 @@ import { performance } from 'perf_hooks'; import { _INTERNAL_safeDateNow, _INTERNAL_safeUnref, defineIntegration, metrics } from '@sentry/core'; -import { _INTERNAL_normalizeCollectionInterval, type NodeRuntimeMetricsOptions } from '@sentry/node'; +import type { NodeRuntimeMetricsOptions } from '@sentry/node'; const INTEGRATION_NAME = 'BunRuntimeMetrics'; const DEFAULT_INTERVAL_MS = 30_000; +const MIN_COLLECTION_INTERVAL_MS = 1_000; /** * Which metrics to collect in the Bun runtime metrics integration. @@ -64,10 +65,16 @@ export interface BunRuntimeMetricsOptions { * ``` */ export const bunRuntimeMetricsIntegration = defineIntegration((options: BunRuntimeMetricsOptions = {}) => { - const collectionIntervalMs = _INTERNAL_normalizeCollectionInterval( - options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS, - INTEGRATION_NAME, - ); + const rawInterval = options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS; + if (!Number.isFinite(rawInterval) || rawInterval < MIN_COLLECTION_INTERVAL_MS) { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] ${INTEGRATION_NAME}: collectionIntervalMs (${rawInterval}) is below the minimum of ${MIN_COLLECTION_INTERVAL_MS}ms. Using minimum of ${MIN_COLLECTION_INTERVAL_MS}ms.`, + ); + } + const collectionIntervalMs = Number.isFinite(rawInterval) + ? Math.max(rawInterval, MIN_COLLECTION_INTERVAL_MS) + : MIN_COLLECTION_INTERVAL_MS; const collect = { // Default on cpuUtilization: true, diff --git a/packages/node-core/src/common-exports.ts b/packages/node-core/src/common-exports.ts index 03cf6addbc13..d6d1e070ef85 100644 --- a/packages/node-core/src/common-exports.ts +++ b/packages/node-core/src/common-exports.ts @@ -12,11 +12,7 @@ import * as logger from './logs/exports'; // Node-core integrations (not OTel-dependent) export { nodeContextIntegration } from './integrations/context'; -export { - nodeRuntimeMetricsIntegration, - type NodeRuntimeMetricsOptions, - _INTERNAL_normalizeCollectionInterval, -} from './integrations/nodeRuntimeMetrics'; +export { nodeRuntimeMetricsIntegration, type NodeRuntimeMetricsOptions } from './integrations/nodeRuntimeMetrics'; export { contextLinesIntegration } from './integrations/contextlines'; export { localVariablesIntegration } from './integrations/local-variables'; export { modulesIntegration } from './integrations/modules'; diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index ac74638130b9..67fe97e59300 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -203,5 +203,4 @@ export { cron, NODE_VERSION, validateOpenTelemetrySetup, - _INTERNAL_normalizeCollectionInterval, } from '@sentry/node-core'; From 36e7b1de359becda13a4b0c646997392f9fd4222 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 2 Apr 2026 10:59:10 +0200 Subject: [PATCH 5/9] Revert "fix(bun): Inline collection interval normalization, remove from node public API" This reverts commit 6fc3c7a3d01325a781ad4b3daea2bf24cdb6cebb. --- .../bun/src/integrations/bunRuntimeMetrics.ts | 17 +++++------------ packages/node-core/src/common-exports.ts | 6 +++++- packages/node/src/index.ts | 1 + 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/bun/src/integrations/bunRuntimeMetrics.ts b/packages/bun/src/integrations/bunRuntimeMetrics.ts index 39ca00639232..2130a5cf6d05 100644 --- a/packages/bun/src/integrations/bunRuntimeMetrics.ts +++ b/packages/bun/src/integrations/bunRuntimeMetrics.ts @@ -1,10 +1,9 @@ import { performance } from 'perf_hooks'; import { _INTERNAL_safeDateNow, _INTERNAL_safeUnref, defineIntegration, metrics } from '@sentry/core'; -import type { NodeRuntimeMetricsOptions } from '@sentry/node'; +import { _INTERNAL_normalizeCollectionInterval, type NodeRuntimeMetricsOptions } from '@sentry/node'; const INTEGRATION_NAME = 'BunRuntimeMetrics'; const DEFAULT_INTERVAL_MS = 30_000; -const MIN_COLLECTION_INTERVAL_MS = 1_000; /** * Which metrics to collect in the Bun runtime metrics integration. @@ -65,16 +64,10 @@ export interface BunRuntimeMetricsOptions { * ``` */ export const bunRuntimeMetricsIntegration = defineIntegration((options: BunRuntimeMetricsOptions = {}) => { - const rawInterval = options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS; - if (!Number.isFinite(rawInterval) || rawInterval < MIN_COLLECTION_INTERVAL_MS) { - // eslint-disable-next-line no-console - console.warn( - `[Sentry] ${INTEGRATION_NAME}: collectionIntervalMs (${rawInterval}) is below the minimum of ${MIN_COLLECTION_INTERVAL_MS}ms. Using minimum of ${MIN_COLLECTION_INTERVAL_MS}ms.`, - ); - } - const collectionIntervalMs = Number.isFinite(rawInterval) - ? Math.max(rawInterval, MIN_COLLECTION_INTERVAL_MS) - : MIN_COLLECTION_INTERVAL_MS; + const collectionIntervalMs = _INTERNAL_normalizeCollectionInterval( + options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS, + INTEGRATION_NAME, + ); const collect = { // Default on cpuUtilization: true, diff --git a/packages/node-core/src/common-exports.ts b/packages/node-core/src/common-exports.ts index d6d1e070ef85..03cf6addbc13 100644 --- a/packages/node-core/src/common-exports.ts +++ b/packages/node-core/src/common-exports.ts @@ -12,7 +12,11 @@ import * as logger from './logs/exports'; // Node-core integrations (not OTel-dependent) export { nodeContextIntegration } from './integrations/context'; -export { nodeRuntimeMetricsIntegration, type NodeRuntimeMetricsOptions } from './integrations/nodeRuntimeMetrics'; +export { + nodeRuntimeMetricsIntegration, + type NodeRuntimeMetricsOptions, + _INTERNAL_normalizeCollectionInterval, +} from './integrations/nodeRuntimeMetrics'; export { contextLinesIntegration } from './integrations/contextlines'; export { localVariablesIntegration } from './integrations/local-variables'; export { modulesIntegration } from './integrations/modules'; diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 67fe97e59300..ac74638130b9 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -203,4 +203,5 @@ export { cron, NODE_VERSION, validateOpenTelemetrySetup, + _INTERNAL_normalizeCollectionInterval, } from '@sentry/node-core'; From f9b34c47eeee1a6b9da407fc4058a204bad58cf2 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 2 Apr 2026 11:15:48 +0200 Subject: [PATCH 6/9] fix: Re-export _INTERNAL_normalizeCollectionInterval in dependent packages The consistent exports CI check requires that packages depending on @sentry/node re-export everything @sentry/node exports. Add the missing _INTERNAL_normalizeCollectionInterval re-export to @sentry/astro, @sentry/bun, @sentry/aws-serverless, and @sentry/google-cloud-serverless. Co-Authored-By: Claude Sonnet 4.6 --- packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index f19f82391a5f..46f82e62c8b5 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -173,6 +173,7 @@ export { unleashIntegration, growthbookIntegration, metrics, + _INTERNAL_normalizeCollectionInterval, } from '@sentry/node'; export { init } from './server/sdk'; diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 00b14ed59235..6961684ed0cb 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -159,6 +159,7 @@ export { unleashIntegration, growthbookIntegration, metrics, + _INTERNAL_normalizeCollectionInterval, } from '@sentry/node'; export { diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 41f5b3cf5c52..734caf19de03 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -177,6 +177,7 @@ export { statsigIntegration, unleashIntegration, metrics, + _INTERNAL_normalizeCollectionInterval, } from '@sentry/node'; export { diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 004c785b6ca5..9cdff9674440 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -159,6 +159,7 @@ export { statsigIntegration, unleashIntegration, metrics, + _INTERNAL_normalizeCollectionInterval, } from '@sentry/node'; export { From b243f976461f1bc2f6e85487e71d6b2c39c31a69 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 2 Apr 2026 13:44:11 +0200 Subject: [PATCH 7/9] Revert "fix: Re-export _INTERNAL_normalizeCollectionInterval in dependent packages" This reverts commit f9b34c47eeee1a6b9da407fc4058a204bad58cf2. --- packages/astro/src/index.server.ts | 1 - packages/aws-serverless/src/index.ts | 1 - packages/bun/src/index.ts | 1 - packages/google-cloud-serverless/src/index.ts | 1 - 4 files changed, 4 deletions(-) diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 46f82e62c8b5..f19f82391a5f 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -173,7 +173,6 @@ export { unleashIntegration, growthbookIntegration, metrics, - _INTERNAL_normalizeCollectionInterval, } from '@sentry/node'; export { init } from './server/sdk'; diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 6961684ed0cb..00b14ed59235 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -159,7 +159,6 @@ export { unleashIntegration, growthbookIntegration, metrics, - _INTERNAL_normalizeCollectionInterval, } from '@sentry/node'; export { diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 734caf19de03..41f5b3cf5c52 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -177,7 +177,6 @@ export { statsigIntegration, unleashIntegration, metrics, - _INTERNAL_normalizeCollectionInterval, } from '@sentry/node'; export { diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 9cdff9674440..004c785b6ca5 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -159,7 +159,6 @@ export { statsigIntegration, unleashIntegration, metrics, - _INTERNAL_normalizeCollectionInterval, } from '@sentry/node'; export { From 158783efd6d711e2da65b78dedb3722a102402e6 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 2 Apr 2026 13:44:29 +0200 Subject: [PATCH 8/9] ci: Ignore _INTERNAL_normalizeCollectionInterval in consistent exports check This export is an internal helper consumed by integrations (e.g. bunRuntimeMetricsIntegration) and does not need to be re-exported by every package that depends on @sentry/node. Co-Authored-By: Claude Sonnet 4.6 --- .../node-exports-test-app/scripts/consistentExports.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index e762909c9173..6ae689b80da3 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -21,6 +21,8 @@ const NODE_EXPORTS_IGNORE = [ 'SentryContextManager', 'validateOpenTelemetrySetup', 'preloadOpenTelemetry', + // Internal helper only needed within integrations (e.g. bunRuntimeMetricsIntegration) + '_INTERNAL_normalizeCollectionInterval', ]; const nodeExports = Object.keys(SentryNode).filter(e => !NODE_EXPORTS_IGNORE.includes(e)); From 775d60972fb8d1f48690249faa15581c0b16d45c Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 2 Apr 2026 14:59:53 +0200 Subject: [PATCH 9/9] fix: Fall back to default interval (not minimum) when collectionIntervalMs is NaN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, non-finite values like NaN would fall back to the 1000ms minimum, which could surprise users expecting the documented default. Distinguish between two error cases: - NaN/Infinity: invalid input → fall back to the integration's default - Below minimum: too low → clamp to 1000ms minimum Update _INTERNAL_normalizeCollectionInterval to accept a defaultInterval parameter and apply it for non-finite inputs. Update deno's inline logic and all tests accordingly. Co-Authored-By: Claude Sonnet 4.6 --- .../bun/src/integrations/bunRuntimeMetrics.ts | 1 + .../integrations/bunRuntimeMetrics.test.ts | 12 ++++++++-- .../src/integrations/denoRuntimeMetrics.ts | 13 +++++++++-- .../deno/test/deno-runtime-metrics.test.ts | 3 ++- .../src/integrations/nodeRuntimeMetrics.ts | 22 +++++++++++++++---- .../integrations/nodeRuntimeMetrics.test.ts | 12 ++++++++-- 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/packages/bun/src/integrations/bunRuntimeMetrics.ts b/packages/bun/src/integrations/bunRuntimeMetrics.ts index 2130a5cf6d05..5bd4e87adbf4 100644 --- a/packages/bun/src/integrations/bunRuntimeMetrics.ts +++ b/packages/bun/src/integrations/bunRuntimeMetrics.ts @@ -67,6 +67,7 @@ export const bunRuntimeMetricsIntegration = defineIntegration((options: BunRunti const collectionIntervalMs = _INTERNAL_normalizeCollectionInterval( options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS, INTEGRATION_NAME, + DEFAULT_INTERVAL_MS, ); const collect = { // Default on diff --git a/packages/bun/test/integrations/bunRuntimeMetrics.test.ts b/packages/bun/test/integrations/bunRuntimeMetrics.test.ts index b0ff4ab48443..a03b07ffe760 100644 --- a/packages/bun/test/integrations/bunRuntimeMetrics.test.ts +++ b/packages/bun/test/integrations/bunRuntimeMetrics.test.ts @@ -231,12 +231,20 @@ describe('bunRuntimeMetricsIntegration', () => { expect(gaugeSpy).toHaveBeenCalled(); }); - it('falls back to minimum when NaN', () => { + it('falls back to default when NaN', () => { const warnSpy = spyOn(globalThis.console, 'warn').mockImplementation(() => {}); - bunRuntimeMetricsIntegration({ collectionIntervalMs: NaN }); + const integration = bunRuntimeMetricsIntegration({ collectionIntervalMs: NaN }); + integration.setup(); expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('collectionIntervalMs')); + + // Should fire at the default 30000ms, not at 1000ms + jest.advanceTimersByTime(1000); + expect(gaugeSpy).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(29_000); + expect(gaugeSpy).toHaveBeenCalled(); }); }); }); diff --git a/packages/deno/src/integrations/denoRuntimeMetrics.ts b/packages/deno/src/integrations/denoRuntimeMetrics.ts index be2fcfb19f36..54d162132db8 100644 --- a/packages/deno/src/integrations/denoRuntimeMetrics.ts +++ b/packages/deno/src/integrations/denoRuntimeMetrics.ts @@ -49,13 +49,22 @@ export interface DenoRuntimeMetricsOptions { */ export const denoRuntimeMetricsIntegration = defineIntegration((options: DenoRuntimeMetricsOptions = {}) => { const rawInterval = options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS; - if (!Number.isFinite(rawInterval) || rawInterval < MIN_INTERVAL_MS) { + let collectionIntervalMs: number; + if (!Number.isFinite(rawInterval)) { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] denoRuntimeMetricsIntegration: collectionIntervalMs (${rawInterval}) is invalid. Using default of ${DEFAULT_INTERVAL_MS}ms.`, + ); + collectionIntervalMs = DEFAULT_INTERVAL_MS; + } else if (rawInterval < MIN_INTERVAL_MS) { // eslint-disable-next-line no-console console.warn( `[Sentry] denoRuntimeMetricsIntegration: collectionIntervalMs (${rawInterval}) is below the minimum of ${MIN_INTERVAL_MS}ms. Using minimum of ${MIN_INTERVAL_MS}ms.`, ); + collectionIntervalMs = MIN_INTERVAL_MS; + } else { + collectionIntervalMs = rawInterval; } - const collectionIntervalMs = Number.isFinite(rawInterval) ? Math.max(rawInterval, MIN_INTERVAL_MS) : MIN_INTERVAL_MS; const collect = { // Default on memRss: true, diff --git a/packages/deno/test/deno-runtime-metrics.test.ts b/packages/deno/test/deno-runtime-metrics.test.ts index ee953f01c81e..48435279a954 100644 --- a/packages/deno/test/deno-runtime-metrics.test.ts +++ b/packages/deno/test/deno-runtime-metrics.test.ts @@ -135,7 +135,7 @@ Deno.test('warns and enforces minimum collectionIntervalMs', () => { assertStringIncludes(warnings[0]!, '1000'); }); -Deno.test('warns and falls back to minimum when collectionIntervalMs is NaN', () => { +Deno.test('warns and falls back to default when collectionIntervalMs is NaN', () => { const warnings: string[] = []; const originalWarn = globalThis.console.warn; globalThis.console.warn = (msg: string) => warnings.push(msg); @@ -148,4 +148,5 @@ Deno.test('warns and falls back to minimum when collectionIntervalMs is NaN', () assertEquals(warnings.length, 1); assertStringIncludes(warnings[0]!, 'collectionIntervalMs'); + assertStringIncludes(warnings[0]!, 'invalid'); }); diff --git a/packages/node-core/src/integrations/nodeRuntimeMetrics.ts b/packages/node-core/src/integrations/nodeRuntimeMetrics.ts index bac79a92efc6..5d6ab5ea7ca0 100644 --- a/packages/node-core/src/integrations/nodeRuntimeMetrics.ts +++ b/packages/node-core/src/integrations/nodeRuntimeMetrics.ts @@ -8,17 +8,30 @@ const EVENT_LOOP_DELAY_RESOLUTION_MS = 10; /** * Normalizes a `collectionIntervalMs` value, enforcing a minimum of 1000ms. - * Warns if the value is below the minimum or non-finite (e.g. NaN). + * - Non-finite values (NaN, Infinity): warns and falls back to `defaultInterval`. + * - Values below the minimum: warns and clamps to 1000ms. * @internal */ -export function _INTERNAL_normalizeCollectionInterval(rawInterval: number, integrationName: string): number { - if (!Number.isFinite(rawInterval) || rawInterval < MIN_COLLECTION_INTERVAL_MS) { +export function _INTERNAL_normalizeCollectionInterval( + rawInterval: number, + integrationName: string, + defaultInterval: number, +): number { + if (!Number.isFinite(rawInterval)) { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] ${integrationName}: collectionIntervalMs (${rawInterval}) is invalid. Using default of ${defaultInterval}ms.`, + ); + return defaultInterval; + } + if (rawInterval < MIN_COLLECTION_INTERVAL_MS) { // eslint-disable-next-line no-console console.warn( `[Sentry] ${integrationName}: collectionIntervalMs (${rawInterval}) is below the minimum of ${MIN_COLLECTION_INTERVAL_MS}ms. Using minimum of ${MIN_COLLECTION_INTERVAL_MS}ms.`, ); + return MIN_COLLECTION_INTERVAL_MS; } - return Number.isFinite(rawInterval) ? Math.max(rawInterval, MIN_COLLECTION_INTERVAL_MS) : MIN_COLLECTION_INTERVAL_MS; + return rawInterval; } export interface NodeRuntimeMetricsOptions { @@ -83,6 +96,7 @@ export const nodeRuntimeMetricsIntegration = defineIntegration((options: NodeRun const collectionIntervalMs = _INTERNAL_normalizeCollectionInterval( options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS, INTEGRATION_NAME, + DEFAULT_INTERVAL_MS, ); const collect = { // Default on diff --git a/packages/node-core/test/integrations/nodeRuntimeMetrics.test.ts b/packages/node-core/test/integrations/nodeRuntimeMetrics.test.ts index 4488ffb5f154..f5dd76edf779 100644 --- a/packages/node-core/test/integrations/nodeRuntimeMetrics.test.ts +++ b/packages/node-core/test/integrations/nodeRuntimeMetrics.test.ts @@ -349,13 +349,21 @@ describe('nodeRuntimeMetricsIntegration', () => { warnSpy.mockRestore(); }); - it('falls back to minimum when collectionIntervalMs is NaN', () => { + it('falls back to default when collectionIntervalMs is NaN', () => { const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - nodeRuntimeMetricsIntegration({ collectionIntervalMs: NaN }); + const integration = nodeRuntimeMetricsIntegration({ collectionIntervalMs: NaN }); + integration.setup(); expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('collectionIntervalMs')); + // Should fire at the default 30000ms, not at 1000ms + vi.advanceTimersByTime(1000); + expect(gaugeSpy).not.toHaveBeenCalled(); + + vi.advanceTimersByTime(29_000); + expect(gaugeSpy).toHaveBeenCalled(); + warnSpy.mockRestore(); }); });