Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
transport: loggingTransport,
});

// eslint-disable-next-line @sentry-internal/sdk/no-floating-promises
Sentry.withMonitor('cron-job-1', async () => {
await new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, 100);
});
}, {
schedule: { type: 'crontab', value: '* * * * *' },
});

// eslint-disable-next-line @sentry-internal/sdk/no-floating-promises
Sentry.withMonitor('cron-job-2', async () => {
await new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, 100);
});
}, {
schedule: { type: 'crontab', value: '* * * * *' },
isolateTrace: true,
});

setTimeout(() => {
process.exit();
}, 500);
Copy link
Member

Choose a reason for hiding this comment

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

I'm afraid this is not gonna work because it doesn't use our test runner at all. Did you test this?

I suggest taking a look at a test like this one and using the same runner, just that you assert on two transactions and check for distinct trace ids.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { SerializedCheckIn } from '@sentry/core';
import { afterAll, describe, expect, test } from 'vitest';
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';

describe('withMonitor isolateTrace', () => {
afterAll(() => {
cleanupChildProcesses();
});

test('creates distinct traces when isolateTrace is enabled', async () => {
const checkIns: SerializedCheckIn[] = [];

await createRunner(__dirname, 'scenario.ts')
.expect({
check_in: checkIn => {
checkIns.push(checkIn);
},
})
.expect({
check_in: checkIn => {
checkIns.push(checkIn);
},
})
.expect({
check_in: checkIn => {
checkIns.push(checkIn);
},
})
.expect({
check_in: checkIn => {
checkIns.push(checkIn);
},
})
.start()
.completed();

// Find the two 'ok' check-ins for comparison
const checkIn1Ok = checkIns.find(c => c.monitor_slug === 'cron-job-1' && c.status === 'ok');
const checkIn2Ok = checkIns.find(c => c.monitor_slug === 'cron-job-2' && c.status === 'ok');

expect(checkIn1Ok).toBeDefined();
expect(checkIn2Ok).toBeDefined();

// Verify both check-ins have trace contexts
expect(checkIn1Ok!.contexts?.trace?.trace_id).toBeDefined();
expect(checkIn2Ok!.contexts?.trace?.trace_id).toBeDefined();

// The key assertion: trace IDs should be different when isolateTrace is enabled
expect(checkIn1Ok!.contexts!.trace!.trace_id).not.toBe(checkIn2Ok!.contexts!.trace!.trace_id);
});
});
31 changes: 31 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 { startNewTrace } from './tracing/trace';
import { timestampInSeconds } from './utils/time';
import { GLOBAL_OBJ } from './utils/worldwide';

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

return withIsolationScope(() => {
// If isolateTrace is enabled, start a new trace for this monitor execution
if (upsertMonitorConfig?.isolateTrace) {
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
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;
}
38 changes: 38 additions & 0 deletions packages/core/test/lib/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,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';
Expand Down Expand Up @@ -2732,6 +2733,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('enableLogs', () => {
Expand Down
Loading