Skip to content

Commit 2a43e94

Browse files
authored
feat: core ExtraErrorData integration (#1778)
1 parent 92e0149 commit 2a43e94

File tree

6 files changed

+159
-15
lines changed

6 files changed

+159
-15
lines changed

packages/browser/src/sdk.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const defaultIntegrations = [
88
new CoreIntegrations.Dedupe(),
99
new CoreIntegrations.InboundFilters(),
1010
new CoreIntegrations.FunctionToString(),
11+
new CoreIntegrations.ExtraErrorData(),
1112
// Native Wrappers
1213
new TryCatch(),
1314
new Breadcrumbs(),
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/hub';
2+
import { Integration, SentryEvent, SentryEventHint } from '@sentry/types';
3+
import { isError } from '@sentry/utils/is';
4+
import { logger } from '../../../utils/logger';
5+
6+
/**
7+
* Just an Error object with arbitrary attributes attached to it.
8+
*/
9+
interface ExtendedError extends Error {
10+
[key: string]: unknown;
11+
}
12+
13+
/** Patch toString calls to return proper name for wrapped functions */
14+
export class ExtraErrorData implements Integration {
15+
/**
16+
* @inheritDoc
17+
*/
18+
public name: string = ExtraErrorData.id;
19+
20+
/**
21+
* @inheritDoc
22+
*/
23+
public static id: string = 'ExtraErrorData';
24+
25+
/**
26+
* @inheritDoc
27+
*/
28+
public setupOnce(): void {
29+
addGlobalEventProcessor(async (event: SentryEvent, hint?: SentryEventHint) => {
30+
const self = getCurrentHub().getIntegration(ExtraErrorData);
31+
32+
if (!self || !hint || !hint.originalException) {
33+
return event;
34+
}
35+
36+
return this.enhanceEventWithErrorData(event, hint.originalException);
37+
});
38+
}
39+
40+
/**
41+
* Attaches extracted information from the Error object to extra field in the SentryEvent
42+
*/
43+
public enhanceEventWithErrorData(event: SentryEvent, error: Error): SentryEvent {
44+
const errorData = this.extractErrorData(error);
45+
46+
if (errorData) {
47+
return {
48+
...event,
49+
extra: {
50+
...event.extra,
51+
...errorData,
52+
},
53+
};
54+
}
55+
56+
return event;
57+
}
58+
59+
/**
60+
* Extract extra information from the Error object
61+
*/
62+
private extractErrorData(error: ExtendedError): { [key: string]: unknown } | null {
63+
// We are trying to enhance already existing event, so no harm done if it won't succeed
64+
try {
65+
const nativeKeys = ['name', 'message', 'stack', 'line', 'column', 'fileName', 'lineNumber', 'columnNumber'];
66+
const name = error.name || error.constructor.name;
67+
const errorKeys = Object.keys(error).filter(key => nativeKeys.indexOf(key) === -1);
68+
69+
if (errorKeys.length) {
70+
const extraErrorInfo: { [key: string]: unknown } = {};
71+
for (const key of errorKeys) {
72+
let value = error[key];
73+
if (isError(value)) {
74+
value = (value as Error).name || (value as Error).constructor.name;
75+
}
76+
extraErrorInfo[key] = value;
77+
}
78+
return {
79+
[name]: extraErrorInfo,
80+
};
81+
}
82+
83+
return null;
84+
} catch (oO) {
85+
logger.error('Unable to extract extra data from the Error object:', oO);
86+
return null;
87+
}
88+
}
89+
}

packages/core/src/integrations/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export { Dedupe } from './dedupe';
22
export { FunctionToString } from './functiontostring';
33
export { SDKInformation } from './sdkinformation';
44
export { InboundFilters } from './inboundfilters';
5+
export { ExtraErrorData } from './extraerrordata';
56

67
export { Debug } from './pluggable/debug';
78
export { RewriteFrames } from './pluggable/rewriteframes';
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { SentryEvent } from '@sentry/types';
2+
import { ExtraErrorData } from '../../../src/integrations/extraerrordata';
3+
4+
/**
5+
* Just an Error object with arbitrary attributes attached to it.
6+
*/
7+
interface ExtendedError extends Error {
8+
[key: string]: any;
9+
}
10+
11+
const extraErrorData = new ExtraErrorData();
12+
let event: SentryEvent;
13+
14+
describe('ExtraErrorData()', () => {
15+
beforeEach(() => {
16+
event = {};
17+
});
18+
19+
it('should enhance event with extra data extracted from the error', () => {
20+
const error = new TypeError('foo') as ExtendedError;
21+
error.baz = 42;
22+
error.foo = 'bar';
23+
24+
const enhancedEvent = extraErrorData.enhanceEventWithErrorData(event, error);
25+
26+
expect(enhancedEvent.extra).toEqual({
27+
TypeError: {
28+
baz: 42,
29+
foo: 'bar',
30+
},
31+
});
32+
});
33+
34+
it('doesnt choke on linked errors and stringify names instead', () => {
35+
const error = new TypeError('foo') as ExtendedError;
36+
error.cause = new SyntaxError('bar');
37+
38+
const enhancedEvent = extraErrorData.enhanceEventWithErrorData(event, error);
39+
40+
expect(enhancedEvent.extra).toEqual({
41+
TypeError: {
42+
cause: 'SyntaxError',
43+
},
44+
});
45+
});
46+
47+
it('should not remove previous data existing in extra field', () => {
48+
event = {
49+
extra: {
50+
foo: 42,
51+
},
52+
};
53+
const error = new TypeError('foo') as ExtendedError;
54+
error.baz = 42;
55+
56+
const enhancedEvent = extraErrorData.enhanceEventWithErrorData(event, error);
57+
58+
expect(enhancedEvent.extra).toEqual({
59+
TypeError: {
60+
baz: 42,
61+
},
62+
foo: 42,
63+
});
64+
});
65+
});

packages/node/src/parsers.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -193,25 +193,12 @@ export async function getExceptionFromError(error: Error): Promise<SentryExcepti
193193
export async function parseError(error: ExtendedError): Promise<SentryEvent> {
194194
const name = error.name || error.constructor.name;
195195
const exception = await getExceptionFromError(error);
196-
const event: SentryEvent = {
196+
return {
197197
exception: {
198198
values: [exception],
199199
},
200200
message: `${name}: ${error.message || '<no message>'}`,
201201
};
202-
const errorKeys = Object.keys(error).filter(key => !(key in ['name', 'message', 'stack', 'domain']));
203-
204-
if (errorKeys.length) {
205-
const extraErrorInfo: { [key: string]: any } = {};
206-
for (const key of errorKeys) {
207-
extraErrorInfo[key] = error[key];
208-
}
209-
event.extra = {
210-
[name]: extraErrorInfo,
211-
};
212-
}
213-
214-
return event;
215202
}
216203

217204
/** JSDoc */

packages/node/src/sdk.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const defaultIntegrations = [
1010
new CoreIntegrations.Dedupe(),
1111
new CoreIntegrations.InboundFilters(),
1212
new CoreIntegrations.FunctionToString(),
13+
new CoreIntegrations.ExtraErrorData(),
1314
// Native Wrappers
1415
new Console(),
1516
new Http(),
@@ -83,4 +84,4 @@ export function init(options: NodeOptions = {}): void {
8384
*/
8485
export function lastEventId(): string | undefined {
8586
return getCurrentHub().lastEventId();
86-
}
87+
}

0 commit comments

Comments
 (0)