From 684e6c968d50b4ef1408690ebafadb4f1262d24c Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 19 Dec 2023 13:20:54 +0000 Subject: [PATCH 1/5] feat(integrations): Capture error cause with `captureErrorCause` in `ExtraErrorData` integration --- packages/integrations/src/extraerrordata.ts | 22 +++++++++-- .../integrations/test/extraerrordata.test.ts | 38 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts index 1c1b46e58c22..f226b8eb568b 100644 --- a/packages/integrations/src/extraerrordata.ts +++ b/packages/integrations/src/extraerrordata.ts @@ -6,6 +6,7 @@ import { DEBUG_BUILD } from './debug-build'; /** JSDoc */ interface ExtraErrorDataOptions { depth: number; + captureErrorCause: boolean; } /** Patch toString calls to return proper name for wrapped functions */ @@ -31,6 +32,8 @@ export class ExtraErrorData implements Integration { this._options = { depth: 3, + // TODO(v8): Flip this to true + captureErrorCause: false, ...options, }; } @@ -53,17 +56,22 @@ export class ExtraErrorData implements Integration { * TODO (v8): Drop this public function. */ public enhanceEventWithErrorData(event: Event, hint: EventHint = {}): Event { - return _enhanceEventWithErrorData(event, hint, this._options.depth); + return _enhanceEventWithErrorData(event, hint, this._options.depth, this._options.captureErrorCause); } } -function _enhanceEventWithErrorData(event: Event, hint: EventHint = {}, depth: number): Event { +function _enhanceEventWithErrorData( + event: Event, + hint: EventHint = {}, + depth: number, + captureErrorCause: boolean, +): Event { if (!hint.originalException || !isError(hint.originalException)) { return event; } const exceptionName = (hint.originalException as ExtendedError).name || hint.originalException.constructor.name; - const errorData = _extractErrorData(hint.originalException as ExtendedError); + const errorData = _extractErrorData(hint.originalException as ExtendedError, captureErrorCause); if (errorData) { const contexts: Contexts = { @@ -91,7 +99,7 @@ function _enhanceEventWithErrorData(event: Event, hint: EventHint = {}, depth: n /** * Extract extra information from the Error object */ -function _extractErrorData(error: ExtendedError): Record | null { +function _extractErrorData(error: ExtendedError, captureErrorCause: boolean): Record | null { // We are trying to enhance already existing event, so no harm done if it won't succeed try { const nativeKeys = [ @@ -117,6 +125,12 @@ function _extractErrorData(error: ExtendedError): Record | null extraErrorInfo[key] = isError(value) ? value.toString() : value; } + // Error.cause is a standard property that is non enumerable, we therefore need to access it separately. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause + if (captureErrorCause && error['cause']) { + extraErrorInfo['cause'] = isError(error['cause']) ? error['cause'].toString() : error['cause']; + } + // Check if someone attached `toJSON` method to grab even more properties (eg. axios is doing that) if (typeof error.toJSON === 'function') { const serializedError = error.toJSON() as Record; diff --git a/packages/integrations/test/extraerrordata.test.ts b/packages/integrations/test/extraerrordata.test.ts index bc7a6312a65a..ebeff4cf7766 100644 --- a/packages/integrations/test/extraerrordata.test.ts +++ b/packages/integrations/test/extraerrordata.test.ts @@ -178,4 +178,42 @@ describe('ExtraErrorData()', () => { }, }); }); + + it('captures Error causes when captureErrorCause = true', () => { + const extraErrorDataWithCauseCapture = new ExtraErrorData({ captureErrorCause: true }); + + // @ts-expect-error TS apparently doesn't understand error causes + const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError; + + const enhancedEvent = extraErrorDataWithCauseCapture.enhanceEventWithErrorData(event, { + originalException: error, + }); + + expect(enhancedEvent.contexts).toEqual({ + Error: { + cause: { + woot: 'foo', + }, + }, + }); + }); + + it("doesn't capture Error causes when captureErrorCause != true", () => { + const extraErrorDataWithoutCauseCapture = new ExtraErrorData(); + + // @ts-expect-error TS apparently doesn't understand error causes + const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError; + + const enhancedEvent = extraErrorDataWithoutCauseCapture.enhanceEventWithErrorData(event, { + originalException: error, + }); + + expect(enhancedEvent.contexts).not.toEqual({ + Error: { + cause: { + woot: 'foo', + }, + }, + }); + }); }); From f8296760cd809f00764bdfab6f398f04165397d5 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 22 Dec 2023 10:50:36 +0000 Subject: [PATCH 2/5] Clean --- packages/integrations/src/extraerrordata.ts | 18 +++++++++++++++--- .../integrations/test/extraerrordata.test.ts | 12 ++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts index 259f516021c6..58fe887707b1 100644 --- a/packages/integrations/src/extraerrordata.ts +++ b/packages/integrations/src/extraerrordata.ts @@ -7,17 +7,29 @@ import { DEBUG_BUILD } from './debug-build'; const INTEGRATION_NAME = 'ExtraErrorData'; interface ExtraErrorDataOptions { + /** + * The object depth up to which to capture data on error objects. + */ depth: number; + + /** + * Whether to capture error causes. + * + * More innformation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause + */ captureErrorCause: boolean; } const extraErrorDataIntegration = ((options: Partial = {}) => { const depth = options.depth || 3; + // TODO(v8): Flip the default for this option to true + const captureErrorCause = options.captureErrorCause || false; + return { name: INTEGRATION_NAME, processEvent(event, hint) { - return _enhanceEventWithErrorData(event, hint, depth, options.captureErrorCause); + return _enhanceEventWithErrorData(event, hint, depth, captureErrorCause); }, }; }) satisfies IntegrationFn; @@ -93,8 +105,8 @@ function _extractErrorData(error: ExtendedError, captureErrorCause: boolean): Re // Error.cause is a standard property that is non enumerable, we therefore need to access it separately. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause - if (captureErrorCause && error['cause']) { - extraErrorInfo['cause'] = isError(error['cause']) ? error['cause'].toString() : error['cause']; + if (captureErrorCause && error.cause !== undefined) { + extraErrorInfo.cause = isError(error.cause) ? error.cause.toString() : error.cause; } // Check if someone attached `toJSON` method to grab even more properties (eg. axios is doing that) diff --git a/packages/integrations/test/extraerrordata.test.ts b/packages/integrations/test/extraerrordata.test.ts index 5501fa3f1c8b..61bdf9befa91 100644 --- a/packages/integrations/test/extraerrordata.test.ts +++ b/packages/integrations/test/extraerrordata.test.ts @@ -180,12 +180,16 @@ describe('ExtraErrorData()', () => { }); it('captures Error causes when captureErrorCause = true', () => { + if (parseInt(process.version.split('.')[0]) < 16) { + return; + } + const extraErrorDataWithCauseCapture = new ExtraErrorData({ captureErrorCause: true }); // @ts-expect-error TS apparently doesn't understand error causes const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError; - const enhancedEvent = extraErrorDataWithCauseCapture.enhanceEventWithErrorData(event, { + const enhancedEvent = extraErrorDataWithCauseCapture.processEvent(event, { originalException: error, }); @@ -199,12 +203,16 @@ describe('ExtraErrorData()', () => { }); it("doesn't capture Error causes when captureErrorCause != true", () => { + if (parseInt(process.version.split('.')[0]) < 16) { + return; + } + const extraErrorDataWithoutCauseCapture = new ExtraErrorData(); // @ts-expect-error TS apparently doesn't understand error causes const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError; - const enhancedEvent = extraErrorDataWithoutCauseCapture.enhanceEventWithErrorData(event, { + const enhancedEvent = extraErrorDataWithoutCauseCapture.processEvent(event, { originalException: error, }); From d92b5104f628a32f262da823603259bbf1828545 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 22 Dec 2023 10:55:53 +0000 Subject: [PATCH 3/5] Update comment --- packages/integrations/test/extraerrordata.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/integrations/test/extraerrordata.test.ts b/packages/integrations/test/extraerrordata.test.ts index 61bdf9befa91..59cbc3c3fde3 100644 --- a/packages/integrations/test/extraerrordata.test.ts +++ b/packages/integrations/test/extraerrordata.test.ts @@ -186,7 +186,7 @@ describe('ExtraErrorData()', () => { const extraErrorDataWithCauseCapture = new ExtraErrorData({ captureErrorCause: true }); - // @ts-expect-error TS apparently doesn't understand error causes + // @ts-expect-error The typing .d.ts library we have installed isn't aware of Error.cause yet const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError; const enhancedEvent = extraErrorDataWithCauseCapture.processEvent(event, { @@ -209,7 +209,7 @@ describe('ExtraErrorData()', () => { const extraErrorDataWithoutCauseCapture = new ExtraErrorData(); - // @ts-expect-error TS apparently doesn't understand error causes + // @ts-expect-error The typing .d.ts library we have installed isn't aware of Error.cause yet const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError; const enhancedEvent = extraErrorDataWithoutCauseCapture.processEvent(event, { From be376c182f037a3ccc8e4f722474e99cef38219c Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 22 Dec 2023 12:17:39 +0100 Subject: [PATCH 4/5] Update packages/integrations/src/extraerrordata.ts Co-authored-by: Lukas Stracke --- packages/integrations/src/extraerrordata.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts index 58fe887707b1..8d9d72cb81d7 100644 --- a/packages/integrations/src/extraerrordata.ts +++ b/packages/integrations/src/extraerrordata.ts @@ -15,7 +15,7 @@ interface ExtraErrorDataOptions { /** * Whether to capture error causes. * - * More innformation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause + * More information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause */ captureErrorCause: boolean; } From c446d509803cc7801f81cac791c21c961c38ed9b Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 22 Dec 2023 11:37:48 +0000 Subject: [PATCH 5/5] Fix tests --- packages/integrations/test/extraerrordata.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/integrations/test/extraerrordata.test.ts b/packages/integrations/test/extraerrordata.test.ts index 59cbc3c3fde3..d72a43c57f8b 100644 --- a/packages/integrations/test/extraerrordata.test.ts +++ b/packages/integrations/test/extraerrordata.test.ts @@ -180,7 +180,9 @@ describe('ExtraErrorData()', () => { }); it('captures Error causes when captureErrorCause = true', () => { - if (parseInt(process.version.split('.')[0]) < 16) { + // Error.cause is only available from node 16 upwards + const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]); + if (nodeMajorVersion < 16) { return; } @@ -203,7 +205,9 @@ describe('ExtraErrorData()', () => { }); it("doesn't capture Error causes when captureErrorCause != true", () => { - if (parseInt(process.version.split('.')[0]) < 16) { + // Error.cause is only available from node 16 upwards + const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]); + if (nodeMajorVersion < 16) { return; }