Skip to content

Implemented DuplicateChecker #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 17, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Implemented DuplicateChecker
  • Loading branch information
frankebersoll committed Nov 11, 2015
commit 3002b6da18dce27c749fce42a3ffe236cc08c51c
83 changes: 83 additions & 0 deletions src/plugins/default/DuplicateCheckerPlugin-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ContextData } from '../ContextData';
import { EventPluginContext } from '../EventPluginContext';
import { DuplicateCheckerPlugin } from './DuplicateCheckerPlugin';
import { ErrorPlugin } from './ErrorPlugin';
import { createFixture } from './EventPluginTestFixture';

describe('DuplicateCheckerPlugin', () => {

let target: DuplicateCheckerPlugin;
let now: number;

beforeEach(() => {
target = new DuplicateCheckerPlugin();
(<any>target).getNow = () => now;
now = 0;
});

function run(exception: Error) {
let context: EventPluginContext;
let contextData: ContextData;
({
context,
contextData
} = createFixture());

contextData.setException(exception);

let errorPlugin = new ErrorPlugin();
errorPlugin.run(context);
target.run(context);

return context;
}

it('should ignore duplicate within window', () => {
let exception = createException([{
name: "methodA"
}]);
run(exception);
let contextOfSecondRun = run(exception);
expect(contextOfSecondRun.cancelled).toBeTruthy();
});

it('shouldn\'t ignore error without stack', () => {
let exception = createException();
run(exception);
let contextOfSecondRun = run(exception);
expect(contextOfSecondRun.cancelled).toBeFalsy();
});

it('shouldn\'t ignore different stack within window', () => {
let exception1 = createException([{
name: "methodA"
}]);
run(exception1);
let exception2 = createException([{
name: "methodB"
}]);
let contextOfSecondRun = run(exception2);
expect(contextOfSecondRun.cancelled).toBeFalsy();
});

it('shouldn\'t ignore duplicate after window', () => {
let exception = createException([{
name: "methodA"
}]);
run(exception);

now = 3000;
let contextOfSecondRun = run(exception);
expect(contextOfSecondRun.cancelled).toBeFalsy();
});
});

function createException(stack?) {
try {
throw new Error();
}
catch (e) {
e.testStack = stack;
return e;
}
}
87 changes: 87 additions & 0 deletions src/plugins/default/DuplicateCheckerPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { IInnerError } from '../../models/IInnerError';
import { IStackFrame } from '../../models/IStackFrame';

import { ILog } from '../../logging/ILog';

import { IEventPlugin } from '../IEventPlugin';
import { EventPluginContext } from '../EventPluginContext';

const ERROR_KEY: string = '@error';
const WINDOW_MILLISECONDS = 2000;
const MAX_QUEUE_LENGTH = 10;

export class DuplicateCheckerPlugin implements IEventPlugin {
public priority: number = 40;
public name: string = 'DuplicateCheckerPlugin';

private recentlyProcessedErrors: TimestampedHash[] = [];

public run(context: EventPluginContext, next?: () => void): void {
if (context.event.type === 'error') {
let error = context.event.data[ERROR_KEY];
let isDuplicate = this.checkDuplicate(error, context.log);
if (isDuplicate) {
context.cancelled = true;
return;
}
}

next && next();
}

private getNow() {
return Date.now();
}

private checkDuplicate(error: IInnerError, log: ILog): boolean {
let now = this.getNow();
let repeatWindow = now - WINDOW_MILLISECONDS;
let hashCode: number;
while (error) {
hashCode = getHashCodeForError(error);

// make sure that we don't process the same error multiple times within the repeat window
if (hashCode && this.recentlyProcessedErrors.some(h =>
h.hash == hashCode && h.timestamp >= repeatWindow)) {
log.info(`Ignoring duplicate error event: hash=${hashCode}`);
return true;
}

// add this exception to our list of recent errors that we have processed
this.recentlyProcessedErrors.push({ hash: hashCode, timestamp: now });

// only keep the last 10 recent errors
while (this.recentlyProcessedErrors.length > MAX_QUEUE_LENGTH) {
this.recentlyProcessedErrors.shift();
}

error = error.inner;
}

return false;
}
}

interface TimestampedHash {
hash: number;
timestamp: number
}

function getHashCodeForError(error: IInnerError): number {
if (!error.stack_trace) {
return null;
}

let stack = JSON.stringify(error.stack_trace);
return getHashCode(stack);
}

function getHashCode(s: string): number {
let hash = 0, length = s.length, char;
for (let i = 0; i < length; i++) {
char = s.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0;
}
return hash;
}
2 changes: 1 addition & 1 deletion src/plugins/default/EnvironmentInfoPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EventPluginContext } from '../EventPluginContext';
import { IEnvironmentInfo } from '../../models/IEnvironmentInfo';

export class EnvironmentInfoPlugin implements IEventPlugin {
public priority:number = 70;
public priority:number = 80;
public name:string = 'EnvironmentInfoPlugin';

public run(context:EventPluginContext, next?:() => void): void {
Expand Down
23 changes: 6 additions & 17 deletions src/plugins/default/ErrorPlugin-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IErrorParser } from '../../services/IErrorParser';

import { ErrorPlugin } from './ErrorPlugin';
import { CapturedExceptions } from './ErrorPlugin-spec-exceptions';

import { createFixture } from './EventPluginTestFixture';

function BaseTestError() {
this.name = 'NotImplementedError';
Expand All @@ -31,22 +31,11 @@ describe('ErrorPlugin', () => {
let event: IEvent;

beforeEach(() => {
errorParser = {
parse: (c: EventPluginContext, exception: Error) => ({
type: exception.name,
message: exception.message
})
};
client = {
config: {
errorParser
}
};
event = {
data: {}
};
contextData = new ContextData();
context = new EventPluginContext(client, event, contextData);
({
contextData,
context,
client,
event} = createFixture());
});

function processError(error) {
Expand Down
42 changes: 42 additions & 0 deletions src/plugins/default/EventPluginTestFixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ContextData } from '../ContextData';
import { EventPluginContext } from '../EventPluginContext';
import { IEvent } from '../../models/IEvent';
import { IError } from '../../models/IError';
import { IErrorParser } from '../../services/IErrorParser';
import { IStackFrame } from '../../models/IStackFrame';

export function createFixture() {
let contextData: ContextData;
let context: EventPluginContext;
let errorParser: IErrorParser;
let client: any;
let event: IEvent;

errorParser = {
parse: (c: EventPluginContext, exception: Error) => ({
type: exception.name,
message: exception.message,
stack_trace: (<any>exception).testStack || null
})
};
client = {
config: {
errorParser,
log: {
info: () => { }
}
}
};
event = {
data: {}
};
contextData = new ContextData();
context = new EventPluginContext(client, event, contextData);

return {
contextData,
context,
client,
event
}
}
2 changes: 1 addition & 1 deletion src/plugins/default/ModuleInfoPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EventPluginContext } from '../EventPluginContext';
import { IModule } from '../../models/IModule';

export class ModuleInfoPlugin implements IEventPlugin {
public priority:number = 40;
public priority:number = 50;
public name:string = 'ModuleInfoPlugin';

public run(context:EventPluginContext, next?:() => void): void {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/default/RequestInfoPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EventPluginContext } from '../EventPluginContext';
import { IRequestInfo } from '../../models/IRequestInfo';

export class RequestInfoPlugin implements IEventPlugin {
public priority:number = 60;
public priority:number = 70;
public name:string = 'RequestInfoPlugin';

public run(context:EventPluginContext, next?:() => void): void {
Expand Down