diff --git a/apps/studio/data/empty-body-diagnostics.test.ts b/apps/studio/data/empty-body-diagnostics.test.ts index c3ec9168822..721e64d6c31 100644 --- a/apps/studio/data/empty-body-diagnostics.test.ts +++ b/apps/studio/data/empty-body-diagnostics.test.ts @@ -87,7 +87,11 @@ describe('reportEmptyBodyResponse', () => { expect(context).toMatchObject({ level: 'warning', fingerprint: ['empty-body-response', '/platform/projects/{ref}/a'], - tags: { endpoint: '/platform/projects/{ref}/a', probe_has_body: 'true' }, + tags: { + endpoint: '/platform/projects/{ref}/a', + probe_has_body: 'true', + empty_body_diagnostic: 'true', + }, extra: { method: 'GET', status: 200, diff --git a/apps/studio/data/empty-body-diagnostics.ts b/apps/studio/data/empty-body-diagnostics.ts index 6731bd035f3..412bd2ff198 100644 --- a/apps/studio/data/empty-body-diagnostics.ts +++ b/apps/studio/data/empty-body-diagnostics.ts @@ -161,7 +161,7 @@ export async function reportEmptyBodyResponse({ Sentry.captureMessage('Empty response body on successful API request', { level: 'warning', fingerprint: ['empty-body-response', endpoint], - tags: { endpoint, probe_has_body }, + tags: { endpoint, probe_has_body, empty_body_diagnostic: 'true' }, extra: { ...context, ...probeContext }, }) } catch (error) { diff --git a/packages/common/sentry.test.ts b/packages/common/sentry.test.ts index d6dafb8851f..f8f924329b0 100644 --- a/packages/common/sentry.test.ts +++ b/packages/common/sentry.test.ts @@ -51,6 +51,30 @@ describe('which errors get sent to Sentry', () => { expect(filterSentryEvent(event, enabled) === event).toBe(isSent) }) + it.each([true, 'true'])( + 'sends every Studio empty-body diagnostic without sampling: %s', + (tag) => { + const random = vi.spyOn(Math, 'random').mockReturnValue(0.99) + const event = { tags: { empty_body_diagnostic: tag } } + expect(filterSentryEvent(event, enabled)).toBe(event) + expect(event.tags).toEqual({ empty_body_diagnostic: tag, codeSampleRate: '1' }) + expect(random).not.toHaveBeenCalled() + } + ) + + it.each([false, 'false', null])( + 'still samples events whose empty-body diagnostic tag is not set: %s', + (tag) => { + vi.spyOn(Math, 'random').mockReturnValue(0.99) + expect(filterSentryEvent({ tags: { empty_body_diagnostic: tag } }, enabled)).toBeNull() + } + ) + + it('drops empty-body diagnostics when reporting is turned off', () => { + const tags = { empty_body_diagnostic: 'true' } + expect(filterSentryEvent({ tags }, { isPlatform: true, hasConsent: false })).toBeNull() + }) + it.each([undefined, false, 'false', null, 1])( 'drops errors from outside the app unless marked as a page crash: %s', (tag) => { diff --git a/packages/common/sentry.ts b/packages/common/sentry.ts index d1573a6ebae..c904ad58d6a 100644 --- a/packages/common/sentry.ts +++ b/packages/common/sentry.ts @@ -3,6 +3,7 @@ type SentryEventTags = { globalErrorBoundary?: string | number | boolean | null third_party_code?: string | number | boolean | null codeSampleRate?: string | number | boolean | null + empty_body_diagnostic?: string | number | boolean | null } } @@ -22,12 +23,18 @@ export function filterSentryEvent( const isThirdPartyOnly = event.tags?.third_party_code === true || event.tags?.third_party_code === 'true' + // Studio's once-per-session empty-body diagnostic is too rare to sample + const isUnsampled = + isErrorBoundaryCrash || + event.tags?.empty_body_diagnostic === true || + event.tags?.empty_body_diagnostic === 'true' + if (isThirdPartyOnly && !isErrorBoundaryCrash) return null - if (!isErrorBoundaryCrash && Math.random() >= NON_CRASH_ERROR_SAMPLE_RATE) return null + if (!isUnsampled && Math.random() >= NON_CRASH_ERROR_SAMPLE_RATE) return null event.tags = { ...event.tags, - codeSampleRate: isErrorBoundaryCrash ? '1' : NON_CRASH_ERROR_SAMPLE_RATE.toString(), + codeSampleRate: isUnsampled ? '1' : NON_CRASH_ERROR_SAMPLE_RATE.toString(), } return event