diff --git a/packages/core/src/domain/error/error.spec.ts b/packages/core/src/domain/error/error.spec.ts index b0580ecc37..971ad53435 100644 --- a/packages/core/src/domain/error/error.spec.ts +++ b/packages/core/src/domain/error/error.spec.ts @@ -8,7 +8,7 @@ import { isError, NO_ERROR_STACK_PRESENT_MESSAGE, } from './error' -import type { RawErrorCause, ErrorWithCause } from './error.types' +import type { ErrorWithCause, RawFlatErrorCause } from './error.types' import { ErrorHandling, ErrorSource, NonErrorPrefix } from './error.types' describe('computeRawError', () => { @@ -179,7 +179,7 @@ describe('computeRawError', () => { expect(formatted.causes!.length).toBe(2) expect(formatted.stack).toContain('Error: foo: bar') - const causes = formatted.causes as RawErrorCause[] + const causes = formatted.causes as RawFlatErrorCause[] expect(causes[0].message).toContain(nestedError.message) expect(causes[0].source).toContain(ErrorSource.SOURCE) @@ -241,7 +241,8 @@ describe('flattenErrorCauses', () => { error.cause = nestedError const errorCauses = flattenErrorCauses(error, ErrorSource.LOGGER) - expect(errorCauses?.length).toEqual(undefined) + expect(errorCauses?.length).toEqual(1) + expect(errorCauses?.[0]).toEqual(jasmine.objectContaining({ biz: 'buz', cause: jasmine.any(Error) })) }) it('should use error to extract stack trace', () => { @@ -250,7 +251,7 @@ describe('flattenErrorCauses', () => { error.cause = new Error('bar') const errorCauses = flattenErrorCauses(error, ErrorSource.LOGGER) - expect(errorCauses?.[0].type).toEqual('Error') + expect((errorCauses?.[0] as RawFlatErrorCause).type).toEqual('Error') }) it('should only return the first 10 errors if nested chain is longer', () => { diff --git a/packages/core/src/domain/error/error.ts b/packages/core/src/domain/error/error.ts index 52a42fb5a5..e83e0a3d3b 100644 --- a/packages/core/src/domain/error/error.ts +++ b/packages/core/src/domain/error/error.ts @@ -5,7 +5,15 @@ import { jsonStringify } from '../../tools/serialisation/jsonStringify' import type { StackTrace } from '../../tools/stackTrace/computeStackTrace' import { computeStackTrace } from '../../tools/stackTrace/computeStackTrace' import { toStackTraceString } from '../../tools/stackTrace/handlingStack' -import type { ErrorSource, ErrorHandling, RawError, RawErrorCause, ErrorWithCause, NonErrorPrefix } from './error.types' +import type { + ErrorSource, + ErrorHandling, + RawError, + RawErrorCause, + ErrorWithCause, + NonErrorPrefix, + RawFlatErrorCause, +} from './error.types' export const NO_ERROR_STACK_PRESENT_MESSAGE = 'No stack, consider using an instance of Error' @@ -87,17 +95,26 @@ export function isError(error: unknown): error is Error { } export function flattenErrorCauses(error: ErrorWithCause, parentSource: ErrorSource): RawErrorCause[] | undefined { - let currentError = error const causes: RawErrorCause[] = [] - while (isError(currentError?.cause) && causes.length < 10) { - const stackTrace = computeStackTrace(currentError.cause) - causes.push({ - message: currentError.cause.message, - source: parentSource, - type: stackTrace?.name, - stack: stackTrace && toStackTraceString(stackTrace), - }) - currentError = currentError.cause + + let currentCause = error.cause + while (currentCause !== undefined && currentCause !== null && causes.length < 10) { + if (isError(currentCause)) { + const stackTrace = computeStackTrace(currentCause) + + causes.push({ + message: currentCause.message, + source: parentSource, + type: stackTrace?.name, + stack: stackTrace && toStackTraceString(stackTrace), + } satisfies RawFlatErrorCause) + + currentCause = (currentCause as ErrorWithCause).cause + } else { + causes.push(currentCause) + + currentCause = undefined + } } return causes.length ? causes : undefined } diff --git a/packages/core/src/domain/error/error.types.ts b/packages/core/src/domain/error/error.types.ts index c4417daa52..d2f5230c03 100644 --- a/packages/core/src/domain/error/error.types.ts +++ b/packages/core/src/domain/error/error.types.ts @@ -14,7 +14,9 @@ export interface ErrorWithCause extends Omit { cause?: unknown } -export interface RawErrorCause { +export type RawErrorCause = unknown + +export interface RawFlatErrorCause { message: string source: ErrorSource type?: string diff --git a/packages/rum-core/src/domain/error/errorCollection.spec.ts b/packages/rum-core/src/domain/error/errorCollection.spec.ts index 2efeb35fda..40a11af296 100644 --- a/packages/rum-core/src/domain/error/errorCollection.spec.ts +++ b/packages/rum-core/src/domain/error/errorCollection.spec.ts @@ -1,6 +1,7 @@ import type { RelativeTime, TimeStamp, ErrorWithCause } from '@datadog/browser-core' import { ErrorHandling, ErrorSource, NO_ERROR_STACK_PRESENT_MESSAGE } from '@datadog/browser-core' import { FAKE_CSP_VIOLATION_EVENT } from '@datadog/browser-core/test' +import type { RawFlatErrorCause } from '@datadog/browser-core/src/domain/error/error.types.ts' import { collectAndValidateRawRumEvents } from '../../../test' import type { RawRumErrorEvent, RawRumEvent } from '../../rawRumEvent.types' import { RumEventType } from '../../rawRumEvent.types' @@ -119,11 +120,12 @@ describe('error collection', () => { expect(error.message).toEqual('foo') expect(error.source).toEqual(ErrorSource.CUSTOM) - expect(error?.causes?.length).toEqual(2) - expect(error?.causes?.[0].message).toEqual('bar') - expect(error?.causes?.[0].source).toEqual(ErrorSource.CUSTOM) - expect(error?.causes?.[1].message).toEqual('biz') - expect(error?.causes?.[1].source).toEqual(ErrorSource.CUSTOM) + const causes = (error?.causes ?? []) as RawFlatErrorCause[] + expect(causes.length).toEqual(2) + expect(causes[0].message).toEqual('bar') + expect(causes[0].source).toEqual(ErrorSource.CUSTOM) + expect(causes[1].message).toEqual('biz') + expect(causes[1].source).toEqual(ErrorSource.CUSTOM) }) it('should extract fingerprint from error', () => {