Skip to content

Commit

Permalink
✨ Add instrumentation stack trace to addError
Browse files Browse the repository at this point in the history
  • Loading branch information
amortemousque committed Jun 10, 2021
1 parent c4944eb commit 11d3d24
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 20 deletions.
21 changes: 20 additions & 1 deletion packages/rum-core/src/boot/rumPublicApi.spec.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -258,6 +266,7 @@ describe('rum public api', () => {
{
context: undefined,
error: new Error('foo'),
instrumentationError: new Error(),
source: ErrorSource.CUSTOM,
startClocks: jasmine.any(Object),
},
Expand All @@ -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()
Expand Down
33 changes: 19 additions & 14 deletions packages/rum-core/src/boot/rumPublicApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -125,21 +126,25 @@ export function makeRumPublicApi<C extends RumUserConfiguration>(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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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 },
})
Expand All @@ -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,
Expand All @@ -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 },
})
Expand All @@ -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 },
},
Expand All @@ -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 },
},
Expand All @@ -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', () => {
Expand Down Expand Up @@ -132,6 +158,7 @@ describe('error collection', () => {
},
source: ErrorSource.NETWORK,
stack: 'bar',
instrumentation_stack: undefined,
type: 'foo',
},
view: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -17,6 +18,7 @@ export interface ProvidedError {
error: unknown
context?: Context
source: ProvidedSource
instrumentationError: Error
}

export type ProvidedSource = 'custom' | 'network' | 'source'
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/rum-core/src/rawRumEvent.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface RawRumErrorEvent {
id: string
type?: string
stack?: string
instrumentation_stack?: string
source: ErrorSource
message: string
}
Expand Down

0 comments on commit 11d3d24

Please sign in to comment.