From 4b923c82bfa471766de0ec6f19fd773b6fd93499 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 14 Feb 2023 10:37:34 +0100 Subject: [PATCH] fix(browser): Ensure dedupe integration ignores non-errors --- packages/browser/src/integrations/dedupe.ts | 6 +++ .../browser/test/integration/suites/api.js | 14 ++++++ packages/integrations/src/dedupe.ts | 6 +++ packages/integrations/test/dedupe.test.ts | 48 ++++++++++++++++++- 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/browser/src/integrations/dedupe.ts b/packages/browser/src/integrations/dedupe.ts index 84d6d983812b..c9903eaadf92 100644 --- a/packages/browser/src/integrations/dedupe.ts +++ b/packages/browser/src/integrations/dedupe.ts @@ -23,6 +23,12 @@ export class Dedupe implements Integration { */ public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { const eventProcessor: EventProcessor = currentEvent => { + // We want to ignore any non-error type events, e.g. transactions or replays + // These should never be deduped, and also not be compared against as _previousEvent. + if (currentEvent.type) { + return currentEvent; + } + const self = getCurrentHub().getIntegration(Dedupe); if (self) { // Juuust in case something goes wrong diff --git a/packages/browser/test/integration/suites/api.js b/packages/browser/test/integration/suites/api.js index be5de29abd0e..00ea7d58cd0d 100644 --- a/packages/browser/test/integration/suites/api.js +++ b/packages/browser/test/integration/suites/api.js @@ -112,12 +112,26 @@ describe('API', function () { // Same exceptions, different stacktrace (different line number), don't dedupe throwSameConsecutiveErrors('bar'); + + // Same exception, with transaction in between, dedupe + throwError(); + Sentry.captureEvent({ + event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', + message: 'someMessage', + transaction: 'wat', + type: 'transaction', + }); + throwError(); }).then(function (summary) { + // We have a length of one here since transactions don't go through beforeSend + // and we add events to summary in beforeSend + assert.equal(summary.events.length, 6); assert.match(summary.events[0].exception.values[0].value, /Exception no \d+/); assert.match(summary.events[1].exception.values[0].value, /Exception no \d+/); assert.equal(summary.events[2].exception.values[0].value, 'foo'); assert.equal(summary.events[3].exception.values[0].value, 'bar'); assert.equal(summary.events[4].exception.values[0].value, 'bar'); + assert.equal(summary.events[5].exception.values[0].value, 'foo'); }); }); diff --git a/packages/integrations/src/dedupe.ts b/packages/integrations/src/dedupe.ts index 625cc88f504a..8cb52295abc8 100644 --- a/packages/integrations/src/dedupe.ts +++ b/packages/integrations/src/dedupe.ts @@ -23,6 +23,12 @@ export class Dedupe implements Integration { */ public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { const eventProcessor: EventProcessor = currentEvent => { + // We want to ignore any non-error type events, e.g. transactions or replays + // These should never be deduped, and also not be compared against as _previousEvent. + if (currentEvent.type) { + return currentEvent; + } + const self = getCurrentHub().getIntegration(Dedupe); if (self) { // Juuust in case something goes wrong diff --git a/packages/integrations/test/dedupe.test.ts b/packages/integrations/test/dedupe.test.ts index 33704907dc83..7ffc30d1bdcf 100644 --- a/packages/integrations/test/dedupe.test.ts +++ b/packages/integrations/test/dedupe.test.ts @@ -1,6 +1,6 @@ -import type { Event as SentryEvent, Exception, StackFrame, Stacktrace } from '@sentry/types'; +import type { Event as SentryEvent, EventProcessor, Exception, Hub, StackFrame, Stacktrace } from '@sentry/types'; -import { _shouldDropEvent } from '../src/dedupe'; +import { _shouldDropEvent, Dedupe } from '../src/dedupe'; type EventWithException = SentryEvent & { exception: { @@ -175,4 +175,48 @@ describe('Dedupe', () => { expect(_shouldDropEvent(eventB, eventC)).toBe(false); }); }); + + describe('setupOnce', () => { + let dedupeFunc: EventProcessor; + + beforeEach(function () { + const integration = new Dedupe(); + const addGlobalEventProcessor = (callback: EventProcessor) => { + dedupeFunc = callback; + }; + + const getCurrentHub = () => { + return { + getIntegration() { + return integration; + }, + } as unknown as Hub; + }; + + integration.setupOnce(addGlobalEventProcessor, getCurrentHub); + }); + + it('ignores consecutive errors', () => { + expect(dedupeFunc(clone(exceptionEvent), {})).not.toBeNull(); + expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull(); + expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull(); + }); + + it('ignores transactions between errors', () => { + expect(dedupeFunc(clone(exceptionEvent), {})).not.toBeNull(); + expect( + dedupeFunc( + { + event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', + message: 'someMessage', + transaction: 'wat', + type: 'transaction', + }, + {}, + ), + ).not.toBeNull(); + expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull(); + expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull(); + }); + }); });