From 2b4f7c1178698a7606c7b491ec21170b547d4597 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Mon, 3 Nov 2025 14:58:18 +0530 Subject: [PATCH 1/5] feat(core): Add isolateTrace option to MonitorConfig Add optional isolateTrace boolean property to MonitorConfig interface to allow creating separate traces for each withMonitor execution. --- packages/core/src/types-hoist/checkin.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core/src/types-hoist/checkin.ts b/packages/core/src/types-hoist/checkin.ts index 9d200811183a..6d25998099ad 100644 --- a/packages/core/src/types-hoist/checkin.ts +++ b/packages/core/src/types-hoist/checkin.ts @@ -105,4 +105,9 @@ export interface MonitorConfig { failureIssueThreshold?: SerializedMonitorConfig['failure_issue_threshold']; /** How many consecutive OK check-ins it takes to resolve an issue. */ recoveryThreshold?: SerializedMonitorConfig['recovery_threshold']; + /** + * If set to true, creates a new trace for the monitor callback instead of continuing the current trace. + * This allows distinguishing between different cron job executions. + */ + isolateTrace?: boolean; } From 382fdc61129626dc4de8ba5935ef9a777224cc54 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Mon, 3 Nov 2025 14:58:37 +0530 Subject: [PATCH 2/5] feat(core): Implement isolateTrace in withMonitor function Modify withMonitor to create new traces when isolateTrace option is enabled. When isolateTrace: true, starts a new span with monitor-specific naming to allow distinguishing between different cron job executions. --- packages/core/src/exports.ts | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index a5dc716d8124..8de33aec7e22 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -15,6 +15,7 @@ import { isThenable } from './utils/is'; import { uuid4 } from './utils/misc'; import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent'; import { parseEventHintOrCaptureContext } from './utils/prepareEvent'; +import { startSpan } from './tracing/trace'; import { timestampInSeconds } from './utils/time'; import { GLOBAL_OBJ } from './utils/worldwide'; @@ -167,6 +168,43 @@ export function withMonitor( } return withIsolationScope(() => { + // If isolateTrace is enabled, start a new trace for this monitor execution + if (upsertMonitorConfig?.isolateTrace) { + return startSpan( + { + name: `monitor.${monitorSlug}`, + op: 'monitor', + forceTransaction: true, + }, + () => { + let maybePromiseResult: T; + try { + maybePromiseResult = callback(); + } catch (e) { + finishCheckIn('error'); + throw e; + } + + if (isThenable(maybePromiseResult)) { + return maybePromiseResult.then( + r => { + finishCheckIn('ok'); + return r; + }, + e => { + finishCheckIn('error'); + throw e; + }, + ) as T; + } + finishCheckIn('ok'); + + return maybePromiseResult; + }, + ); + } + + // Default behavior without isolateTrace let maybePromiseResult: T; try { maybePromiseResult = callback(); From 4dfa0af7526969e1c6db631dafccd17926ce4bc6 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Mon, 3 Nov 2025 14:58:47 +0530 Subject: [PATCH 3/5] test(core): Add tests for isolateTrace functionality Add unit tests for the new isolateTrace option in withMonitor: - Test isolateTrace: true option acceptance - Test isolateTrace: false maintains default behavior - Test isolateTrace works with async operations --- packages/core/test/lib/client.test.ts | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index ae324aa40f9f..8ac3d2487d5d 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -2600,6 +2600,43 @@ describe('Client', () => { const promise = await withMonitor('test-monitor', callback); await expect(promise).rejects.toThrowError(error); }); + + test('accepts isolateTrace option without error', () => { + const result = 'foo'; + const callback = vi.fn().mockReturnValue(result); + + const returnedResult = withMonitor('test-monitor', callback, { + schedule: { type: 'crontab', value: '* * * * *' }, + isolateTrace: true + }); + + expect(returnedResult).toBe(result); + expect(callback).toHaveBeenCalledTimes(1); + }); + + test('works with isolateTrace set to false', () => { + const result = 'foo'; + const callback = vi.fn().mockReturnValue(result); + + const returnedResult = withMonitor('test-monitor', callback, { + schedule: { type: 'crontab', value: '* * * * *' }, + isolateTrace: false + }); + + expect(returnedResult).toBe(result); + expect(callback).toHaveBeenCalledTimes(1); + }); + + test('handles isolateTrace with asynchronous operations', async () => { + const result = 'foo'; + const callback = vi.fn().mockResolvedValue(result); + + const promise = withMonitor('test-monitor', callback, { + schedule: { type: 'crontab', value: '* * * * *' }, + isolateTrace: true + }); + await expect(promise).resolves.toEqual(result); + }); }); describe('log weight-based flushing', () => { From e06ae3044506cd46b88067d479151134672388c2 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Fri, 7 Nov 2025 15:32:59 +0530 Subject: [PATCH 4/5] feat: Update isolateTrace implementation to use startNewTrace Update withMonitor to use startNewTrace() instead of startSpan() when isolateTrace is enabled, following PR feedback to only isolate traces without creating spans. --- packages/core/src/exports.ts | 59 ++++++++++++--------------- packages/core/test/lib/client.test.ts | 1 + 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 8de33aec7e22..5476aa69014b 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -15,7 +15,7 @@ import { isThenable } from './utils/is'; import { uuid4 } from './utils/misc'; import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent'; import { parseEventHintOrCaptureContext } from './utils/prepareEvent'; -import { startSpan } from './tracing/trace'; +import { startNewTrace } from './tracing/trace'; import { timestampInSeconds } from './utils/time'; import { GLOBAL_OBJ } from './utils/worldwide'; @@ -170,38 +170,31 @@ export function withMonitor( return withIsolationScope(() => { // If isolateTrace is enabled, start a new trace for this monitor execution if (upsertMonitorConfig?.isolateTrace) { - return startSpan( - { - name: `monitor.${monitorSlug}`, - op: 'monitor', - forceTransaction: true, - }, - () => { - let maybePromiseResult: T; - try { - maybePromiseResult = callback(); - } catch (e) { - finishCheckIn('error'); - throw e; - } - - if (isThenable(maybePromiseResult)) { - return maybePromiseResult.then( - r => { - finishCheckIn('ok'); - return r; - }, - e => { - finishCheckIn('error'); - throw e; - }, - ) as T; - } - finishCheckIn('ok'); - - return maybePromiseResult; - }, - ); + return startNewTrace(() => { + let maybePromiseResult: T; + try { + maybePromiseResult = callback(); + } catch (e) { + finishCheckIn('error'); + throw e; + } + + if (isThenable(maybePromiseResult)) { + return maybePromiseResult.then( + r => { + finishCheckIn('ok'); + return r; + }, + e => { + finishCheckIn('error'); + throw e; + }, + ) as T; + } + finishCheckIn('ok'); + + return maybePromiseResult; + }); } // Default behavior without isolateTrace diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index 8ac3d2487d5d..10f8d3a39c51 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -11,6 +11,7 @@ import { SyncPromise, withMonitor, } from '../../src'; +import { startNewTrace } from '../../src/tracing'; import * as integrationModule from '../../src/integration'; import { _INTERNAL_captureLog } from '../../src/logs/internal'; import { _INTERNAL_captureMetric } from '../../src/metrics/internal'; From a16ae77c8ce32227daac87841cd3781269483ed9 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Fri, 7 Nov 2025 15:33:21 +0530 Subject: [PATCH 5/5] test: Add integration test for isolateTrace functionality Add node integration test to verify that withMonitor calls with isolateTrace: true generate different trace IDs, confirming trace isolation works as expected. --- .../suites/public-api/withMonitor/scenario.ts | 28 +++++++++++++++++++ .../suites/public-api/withMonitor/test.ts | 19 +++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 dev-packages/node-integration-tests/suites/public-api/withMonitor/scenario.ts create mode 100644 dev-packages/node-integration-tests/suites/public-api/withMonitor/test.ts diff --git a/dev-packages/node-integration-tests/suites/public-api/withMonitor/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/withMonitor/scenario.ts new file mode 100644 index 000000000000..9a24274f9ff9 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/withMonitor/scenario.ts @@ -0,0 +1,28 @@ +import { withMonitor } from '@sentry/node'; + +export async function run(): Promise { + // First withMonitor call without isolateTrace (should share trace) + await withMonitor('cron-job-1', async () => { + // Simulate some work + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 100); + }); + }, { + schedule: { type: 'crontab', value: '* * * * *' } + }); + + // Second withMonitor call with isolateTrace (should have different trace) + await withMonitor('cron-job-2', async () => { + // Simulate some work + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 100); + }); + }, { + schedule: { type: 'crontab', value: '* * * * *' }, + isolateTrace: true + }); +} diff --git a/dev-packages/node-integration-tests/suites/public-api/withMonitor/test.ts b/dev-packages/node-integration-tests/suites/public-api/withMonitor/test.ts new file mode 100644 index 000000000000..43fa1b4e1ba8 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/withMonitor/test.ts @@ -0,0 +1,19 @@ +import { expect } from 'vitest'; +import type { Event, TransactionEvent } from '@sentry/types'; + +export async function testResults(events: Event[]): Promise { + // Get all transaction events (which represent traces) + const transactionEvents = events.filter((event): event is TransactionEvent => event.type === 'transaction'); + + // Should have at least 2 transaction events (one for each withMonitor call) + expect(transactionEvents.length).toBeGreaterThanOrEqual(2); + + // Get trace IDs from the transactions + const traceIds = transactionEvents.map(event => event.contexts?.trace?.trace_id).filter(Boolean); + + // Should have at least 2 different trace IDs (verifying trace isolation) + const uniqueTraceIds = [...new Set(traceIds)]; + expect(uniqueTraceIds.length).toBeGreaterThanOrEqual(2); + + console.log('✅ Found traces with different trace IDs:', uniqueTraceIds); +}