Skip to content

Commit cdf574e

Browse files
Luca ForstnerLms24
authored andcommitted
feat(integrations): Capture error cause with captureErrorCause in ExtraErrorData integration (#9914)
Co-authored-by: Lukas Stracke <lukas.stracke@sentry.io>
1 parent 2597e37 commit cdf574e

File tree

2 files changed

+78
-4
lines changed

2 files changed

+78
-4
lines changed

packages/integrations/src/extraerrordata.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,29 @@ import { DEBUG_BUILD } from './debug-build';
77
const INTEGRATION_NAME = 'ExtraErrorData';
88

99
interface ExtraErrorDataOptions {
10+
/**
11+
* The object depth up to which to capture data on error objects.
12+
*/
1013
depth: number;
14+
15+
/**
16+
* Whether to capture error causes.
17+
*
18+
* More information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
19+
*/
20+
captureErrorCause: boolean;
1121
}
1222

1323
const extraErrorDataIntegration = ((options: Partial<ExtraErrorDataOptions> = {}) => {
1424
const depth = options.depth || 3;
1525

26+
// TODO(v8): Flip the default for this option to true
27+
const captureErrorCause = options.captureErrorCause || false;
28+
1629
return {
1730
name: INTEGRATION_NAME,
1831
processEvent(event, hint) {
19-
return _enhanceEventWithErrorData(event, hint, depth);
32+
return _enhanceEventWithErrorData(event, hint, depth, captureErrorCause);
2033
},
2134
};
2235
}) satisfies IntegrationFn;
@@ -25,13 +38,18 @@ const extraErrorDataIntegration = ((options: Partial<ExtraErrorDataOptions> = {}
2538
// eslint-disable-next-line deprecation/deprecation
2639
export const ExtraErrorData = convertIntegrationFnToClass(INTEGRATION_NAME, extraErrorDataIntegration);
2740

28-
function _enhanceEventWithErrorData(event: Event, hint: EventHint = {}, depth: number): Event {
41+
function _enhanceEventWithErrorData(
42+
event: Event,
43+
hint: EventHint = {},
44+
depth: number,
45+
captureErrorCause: boolean,
46+
): Event {
2947
if (!hint.originalException || !isError(hint.originalException)) {
3048
return event;
3149
}
3250
const exceptionName = (hint.originalException as ExtendedError).name || hint.originalException.constructor.name;
3351

34-
const errorData = _extractErrorData(hint.originalException as ExtendedError);
52+
const errorData = _extractErrorData(hint.originalException as ExtendedError, captureErrorCause);
3553

3654
if (errorData) {
3755
const contexts: Contexts = {
@@ -59,7 +77,7 @@ function _enhanceEventWithErrorData(event: Event, hint: EventHint = {}, depth: n
5977
/**
6078
* Extract extra information from the Error object
6179
*/
62-
function _extractErrorData(error: ExtendedError): Record<string, unknown> | null {
80+
function _extractErrorData(error: ExtendedError, captureErrorCause: boolean): Record<string, unknown> | null {
6381
// We are trying to enhance already existing event, so no harm done if it won't succeed
6482
try {
6583
const nativeKeys = [
@@ -85,6 +103,12 @@ function _extractErrorData(error: ExtendedError): Record<string, unknown> | null
85103
extraErrorInfo[key] = isError(value) ? value.toString() : value;
86104
}
87105

106+
// Error.cause is a standard property that is non enumerable, we therefore need to access it separately.
107+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
108+
if (captureErrorCause && error.cause !== undefined) {
109+
extraErrorInfo.cause = isError(error.cause) ? error.cause.toString() : error.cause;
110+
}
111+
88112
// Check if someone attached `toJSON` method to grab even more properties (eg. axios is doing that)
89113
if (typeof error.toJSON === 'function') {
90114
const serializedError = error.toJSON() as Record<string, unknown>;

packages/integrations/test/extraerrordata.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,54 @@ describe('ExtraErrorData()', () => {
178178
},
179179
});
180180
});
181+
182+
it('captures Error causes when captureErrorCause = true', () => {
183+
// Error.cause is only available from node 16 upwards
184+
const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]);
185+
if (nodeMajorVersion < 16) {
186+
return;
187+
}
188+
189+
const extraErrorDataWithCauseCapture = new ExtraErrorData({ captureErrorCause: true });
190+
191+
// @ts-expect-error The typing .d.ts library we have installed isn't aware of Error.cause yet
192+
const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError;
193+
194+
const enhancedEvent = extraErrorDataWithCauseCapture.processEvent(event, {
195+
originalException: error,
196+
});
197+
198+
expect(enhancedEvent.contexts).toEqual({
199+
Error: {
200+
cause: {
201+
woot: 'foo',
202+
},
203+
},
204+
});
205+
});
206+
207+
it("doesn't capture Error causes when captureErrorCause != true", () => {
208+
// Error.cause is only available from node 16 upwards
209+
const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]);
210+
if (nodeMajorVersion < 16) {
211+
return;
212+
}
213+
214+
const extraErrorDataWithoutCauseCapture = new ExtraErrorData();
215+
216+
// @ts-expect-error The typing .d.ts library we have installed isn't aware of Error.cause yet
217+
const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError;
218+
219+
const enhancedEvent = extraErrorDataWithoutCauseCapture.processEvent(event, {
220+
originalException: error,
221+
});
222+
223+
expect(enhancedEvent.contexts).not.toEqual({
224+
Error: {
225+
cause: {
226+
woot: 'foo',
227+
},
228+
},
229+
});
230+
});
181231
});

0 commit comments

Comments
 (0)