Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

window._errorCount = 0;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
sampleRate: '0',
beforeSend() {
window._errorCount++;
return null;
},
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../../utils/fixtures';
import { countEnvelopes } from '../../../../utils/helpers';

sentryTest('parses a string sample rate', async ({ getLocalTestUrl, page }) => {
sentryTest('drops error events when sampleRate is the string "0"', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const errorCountPromise = countEnvelopes(page, { envelopeType: 'event', timeout: 2000 });

await page.goto(url);

await page.waitForFunction('window._testDone');
await page.evaluate('window.Sentry.getClient().flush()');

const count = await page.evaluate('window._errorCount');

expect(count).toStrictEqual(0);
expect(await errorCountPromise).toBe(0);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
traceLifecycle: 'static',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '0.1',
sampleRate: 0,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
document.getElementById('throw-error').addEventListener('click', () => {
throw new Error('unhandled crash');
});

document.getElementById('capture-exception').addEventListener('click', () => {
Sentry.captureException(new Error('handled capture'));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<button id="throw-error">Throw Error</button>
<button id="capture-exception">Capture Exception</button>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../utils/fixtures';
import { countEnvelopes, waitForSession } from '../../../utils/helpers';

sentryTest(
'marks session as crashed when unhandled error is sampled out by sampleRate',
async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const pageloadSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const pageloadSession = await pageloadSessionPromise;

const updatedSessionPromise = waitForSession(page, s => !s.init && s.status !== 'ok');
const errorCountPromise = countEnvelopes(page, { envelopeType: 'event', timeout: 2000 });
await page.locator('#throw-error').click();
const updatedSession = await updatedSessionPromise;
const errorCount = await errorCountPromise;

// The error event is not sent — it was sampled out
expect(errorCount).toBe(0);

// But the session update is still sent, reflecting the crash
expect(updatedSession.sid).toBe(pageloadSession.sid);
expect(updatedSession.errors).toBe(1);
expect(updatedSession.status).toBe('crashed');
},
);

sentryTest(
'marks session as errored when handled exception is sampled out by sampleRate',
async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const pageloadSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const pageloadSession = await pageloadSessionPromise;

const updatedSessionPromise = waitForSession(page, s => !s.init);
const errorCountPromise = countEnvelopes(page, { envelopeType: 'event', timeout: 2000 });
await page.locator('#capture-exception').click();
const updatedSession = await updatedSessionPromise;
const errorCount = await errorCountPromise;

// The error event is not sent — it was sampled out
expect(errorCount).toBe(0);

// But the session update is still sent, recording the error
expect(updatedSession.sid).toBe(pageloadSession.sid);
expect(updatedSession.errors).toBe(1);
expect(updatedSession.status).toBe('ok');
},
);
17 changes: 7 additions & 10 deletions packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ import { makePromiseBuffer, type PromiseBuffer, SENTRY_BUFFER_FULL_ERROR } from
import { safeMathRandom } from './utils/randomSafeContext';
import { reparentChildSpans, shouldIgnoreSpan } from './utils/should-ignore-span';
import { showSpanDropWarning } from './utils/spanUtils';
import { rejectedSyncPromise } from './utils/syncpromise';
import { safeUnref } from './utils/timer';
import { convertSpanJsonToTransactionEvent, convertTransactionEventToSpanJson } from './utils/transactionEvent';
import { resolveDataCollectionOptions } from './utils/data-collection/resolveDataCollectionOptions';
Expand Down Expand Up @@ -1438,15 +1437,6 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
// 0.0 === 0% events are sent
// Sampling for transaction happens somewhere else
const parsedSampleRate = typeof sampleRate === 'undefined' ? undefined : parseSampleRate(sampleRate);
if (isError && typeof parsedSampleRate === 'number' && safeMathRandom() > parsedSampleRate) {
this.recordDroppedEvent('sample_rate', 'error');
return rejectedSyncPromise(
_makeDoNotSendEventError(
`Discarding event because it's not included in the random sample (sampling rate = ${sampleRate})`,
),
);
}

const dataCategory = getDataCategoryByType(event.type);

return this._prepareEvent(event, hint, currentScope, isolationScope)
Expand Down Expand Up @@ -1481,6 +1471,13 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
this._updateSessionFromEvent(session, processedEvent);
}

if (isError && typeof parsedSampleRate === 'number' && safeMathRandom() > parsedSampleRate) {
this.recordDroppedEvent('sample_rate', 'error');
throw _makeDoNotSendEventError(
`Discarding event because it's not included in the random sample (sampling rate = ${sampleRate})`,
);
}

if (isTransaction) {
const spanCountBefore = processedEvent.sdkProcessingMetadata?.spanCountBeforeProcessing || 0;
const spanCountAfter = processedEvent.spans ? processedEvent.spans.length : 0;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export function setConversationId(conversationId: string | null | undefined): vo
* isolation scope. If you call this function after handling a certain error and another error
* is captured in between, the last one is returned instead of the one you might expect.
* Also, ids of events that were never sent to Sentry (for example because
* they were dropped in `beforeSend`) could be returned.
* they were dropped by sampling or `beforeSend`) could be returned.
*
* @returns The last event id of the isolation scope.
*/
Expand Down
Loading
Loading