From 11d3d24e364c8e6ff1829cce2a395319528bbe84 Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Thu, 10 Jun 2021 10:39:28 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20instrumentation=20stack=20tra?= =?UTF-8?q?ce=20to=20addError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rum-core/src/boot/rumPublicApi.spec.ts | 21 +++++++++++- packages/rum-core/src/boot/rumPublicApi.ts | 33 +++++++++++-------- .../error/errorCollection.spec.ts | 29 +++++++++++++++- .../error/errorCollection.ts | 17 +++++++--- packages/rum-core/src/rawRumEvent.types.ts | 1 + 5 files changed, 81 insertions(+), 20 deletions(-) diff --git a/packages/rum-core/src/boot/rumPublicApi.spec.ts b/packages/rum-core/src/boot/rumPublicApi.spec.ts index c4baf9b49f..ec4f42f8be 100644 --- a/packages/rum-core/src/boot/rumPublicApi.spec.ts +++ b/packages/rum-core/src/boot/rumPublicApi.spec.ts @@ -1,4 +1,12 @@ -import { ErrorSource, ONE_SECOND, RelativeTime, getTimeStamp, display, TimeStamp } from '@datadog/browser-core' +import { + ErrorSource, + ONE_SECOND, + RelativeTime, + getTimeStamp, + display, + TimeStamp, + computeStackTrace, +} from '@datadog/browser-core' import { setup, TestSetupBuilder } from '../../test/specHelper' import { ActionType } from '../rawRumEvent.types' import { makeRumPublicApi, RumPublicApi, RumUserConfiguration, StartRum } from './rumPublicApi' @@ -258,6 +266,7 @@ describe('rum public api', () => { { context: undefined, error: new Error('foo'), + instrumentationError: new Error(), source: ErrorSource.CUSTOM, startClocks: jasmine.any(Object), }, @@ -279,6 +288,16 @@ describe('rum public api', () => { expect(displaySpy).toHaveBeenCalledWith("DD_RUM.addError: Invalid source 'invalid'") }) + it('should generate an instrumentation error at the highest position of RUM call stack', () => { + rumPublicApi.addError(new Error('message')) + rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) + + expect(addErrorSpy).toHaveBeenCalledTimes(1) + const instrumentationError = addErrorSpy.calls.argsFor(0)[0].instrumentationError + expect(instrumentationError).toEqual(new Error()) + expect(computeStackTrace(instrumentationError).stack[0].func).toContain('addError') + }) + describe('save context when capturing an error', () => { it('saves the date', () => { const { clock } = setupBuilder.withFakeClock().build() diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index 52dcb9cdb2..33dc5e4fc0 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -14,6 +14,7 @@ import { clocksNow, timeStampNow, display, + callMonitored, } from '@datadog/browser-core' import { ProvidedSource } from '../domain/rumEventsCollection/error/errorCollection' import { CommonContext, User, ActionType } from '../rawRumEvent.types' @@ -125,21 +126,25 @@ export function makeRumPublicApi(startRumImpl: S rumPublicApi.addAction(name, context as Context) }, - addError: monitor((error: unknown, context?: object, source: ProvidedSource = ErrorSource.CUSTOM) => { - let checkedSource: ProvidedSource - if (source === ErrorSource.CUSTOM || source === ErrorSource.NETWORK || source === ErrorSource.SOURCE) { - checkedSource = source - } else { - display.error(`DD_RUM.addError: Invalid source '${source as string}'`) - checkedSource = ErrorSource.CUSTOM - } - addErrorStrategy({ - error, - context: deepClone(context as Context), - source: checkedSource, - startClocks: clocksNow(), + addError: (error: unknown, context?: object, source: ProvidedSource = ErrorSource.CUSTOM) => { + const instrumentationError = new Error() + callMonitored(() => { + let checkedSource: ProvidedSource + if (source === ErrorSource.CUSTOM || source === ErrorSource.NETWORK || source === ErrorSource.SOURCE) { + checkedSource = source + } else { + display.error(`DD_RUM.addError: Invalid source '${source as string}'`) + checkedSource = ErrorSource.CUSTOM + } + addErrorStrategy({ + error, + instrumentationError, + context: deepClone(context as Context), + source: checkedSource, + startClocks: clocksNow(), + }) }) - }), + }, addTiming: monitor((name: string) => { addTimingStrategy(name) diff --git a/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.spec.ts index 142f89fd20..ee22580c9c 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.spec.ts @@ -1,6 +1,6 @@ import { ErrorSource, RelativeTime, TimeStamp } from '@datadog/browser-core' import { setup, TestSetupBuilder } from '../../../../test/specHelper' -import { RumEventType } from '../../../rawRumEvent.types' +import { RawRumErrorEvent, RumEventType } from '../../../rawRumEvent.types' import { LifeCycleEventType } from '../../lifeCycle' import { doStartErrorCollection } from './errorCollection' @@ -30,6 +30,7 @@ describe('error collection', () => { const { rawRumEvents } = setupBuilder.build() addError({ error: new Error('foo'), + instrumentationError: new Error(), source: ErrorSource.CUSTOM, startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, }) @@ -45,6 +46,7 @@ describe('error collection', () => { resource: undefined, source: ErrorSource.CUSTOM, stack: jasmine.stringMatching('Error: foo'), + instrumentation_stack: jasmine.any(String), type: 'Error', }, type: RumEventType.ERROR, @@ -62,6 +64,7 @@ describe('error collection', () => { addError({ context: { foo: 'bar' }, error: new Error('foo'), + instrumentationError: new Error(), source: ErrorSource.CUSTOM, startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, }) @@ -75,6 +78,7 @@ describe('error collection', () => { addError( { error: new Error('foo'), + instrumentationError: new Error(), source: ErrorSource.CUSTOM, startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, }, @@ -90,6 +94,7 @@ describe('error collection', () => { addError( { error: new Error('foo'), + instrumentationError: new Error(), source: ErrorSource.CUSTOM, startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, }, @@ -99,6 +104,27 @@ describe('error collection', () => { id: 'foo', }) }) + + it('should generate an instrumentation stack', () => { + const { rawRumEvents } = setupBuilder.build() + function publicApiAddError() { + const instrumentationTask = new Error() + addError({ + error: new Error('foo'), + instrumentationError: instrumentationTask, + source: ErrorSource.CUSTOM, + startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, + }) + } + function triggerAddError() { + publicApiAddError() + } + triggerAddError() + expect(rawRumEvents.length).toBe(1) + const rawError = rawRumEvents[0].rawRumEvent as RawRumErrorEvent + expect(rawError.error.instrumentation_stack).toContain('triggerAddError') + expect(rawError.error.instrumentation_stack).not.toContain('addError') + }) }) describe('RAW_ERROR_COLLECTED LifeCycle event', () => { @@ -132,6 +158,7 @@ describe('error collection', () => { }, source: ErrorSource.NETWORK, stack: 'bar', + instrumentation_stack: undefined, type: 'foo', }, view: { diff --git a/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts index 63fba6a13e..df73a86c43 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts @@ -7,6 +7,7 @@ import { startAutomaticErrorCollection, ClocksState, generateUUID, + removeLastStackTraceCall, } from '@datadog/browser-core' import { CommonContext, RawRumErrorEvent, RumEventType } from '../../../rawRumEvent.types' import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' @@ -17,6 +18,7 @@ export interface ProvidedError { error: unknown context?: Context source: ProvidedSource + instrumentationError: Error } export type ProvidedSource = 'custom' | 'network' | 'source' @@ -44,10 +46,10 @@ export function doStartErrorCollection(lifeCycle: LifeCycle, foregroundContexts: return { addError: ( - { error, startClocks, context: customerContext, source }: ProvidedError, + { error, instrumentationError, startClocks, context: customerContext, source }: ProvidedError, savedCommonContext?: CommonContext ) => { - const rawError = computeRawError(error, startClocks, source) + const rawError = computeRawError(error, instrumentationError, startClocks, source) lifeCycle.notify(LifeCycleEventType.RAW_ERROR_COLLECTED, { customerContext, savedCommonContext, @@ -57,9 +59,15 @@ export function doStartErrorCollection(lifeCycle: LifeCycle, foregroundContexts: } } -function computeRawError(error: unknown, startClocks: ClocksState, source: ProvidedSource): RawError { +function computeRawError( + error: unknown, + instrumentationError: Error, + startClocks: ClocksState, + source: ProvidedSource +): RawError { const stackTrace = error instanceof Error ? computeStackTrace(error) : undefined - return { startClocks, source, ...formatUnknownError(stackTrace, error, 'Provided') } + const instrumentationStack = removeLastStackTraceCall(computeStackTrace(instrumentationError)) + return { startClocks, source, ...formatUnknownError(stackTrace, error, 'Provided', instrumentationStack) } } function processError(error: RawError, foregroundContexts: ForegroundContexts) { @@ -77,6 +85,7 @@ function processError(error: RawError, foregroundContexts: ForegroundContexts) { : undefined, source: error.source, stack: error.stack, + instrumentation_stack: error.instrumentationStack, type: error.type, }, type: RumEventType.ERROR as const, diff --git a/packages/rum-core/src/rawRumEvent.types.ts b/packages/rum-core/src/rawRumEvent.types.ts index 1d7cdb7dc4..8d54692fb9 100644 --- a/packages/rum-core/src/rawRumEvent.types.ts +++ b/packages/rum-core/src/rawRumEvent.types.ts @@ -49,6 +49,7 @@ export interface RawRumErrorEvent { id: string type?: string stack?: string + instrumentation_stack?: string source: ErrorSource message: string }