Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions packages/core/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -167,6 +168,43 @@ export function withMonitor<T>(
}

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;
},
);
}
Copy link

Choose a reason for hiding this comment

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

Bug: Isolated Trace Creation Failing in Monitored Spans

withMonitor(..., { isolateTrace: true }) does not actually start a new trace when an active span exists. It calls startSpan({ forceTransaction: true }) without resetting the propagation context or explicitly nulling the parent, so startSpan continues the existing traceId if a parent span is present (per createChildOrRootSpan’s parentSpan && forceTransaction branch). This contradicts the intent to create a separate trace per monitor execution. To truly isolate, start a new trace (e.g., via startNewTrace) before creating the monitor span (or set a new propagation context/parentSpan: null with a new traceId).

Fix in Cursor Fix in Web


// Default behavior without isolateTrace
let maybePromiseResult: T;
try {
maybePromiseResult = callback();
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/types-hoist/checkin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
37 changes: 37 additions & 0 deletions packages/core/test/lib/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down