Skip to content

Commit ac556aa

Browse files
committed
Attempt to fix an issue where data items are not excluded properly.
We still need to implement depth on stringily and make sure request info like cookies, query strings, post_data are being checked for excluded items. @frank, I moved some things to functions as it takes it off the prototype (shrinks code at the expense of uglier code). Also, I was trying to see if we could share the stringify code for extra error properties but we might need to revert that change.
1 parent f924985 commit ac556aa

11 files changed

+75
-92
lines changed

src/EventBuilder.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,15 @@ export class EventBuilder {
9999
return this;
100100
}
101101

102-
public setProperty(name:string, value:any): EventBuilder {
102+
/**
103+
* Adds the object to extended data. Uses @excludedPropertyNames
104+
* to exclude data from being included in the event.
105+
* @param name The data object to add.
106+
* @param value The name of the object to add.
107+
* @param maxDepth The max depth of the object to include.
108+
* @param excludedPropertyNames Any property names that should be excluded.
109+
*/
110+
public setProperty(name:string, value:any, maxDepth?:number, excludedPropertyNames?:string[]): EventBuilder {
103111
if (!name || (value === undefined || value == null)) {
104112
return this;
105113
}
@@ -108,7 +116,7 @@ export class EventBuilder {
108116
this.target.data = {};
109117
}
110118

111-
this.target.data[name] = value;
119+
this.target.data[name] = JSON.parse(Utils.stringify(value, this.client.config.dataExclusions.concat(excludedPropertyNames || []), maxDepth));
112120
return this;
113121
}
114122

src/ExceptionlessClient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ export class ExceptionlessClient {
176176
* @param referenceId The reference id of the event to update.
177177
* @param email The user's email address to set on the event.
178178
* @param description The user's description of the event.
179+
* @param callback The submission response.
179180
*/
180181
public updateUserEmailAndDescription(referenceId:string, email:string, description:string, callback?:(response:SubmissionResponse) => void) {
181182
if (!referenceId || !email || !description || !this.config.enabled) {

src/Utils-spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe('Utils', () => {
9595
});
9696

9797
it('*Address', () => {
98-
let event = {"type":"usage","source":"about" };
98+
let event = { type:'usage', source:'about' };
9999
expect(Utils.stringify(event, ['*Address'])).toBe(JSON.stringify(event));
100100
});
101101
});

src/Utils.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ export class Utils {
1717
return target;
1818
}
1919

20-
public static getHashCode(source:string): string {
20+
public static getHashCode(source:string): number {
2121
if (!source || source.length === 0) {
22-
return null;
22+
return 0;
2323
}
2424

2525
let hash:number = 0;
@@ -29,7 +29,7 @@ export class Utils {
2929
hash |= 0;
3030
}
3131

32-
return hash.toString();
32+
return hash;
3333
}
3434

3535
public static getCookies(cookies:string): Object {
@@ -107,7 +107,13 @@ export class Utils {
107107
return Math.floor(Math.random() * 9007199254740992);
108108
}
109109

110-
public static stringify(data:any, exclusions?:string[]): string {
110+
/**
111+
* Stringifys an object with optional exclusions and max depth.
112+
* @param data The data object to add.
113+
* @param exclusions Any property names that should be excluded.
114+
* @param maxDepth The max depth of the object to include.
115+
*/
116+
public static stringify(data:any, exclusions?:string[], maxDepth?:number): string {
111117
function checkForMatch(pattern:string, value:string): boolean {
112118
if (!pattern || !value || typeof value !== 'string') {
113119
return false;

src/plugins/default/ConfigurationDefaultsPlugin.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
import { IEventPlugin } from '../IEventPlugin';
22
import { EventPluginContext } from '../EventPluginContext';
3+
import { Utils } from '../../Utils';
34

45
export class ConfigurationDefaultsPlugin implements IEventPlugin {
56
public priority:number = 10;
67
public name:string = 'ConfigurationDefaultsPlugin';
78

89
public run(context:EventPluginContext, next?:() => void): void {
9-
let defaultTags:string[] = context.client.config.defaultTags || [];
10+
let config = context.client.config;
11+
let defaultTags:string[] = config.defaultTags || [];
1012
for (let index = 0; index < defaultTags.length; index++) {
1113
let tag = defaultTags[index];
1214
if (!!tag && context.event.tags.indexOf(tag) < 0) {
1315
context.event.tags.push(tag);
1416
}
1517
}
1618

17-
let defaultData:Object = context.client.config.defaultData || {};
19+
let defaultData:Object = config.defaultData || {};
1820
for (let key in defaultData) {
1921
if (!!defaultData[key]) {
20-
context.event.data[key] = defaultData[key];
22+
context.event.data[key] = JSON.parse(Utils.stringify(defaultData[key], config.dataExclusions));
2123
}
2224
}
2325

Lines changed: 36 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { IInnerError } from '../../models/IInnerError';
22
import { IStackFrame } from '../../models/IStackFrame';
3-
43
import { ILog } from '../../logging/ILog';
5-
64
import { IEventPlugin } from '../IEventPlugin';
75
import { EventPluginContext } from '../EventPluginContext';
6+
import { Utils } from '../../Utils';
87

98
const ERROR_KEY: string = '@error';
109
const WINDOW_MILLISECONDS = 2000;
@@ -17,71 +16,56 @@ export class DuplicateCheckerPlugin implements IEventPlugin {
1716
private recentlyProcessedErrors: TimestampedHash[] = [];
1817

1918
public run(context: EventPluginContext, next?: () => void): void {
20-
if (context.event.type === 'error') {
21-
let error = context.event.data[ERROR_KEY];
22-
let isDuplicate = this.checkDuplicate(error, context.log);
23-
if (isDuplicate) {
24-
context.cancelled = true;
25-
return;
19+
function checkDuplicate(error: IInnerError, log: ILog): boolean {
20+
function getHashCodeForError(err: IInnerError): number {
21+
if (!err.stack_trace) {
22+
return null;
23+
}
24+
25+
return Utils.getHashCode(JSON.stringify(err.stack_trace));
2626
}
27-
}
2827

29-
next && next();
30-
}
28+
let now = Date.now();
29+
let repeatWindow = now - WINDOW_MILLISECONDS;
30+
let hashCode: number;
31+
while (error) {
32+
hashCode = getHashCodeForError(error);
3133

32-
private getNow() {
33-
return Date.now();
34-
}
34+
// make sure that we don't process the same error multiple times within the repeat window
35+
if (hashCode && this.recentlyProcessedErrors.some(h =>
36+
h.hash === hashCode && h.timestamp >= repeatWindow)) {
37+
log.info(`Ignoring duplicate error event: hash=${hashCode}`);
38+
return true;
39+
}
40+
41+
// add this exception to our list of recent errors that we have processed
42+
this.recentlyProcessedErrors.push({ hash: hashCode, timestamp: now });
3543

36-
private checkDuplicate(error: IInnerError, log: ILog): boolean {
37-
let now = this.getNow();
38-
let repeatWindow = now - WINDOW_MILLISECONDS;
39-
let hashCode: number;
40-
while (error) {
41-
hashCode = getHashCodeForError(error);
44+
// only keep the last 10 recent errors
45+
while (this.recentlyProcessedErrors.length > MAX_QUEUE_LENGTH) {
46+
this.recentlyProcessedErrors.shift();
47+
}
4248

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

50-
// add this exception to our list of recent errors that we have processed
51-
this.recentlyProcessedErrors.push({ hash: hashCode, timestamp: now });
52+
return false;
53+
}
5254

53-
// only keep the last 10 recent errors
54-
while (this.recentlyProcessedErrors.length > MAX_QUEUE_LENGTH) {
55-
this.recentlyProcessedErrors.shift();
55+
if (context.event.type === 'error') {
56+
let error = context.event.data[ERROR_KEY];
57+
let isDuplicate = checkDuplicate(error, context.log);
58+
if (isDuplicate) {
59+
context.cancelled = true;
60+
return;
5661
}
57-
58-
error = error.inner;
5962
}
6063

61-
return false;
64+
next && next();
6265
}
6366
}
6467

6568
interface TimestampedHash {
6669
hash: number;
6770
timestamp: number;
6871
}
69-
70-
function getHashCodeForError(error: IInnerError): number {
71-
if (!error.stack_trace) {
72-
return null;
73-
}
74-
75-
let stack = JSON.stringify(error.stack_trace);
76-
return getHashCode(stack);
77-
}
78-
79-
function getHashCode(s: string): number {
80-
let hash = 0, length = s.length, char;
81-
for (let i = 0; i < length; i++) {
82-
char = s.charCodeAt(i);
83-
hash = ((hash << 5) - hash) + char;
84-
hash |= 0;
85-
}
86-
return hash;
87-
}

src/plugins/default/ErrorPlugin-spec.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { createFixture } from './EventPluginTestFixture';
1111
function BaseTestError() {
1212
this.name = 'NotImplementedError';
1313
this.someProperty = 'Test';
14-
};
14+
}
1515

1616
BaseTestError.prototype = new Error();
1717

@@ -22,7 +22,6 @@ function DerivedTestError() {
2222
DerivedTestError.prototype = new BaseTestError();
2323

2424
describe('ErrorPlugin', () => {
25-
2625
let target = new ErrorPlugin();
2726
let contextData: ContextData;
2827
let context: EventPluginContext;
@@ -45,9 +44,7 @@ describe('ErrorPlugin', () => {
4544
}
4645

4746
describe('additional data', () => {
48-
4947
describeForCapturedExceptions((exception) => {
50-
5148
it('should ignore default error properties', () => {
5249
contextData.setException(exception);
5350
target.run(context);

src/plugins/default/ErrorPlugin.ts

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IEventPlugin } from '../IEventPlugin';
22
import { EventPluginContext } from '../EventPluginContext';
3+
import { Utils } from '../../Utils';
34

45
export class ErrorPlugin implements IEventPlugin {
56
public priority: number = 30;
@@ -25,26 +26,26 @@ export class ErrorPlugin implements IEventPlugin {
2526

2627
public run(context: EventPluginContext, next?: () => void): void {
2728
const ERROR_KEY: string = '@error'; // optimization for minifier.
28-
const EXTRA_PROPERTIES_KEY: string = '@ext';
2929

3030
let exception = context.contextData.getException();
3131
if (!!exception) {
3232
context.event.type = 'error';
3333

3434
if (!context.event.data[ERROR_KEY]) {
35-
let parser = context.client.config.errorParser;
35+
let config = context.client.config;
36+
let parser = config.errorParser;
3637
if (!parser) {
3738
throw new Error('No error parser was defined.');
3839
}
3940

4041
let result = parser.parse(context, exception);
4142
if (!!result) {
42-
let additionalData = this.getAdditionalData(exception);
43+
let additionalData = JSON.parse(Utils.stringify(exception, config.dataExclusions.concat(this.ignoredProperties)));
4344
if (!!additionalData) {
4445
if (!result.data) {
4546
result.data = {};
4647
}
47-
result.data[EXTRA_PROPERTIES_KEY] = additionalData;
48+
result.data['@ext'] = additionalData;
4849
}
4950

5051
context.event.data[ERROR_KEY] = result;
@@ -54,21 +55,4 @@ export class ErrorPlugin implements IEventPlugin {
5455

5556
next && next();
5657
}
57-
58-
private getAdditionalData(exception: Error): { [key: string]: any } {
59-
let additionalData = {};
60-
for (var key in exception) {
61-
if (this.ignoredProperties.indexOf(key) >= 0) {
62-
continue;
63-
}
64-
let value = exception[key];
65-
if (typeof value !== 'function') {
66-
additionalData[key] = value;
67-
}
68-
}
69-
70-
return Object.getOwnPropertyNames(additionalData).length
71-
? additionalData
72-
: null;
73-
}
7458
}

src/plugins/default/EventPluginTestFixture.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IError } from '../../models/IError';
55
import { IErrorParser } from '../../services/IErrorParser';
66
import { IStackFrame } from '../../models/IStackFrame';
77

8+
// TODO: This should use the real object instances and inject the error parser.
89
export function createFixture() {
910
let contextData: ContextData;
1011
let context: EventPluginContext;
@@ -21,6 +22,7 @@ export function createFixture() {
2122
};
2223
client = {
2324
config: {
25+
dataExclusions:[],
2426
errorParser,
2527
log: {
2628
info: () => { }

src/services/DefaultModuleCollector.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class DefaultModuleCollector implements IModuleCollector {
1010
}
1111

1212
let modules:IModule[] = [];
13-
let scripts = document.getElementsByTagName('script');
13+
let scripts:NodeListOf<HTMLScriptElement> = document.getElementsByTagName('script');
1414
if (scripts && scripts.length > 0) {
1515
for (let index = 0; index < scripts.length; index++) {
1616
if (scripts[index].src) {
@@ -23,7 +23,7 @@ export class DefaultModuleCollector implements IModuleCollector {
2323
modules.push({
2424
module_id: index,
2525
name: 'Script Tag',
26-
version: Utils.getHashCode(scripts[index].innerHTML)
26+
version: Utils.getHashCode(scripts[index].innerHTML).toString()
2727
});
2828
}
2929
}

src/submission/DefaultSubmissionClient.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ import { ISubmissionClient } from './ISubmissionClient';
77
import { SettingsResponse } from './SettingsResponse';
88
import { SubmissionRequest } from './SubmissionRequest';
99
import { SubmissionResponse } from './SubmissionResponse';
10-
import { Utils } from '../Utils';
1110

1211
declare var XDomainRequest:{ new (); create(); };
1312

1413
export class DefaultSubmissionClient implements ISubmissionClient {
1514
public configurationVersionHeader:string = 'x-exceptionless-configversion';
1615

1716
public postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void {
18-
let data = Utils.stringify(events, config.dataExclusions);
17+
let data = JSON.stringify(events);
1918
let request = this.createRequest(config, 'POST', '/api/v2/events', data);
2019
let cb = this.createSubmissionCallback(config, callback);
2120

@@ -24,7 +23,7 @@ export class DefaultSubmissionClient implements ISubmissionClient {
2423

2524
public postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void {
2625
let path = `/api/v2/events/by-ref/${encodeURIComponent(referenceId)}/user-description`;
27-
let data = Utils.stringify(description, config.dataExclusions);
26+
let data = JSON.stringify(description);
2827
let request = this.createRequest(config, 'POST', path, data);
2928
let cb = this.createSubmissionCallback(config, callback);
3029

0 commit comments

Comments
 (0)