Skip to content

Commit

Permalink
feat: Make dedupe integration default for browser (#3730)
Browse files Browse the repository at this point in the history
* feat: Make dedupe integration default for browser

* ref: Also add dedupe as default integration
  • Loading branch information
HazAT authored Jun 25, 2021
1 parent 0b42582 commit bf52938
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 1 deletion.
201 changes: 201 additions & 0 deletions packages/browser/src/integrations/dedupe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { Event, EventProcessor, Exception, Hub, Integration, StackFrame } from '@sentry/types';

/** Deduplication filter */
export class Dedupe implements Integration {
/**
* @inheritDoc
*/
public static id: string = 'Dedupe';

/**
* @inheritDoc
*/
public name: string = Dedupe.id;

/**
* @inheritDoc
*/
private _previousEvent?: Event;

/**
* @inheritDoc
*/
public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
addGlobalEventProcessor((currentEvent: Event) => {
const self = getCurrentHub().getIntegration(Dedupe);
if (self) {
// Juuust in case something goes wrong
try {
if (self._shouldDropEvent(currentEvent, self._previousEvent)) {
return null;
}
} catch (_oO) {
return (self._previousEvent = currentEvent);
}

return (self._previousEvent = currentEvent);
}
return currentEvent;
});
}

/** JSDoc */
private _shouldDropEvent(currentEvent: Event, previousEvent?: Event): boolean {
if (!previousEvent) {
return false;
}

if (this._isSameMessageEvent(currentEvent, previousEvent)) {
return true;
}

if (this._isSameExceptionEvent(currentEvent, previousEvent)) {
return true;
}

return false;
}

/** JSDoc */
private _isSameMessageEvent(currentEvent: Event, previousEvent: Event): boolean {
const currentMessage = currentEvent.message;
const previousMessage = previousEvent.message;

// If neither event has a message property, they were both exceptions, so bail out
if (!currentMessage && !previousMessage) {
return false;
}

// If only one event has a stacktrace, but not the other one, they are not the same
if ((currentMessage && !previousMessage) || (!currentMessage && previousMessage)) {
return false;
}

if (currentMessage !== previousMessage) {
return false;
}

if (!this._isSameFingerprint(currentEvent, previousEvent)) {
return false;
}

if (!this._isSameStacktrace(currentEvent, previousEvent)) {
return false;
}

return true;
}

/** JSDoc */
private _getFramesFromEvent(event: Event): StackFrame[] | undefined {
const exception = event.exception;

if (exception) {
try {
// @ts-ignore Object could be undefined
return exception.values[0].stacktrace.frames;
} catch (_oO) {
return undefined;
}
} else if (event.stacktrace) {
return event.stacktrace.frames;
}
return undefined;
}

/** JSDoc */
private _isSameStacktrace(currentEvent: Event, previousEvent: Event): boolean {
let currentFrames = this._getFramesFromEvent(currentEvent);
let previousFrames = this._getFramesFromEvent(previousEvent);

// If neither event has a stacktrace, they are assumed to be the same
if (!currentFrames && !previousFrames) {
return true;
}

// If only one event has a stacktrace, but not the other one, they are not the same
if ((currentFrames && !previousFrames) || (!currentFrames && previousFrames)) {
return false;
}

currentFrames = currentFrames as StackFrame[];
previousFrames = previousFrames as StackFrame[];

// If number of frames differ, they are not the same
if (previousFrames.length !== currentFrames.length) {
return false;
}

// Otherwise, compare the two
for (let i = 0; i < previousFrames.length; i++) {
const frameA = previousFrames[i];
const frameB = currentFrames[i];

if (
frameA.filename !== frameB.filename ||
frameA.lineno !== frameB.lineno ||
frameA.colno !== frameB.colno ||
frameA.function !== frameB.function
) {
return false;
}
}

return true;
}

/** JSDoc */
private _getExceptionFromEvent(event: Event): Exception | undefined {
return event.exception && event.exception.values && event.exception.values[0];
}

/** JSDoc */
private _isSameExceptionEvent(currentEvent: Event, previousEvent: Event): boolean {
const previousException = this._getExceptionFromEvent(previousEvent);
const currentException = this._getExceptionFromEvent(currentEvent);

if (!previousException || !currentException) {
return false;
}

if (previousException.type !== currentException.type || previousException.value !== currentException.value) {
return false;
}

if (!this._isSameFingerprint(currentEvent, previousEvent)) {
return false;
}

if (!this._isSameStacktrace(currentEvent, previousEvent)) {
return false;
}

return true;
}

/** JSDoc */
private _isSameFingerprint(currentEvent: Event, previousEvent: Event): boolean {
let currentFingerprint = currentEvent.fingerprint;
let previousFingerprint = previousEvent.fingerprint;

// If neither event has a fingerprint, they are assumed to be the same
if (!currentFingerprint && !previousFingerprint) {
return true;
}

// If only one event has a fingerprint, but not the other one, they are not the same
if ((currentFingerprint && !previousFingerprint) || (!currentFingerprint && previousFingerprint)) {
return false;
}

currentFingerprint = currentFingerprint as string[];
previousFingerprint = previousFingerprint as string[];

// Otherwise, compare the two
try {
return !!(currentFingerprint.join('') === previousFingerprint.join(''));
} catch (_oO) {
return false;
}
}
}
1 change: 1 addition & 0 deletions packages/browser/src/integrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { TryCatch } from './trycatch';
export { Breadcrumbs } from './breadcrumbs';
export { LinkedErrors } from './linkederrors';
export { UserAgent } from './useragent';
export { Dedupe } from './dedupe';
3 changes: 2 additions & 1 deletion packages/browser/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { addInstrumentationHandler, getGlobalObject, logger, SyncPromise } from
import { BrowserOptions } from './backend';
import { BrowserClient } from './client';
import { ReportDialogOptions, wrap as internalWrap } from './helpers';
import { Breadcrumbs, GlobalHandlers, LinkedErrors, TryCatch, UserAgent } from './integrations';
import { Breadcrumbs, Dedupe, GlobalHandlers, LinkedErrors, TryCatch, UserAgent } from './integrations';

export const defaultIntegrations = [
new CoreIntegrations.InboundFilters(),
Expand All @@ -13,6 +13,7 @@ export const defaultIntegrations = [
new Breadcrumbs(),
new GlobalHandlers(),
new LinkedErrors(),
new Dedupe(),
new UserAgent(),
];

Expand Down

0 comments on commit bf52938

Please sign in to comment.