diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts index 9d8a00f976cf..8d9d72cb81d7 100644 --- a/packages/integrations/src/extraerrordata.ts +++ b/packages/integrations/src/extraerrordata.ts @@ -7,16 +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 information: 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); + return _enhanceEventWithErrorData(event, hint, depth, captureErrorCause); }, }; }) satisfies IntegrationFn; @@ -25,13 +38,18 @@ const extraErrorDataIntegration = ((options: Partial = {} // eslint-disable-next-line deprecation/deprecation export const ExtraErrorData = convertIntegrationFnToClass(INTEGRATION_NAME, extraErrorDataIntegration); -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 = { @@ -59,7 +77,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 = [ @@ -85,6 +103,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 !== 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) 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 166c8e66fe37..d72a43c57f8b 100644 --- a/packages/integrations/test/extraerrordata.test.ts +++ b/packages/integrations/test/extraerrordata.test.ts @@ -178,4 +178,54 @@ describe('ExtraErrorData()', () => { }, }); }); + + it('captures Error causes when captureErrorCause = true', () => { + // Error.cause is only available from node 16 upwards + const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]); + if (nodeMajorVersion < 16) { + return; + } + + const extraErrorDataWithCauseCapture = new ExtraErrorData({ captureErrorCause: true }); + + // @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, { + originalException: error, + }); + + expect(enhancedEvent.contexts).toEqual({ + Error: { + cause: { + woot: 'foo', + }, + }, + }); + }); + + it("doesn't capture Error causes when captureErrorCause != true", () => { + // Error.cause is only available from node 16 upwards + const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]); + if (nodeMajorVersion < 16) { + return; + } + + const extraErrorDataWithoutCauseCapture = new ExtraErrorData(); + + // @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, { + originalException: error, + }); + + expect(enhancedEvent.contexts).not.toEqual({ + Error: { + cause: { + woot: 'foo', + }, + }, + }); + }); });