Skip to content

Commit fd0f3bb

Browse files
committed
Start work to make the deduplication plugin work like the .NET one
1 parent b45a092 commit fd0f3bb

10 files changed

+126
-75
lines changed

dist/exceptionless.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface IEvent {
88
value?: number;
99
data?: any;
1010
reference_id?: string;
11+
count?: number;
1112
}
1213
export declare class SubmissionResponse {
1314
success: boolean;
@@ -428,8 +429,10 @@ export declare class DuplicateCheckerPlugin implements IEventPlugin {
428429
name: string;
429430
private _processedHashcodes;
430431
private _getCurrentTime;
431-
constructor(getCurrentTime?: () => number);
432+
constructor(getCurrentTime?: () => number, interval?: number);
432433
run(context: EventPluginContext, next?: () => void): void;
434+
private onInterval();
435+
private enqueueMergedEvents();
433436
}
434437
export declare class EventExclusionPlugin implements IEventPlugin {
435438
priority: number;

dist/exceptionless.js

Lines changed: 19 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/exceptionless.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/exceptionless.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/exceptionless.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/exceptionless.node.js

Lines changed: 19 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/exceptionless.node.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/models/IEvent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export interface IEvent {
88
value?: number;
99
data?: any;
1010
reference_id?: string;
11+
count?: number;
1112
}

src/plugins/default/DuplicateCheckerPlugin-spec.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { ContextData } from '../ContextData';
2+
import { DefaultErrorParser } from '../../services/DefaultErrorParser';
3+
import { ExceptionlessClient } from '../../ExceptionlessClient';
4+
import { IEvent } from '../../models/IEvent';
25
import { EventPluginContext } from '../EventPluginContext';
36
import { DuplicateCheckerPlugin } from './DuplicateCheckerPlugin';
47
import { ErrorPlugin } from './ErrorPlugin';
@@ -7,33 +10,39 @@ import { expect } from 'chai';
710

811
describe('DuplicateCheckerPlugin', () => {
912
let now: number = 0;
13+
let client: ExceptionlessClient;
1014
let plugin: DuplicateCheckerPlugin;
1115

1216
beforeEach(() => {
13-
plugin = new DuplicateCheckerPlugin(() => now);
17+
client = new ExceptionlessClient('LhhP1C9gijpSKCslHHCvwdSIz298twx271n1l6xw', 'http://localhost:50000');
18+
plugin = new DuplicateCheckerPlugin(() => now, 50);
1419
});
1520

1621
function run(exception: Error) {
17-
let context: EventPluginContext;
18-
let contextData: ContextData;
19-
({ context, contextData } = createFixture());
22+
let errorParser = new DefaultErrorParser();
23+
let context = new EventPluginContext(client, { type: 'error', data: {} });
24+
context.event.data['@error'] = errorParser.parse(context, exception);
2025

21-
contextData.setException(exception);
22-
23-
let errorPlugin = new ErrorPlugin();
24-
errorPlugin.run(context);
2526
plugin.run(context);
2627

2728
return context;
2829
}
2930

30-
it('should ignore duplicate within window', () => {
31+
it('should ignore duplicate within window', (done) => {
3132
let exception = createException([{
3233
name: 'methodA'
3334
}]);
3435
run(exception);
36+
3537
let contextOfSecondRun = run(exception);
3638
expect(contextOfSecondRun.cancelled).to.be.true;
39+
setTimeout(() => {
40+
41+
expect(contextOfSecondRun.event.count).to.equal(1);
42+
43+
done();
44+
}, 100);
45+
3746
});
3847

3948
it('shouldn\'t ignore error without stack', () => {
@@ -68,8 +77,11 @@ describe('DuplicateCheckerPlugin', () => {
6877
});
6978

7079
function createException(stack?) {
80+
function throwError() {
81+
throw new ReferenceError('This is a test');
82+
}
7183
try {
72-
throw new Error();
84+
throwError();
7385
} catch (e) {
7486
e.testStack = stack;
7587
return e;

src/plugins/default/DuplicateCheckerPlugin.ts

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,50 @@ import { EventPluginContext } from '../EventPluginContext';
55
import { Utils } from '../../Utils';
66

77
export class DuplicateCheckerPlugin implements IEventPlugin {
8-
public priority: number = 40;
8+
public priority: number = 90;
99
public name: string = 'DuplicateCheckerPlugin';
1010

11+
private _mergedEvents: MergedEvent[] = [];
1112
private _processedHashcodes: TimestampedHash[] = [];
1213
private _getCurrentTime: () => number;
14+
private _interval: number;
1315

14-
constructor(getCurrentTime: () => number = () => Date.now()) {
16+
constructor(getCurrentTime: () => number = () => Date.now(), interval: number = 60000) {
1517
this._getCurrentTime = getCurrentTime;
18+
this._interval = interval;
19+
20+
setInterval(() => {
21+
while(this._mergedEvents.length > 0){
22+
this._mergedEvents.shift().resubmit();
23+
}
24+
}, interval);
1625
}
1726

1827
public run(context: EventPluginContext, next?: () => void): void {
19-
function isDuplicate(error: IInnerError, processedHashcodes, now, log: ILog): boolean {
20-
while (error) {
21-
let hashCode = Utils.getHashCode(error.stack_trace && JSON.stringify(error.stack_trace));
22-
23-
// Only process the unique errors times within a 2 second window.
24-
if (hashCode && processedHashcodes.some(h => h.hash === hashCode && h.timestamp >= (now - 2000))) {
25-
log.info(`Ignoring duplicate error event hash: ${hashCode}`);
26-
return true;
27-
}
28+
let hashCode = Utils.getHashCode(JSON.stringify(context.event.data['@error'], ['stack_trace', 'inner']));
29+
let count = context.event.count || 1;
2830

29-
// Add this exception to our list of recent processed errors.
30-
processedHashcodes.push({ hash: hashCode, timestamp: now });
31+
let now = this._getCurrentTime();
3132

32-
// Only keep the last 20 recent errors.
33-
while (processedHashcodes.length > 20) {
34-
processedHashcodes.shift();
35-
}
36-
37-
error = error.inner;
38-
}
33+
let merged = this._mergedEvents.filter(s => s.hashCode === hashCode)[0];
34+
if(merged) {
35+
merged.incrementCount(count);
36+
merged.updateDate(context.event.date);
37+
context.cancelled = true;
38+
return;
39+
}
3940

40-
return false;
41+
if(this._processedHashcodes.some(h => h.hash === hashCode && h.timestamp >= (now - this._interval))) {
42+
this._mergedEvents.push(new MergedEvent(hashCode, context, count));
43+
context.cancelled = true;
44+
return;
4145
}
4246

43-
if (context.event.type === 'error') {
44-
if (isDuplicate(context.event.data['@error'], this._processedHashcodes, this._getCurrentTime(), context.log)) {
45-
context.cancelled = true;
46-
return;
47-
}
47+
this._processedHashcodes.push({ hash: hashCode, timestamp: now });
48+
49+
// Only keep the last 50 recent errors.
50+
while (this._processedHashcodes.length > 50) {
51+
this._processedHashcodes.shift();
4852
}
4953

5054
next && next();
@@ -55,3 +59,30 @@ interface TimestampedHash {
5559
hash: number;
5660
timestamp: number;
5761
}
62+
63+
class MergedEvent {
64+
private _count: number;
65+
private _context: EventPluginContext;
66+
public hashCode: number;
67+
68+
constructor(hashCode: number, context: EventPluginContext, count: number){
69+
this.hashCode = hashCode;
70+
this._context = context;
71+
this._count = count;
72+
}
73+
74+
public incrementCount(count: number){
75+
this._count += count;
76+
}
77+
78+
public resubmit() {
79+
this._context.event.count = this._count;
80+
this._context.client.config.queue.enqueue(this._context.event);
81+
}
82+
83+
public updateDate(date) {
84+
if(date > this._context.event.date) {
85+
this._context.event.date = date;
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)