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(); 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; } 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', () => {