Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/src/tools/stackTrace/handlingStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { computeStackTrace } from './computeStackTrace'
* - No monitored function should encapsulate it, that is why we need to use callMonitored inside it.
*/
export function createHandlingStack(
type: 'console error' | 'action' | 'error' | 'instrumented method' | 'log' | 'react error'
type: 'console error' | 'action' | 'error' | 'instrumented method' | 'log' | 'react error' | 'view' | 'vital'
): string {
/**
* Skip the two internal frames:
Expand Down
5 changes: 4 additions & 1 deletion packages/rum-core/src/boot/rumPublicApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ describe('rum public api', () => {
})
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)
rumPublicApi.startView('foo')
expect(startViewSpy.calls.argsFor(0)[0]).toEqual({ name: 'foo' })
expect(startViewSpy.calls.argsFor(0)[0]).toEqual({ name: 'foo', handlingStack: jasmine.any(String) })
})

it('should call RUM results startView with the view options', () => {
Expand All @@ -610,6 +610,7 @@ describe('rum public api', () => {
service: 'bar',
version: 'baz',
context: { foo: 'bar' },
handlingStack: jasmine.any(String),
})
})
})
Expand Down Expand Up @@ -685,6 +686,7 @@ describe('rum public api', () => {
expect(startDurationVitalSpy).toHaveBeenCalledWith('foo', {
description: 'description-value',
context: { foo: 'bar' },
handlingStack: jasmine.any(String),
})
})
})
Expand Down Expand Up @@ -827,6 +829,7 @@ describe('rum public api', () => {
duration: 100,
context: { foo: 'bar' },
description: 'description-value',
handlingStack: jasmine.any(String),
type: VitalType.DURATION,
})
})
Expand Down
55 changes: 33 additions & 22 deletions packages/rum-core/src/boot/rumPublicApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,11 +625,14 @@ export function makeRumPublicApi(
const startView: {
(name?: string): void
(options: ViewOptions): void
} = monitor((options?: string | ViewOptions) => {
const sanitizedOptions = typeof options === 'object' ? options : { name: options }
strategy.startView(sanitizedOptions)
addTelemetryUsage({ feature: 'start-view' })
})
} = (options?: string | ViewOptions) => {
const handlingStack = createHandlingStack('view')
callMonitored(() => {
const sanitizedOptions = typeof options === 'object' ? options : { name: options }
strategy.startView({ ...sanitizedOptions, handlingStack })
addTelemetryUsage({ feature: 'start-view' })
})
}

const rumPublicApi: RumPublicApi = makePublicApi<RumPublicApi>({
init: (initConfiguration) => {
Expand Down Expand Up @@ -838,25 +841,33 @@ export function makeRumPublicApi(

stopSessionReplayRecording: monitor(() => recorderApi.stop()),

addDurationVital: monitor((name, options) => {
addTelemetryUsage({ feature: 'add-duration-vital' })
strategy.addDurationVital({
name: sanitize(name)!,
type: VitalType.DURATION,
startClocks: timeStampToClocks(options.startTime as TimeStamp),
duration: options.duration as Duration,
context: sanitize(options && options.context) as Context,
description: sanitize(options && options.description) as string | undefined,
addDurationVital: (name, options) => {
const handlingStack = createHandlingStack('vital')
callMonitored(() => {
addTelemetryUsage({ feature: 'add-duration-vital' })
strategy.addDurationVital({
name: sanitize(name)!,
type: VitalType.DURATION,
startClocks: timeStampToClocks(options.startTime as TimeStamp),
duration: options.duration as Duration,
context: sanitize(options && options.context) as Context,
description: sanitize(options && options.description) as string | undefined,
handlingStack,
})
})
}),
},

startDurationVital: monitor((name, options) => {
addTelemetryUsage({ feature: 'start-duration-vital' })
return strategy.startDurationVital(sanitize(name)!, {
context: sanitize(options && options.context) as Context,
description: sanitize(options && options.description) as string | undefined,
})
}),
startDurationVital: (name, options) => {
const handlingStack = createHandlingStack('vital')
return callMonitored(() => {
addTelemetryUsage({ feature: 'start-duration-vital' })
return strategy.startDurationVital(sanitize(name)!, {
context: sanitize(options && options.context) as Context,
description: sanitize(options && options.description) as string | undefined,
handlingStack,
})
}) as DurationVitalReference
},

stopDurationVital: monitor((nameOrRef, options) => {
addTelemetryUsage({ feature: 'stop-duration-vital' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('eventCollection', () => {
duration: 100,
},
}
const domainContext: RumEventDomainContext = { custom: 'context' }
const domainContext: RumEventDomainContext = {}

eventCollection.addEvent(startTime, event, domainContext, duration)

Expand Down
9 changes: 9 additions & 0 deletions packages/rum-core/src/domain/view/trackViews.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,15 @@ describe('start view', () => {
expect(getViewUpdate(1).duration).toBe(50 as Duration)
expect(getViewUpdate(2).startClocks.relative).toBe(50 as RelativeTime)
})

it('should create view with handling stack', () => {
const { startView, getViewUpdate } = viewTest

startView({ name: 'foo', handlingStack: 'Error\n at foo\n at bar' })

// The new view is at index 2 (after the initial view end and the new view start)
expect(getViewUpdate(2).handlingStack).toBe('Error\n at foo\n at bar')
})
})

describe('view event count', () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/rum-core/src/domain/view/trackViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface ViewEvent {
version?: string
context?: Context
location: Readonly<Location>
handlingStack?: string
commonViewMetrics: CommonViewMetrics
initialViewMetrics: InitialViewMetrics
customTimings: ViewCustomTimings
Expand Down Expand Up @@ -99,6 +100,7 @@ export interface ViewOptions {
service?: RumInitConfiguration['service']
version?: RumInitConfiguration['version']
context?: Context
handlingStack?: string
}

export function trackViews(
Expand Down Expand Up @@ -228,6 +230,7 @@ function newView(
const service = viewOptions?.service || configuration.service
const version = viewOptions?.version || configuration.version
const context = viewOptions?.context
const handlingStack = viewOptions?.handlingStack

if (context) {
contextManager.setContext(context)
Expand Down Expand Up @@ -323,6 +326,7 @@ function newView(
context: contextManager.getContext(),
loadingType,
location,
handlingStack,
startClocks,
commonViewMetrics: getCommonViewMetrics(),
initialViewMetrics,
Expand Down
1 change: 1 addition & 0 deletions packages/rum-core/src/domain/view/viewCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ function processViewUpdate(
duration: view.duration,
domainContext: {
location: view.location,
handlingStack: view.handlingStack,
},
}
}
Expand Down
11 changes: 11 additions & 0 deletions packages/rum-core/src/domain/vital/vitalCollection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,17 @@ describe('vitalCollection', () => {
expect(rawRumEvents[0].domainContext).toEqual({})
})

it('should create vital with handling stack', () => {
vitalCollection.startDurationVital('foo', {
handlingStack: 'Error\n at foo\n at bar',
})
vitalCollection.stopDurationVital('foo')

expect(rawRumEvents[0].domainContext).toEqual({
handlingStack: 'Error\n at foo\n at bar',
})
})

it('should collect raw rum event from operation step vital', () => {
mockExperimentalFeatures([ExperimentalFeature.FEATURE_OPERATION_VITAL])
vitalCollection.addOperationStepVital('foo', 'start')
Expand Down
17 changes: 13 additions & 4 deletions packages/rum-core/src/domain/vital/vitalCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,14 @@ export interface VitalOptions {
/**
* Duration vital options
*/
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface DurationVitalOptions extends VitalOptions {}

export interface DurationVitalOptions extends VitalOptions {
/**
* Handling stack (internal use only)
*/
handlingStack?: string
}

export interface FeatureOperationOptions extends VitalOptions {
operationKey?: string
}
Expand Down Expand Up @@ -64,11 +70,13 @@ export interface DurationVitalReference {
export interface DurationVitalStart extends DurationVitalOptions {
name: string
startClocks: ClocksState
handlingStack?: string
}

interface BaseVital extends VitalOptions {
name: string
startClocks: ClocksState
handlingStack?: string
}
export interface DurationVital extends BaseVital {
type: typeof VitalType.DURATION
Expand Down Expand Up @@ -200,11 +208,12 @@ function buildDurationVital(
duration: elapsed(startClocks.timeStamp, stopClocks.timeStamp),
context: combine(vitalStart.context, stopOptions.context),
description: stopOptions.description ?? vitalStart.description,
handlingStack: vitalStart.handlingStack,
}
}

function processVital(vital: DurationVital | OperationStepVital): RawRumEventCollectedData<RawRumVitalEvent> {
const { startClocks, type, name, description, context } = vital
const { startClocks, type, name, description, context, handlingStack } = vital
const vitalData = {
id: generateUUID(),
type,
Expand All @@ -228,6 +237,6 @@ function processVital(vital: DurationVital | OperationStepVital): RawRumEventCol
},
startTime: startClocks.relative,
duration: type === VitalType.DURATION ? vital.duration : undefined,
domainContext: {},
domainContext: handlingStack ? { handlingStack } : {},
}
}
6 changes: 4 additions & 2 deletions packages/rum-core/src/domainContext.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type RumEventDomainContext<T extends RumEventType = any> = T extends type

export interface RumViewEventDomainContext {
location: Readonly<Location>
handlingStack?: string
}

export interface RumActionEventDomainContext {
Expand Down Expand Up @@ -57,5 +58,6 @@ export interface RumLongTaskEventDomainContext {
performanceEntry: PerformanceEntry
}

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface RumVitalEventDomainContext {}
export interface RumVitalEventDomainContext {
handlingStack?: string
}
67 changes: 67 additions & 0 deletions test/e2e/scenario/microfrontend.scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,47 @@ test.describe('microfrontend', () => {
expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX)
})

createTest('expose handling stack for DD_RUM.startView')
.withRum(RUM_CONFIG)
.withRumInit((configuration) => {
window.DD_RUM!.init(configuration)

function testHandlingStack() {
window.DD_RUM!.startView({ name: 'test-view' })
}

testHandlingStack()
})
.run(async ({ intakeRegistry, flushEvents }) => {
await flushEvents()

const event = intakeRegistry.rumViewEvents.find((event) => event.view.name === 'test-view')

expect(event).toBeTruthy()
expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX)
})

createTest('expose handling stack for DD_RUM.startDurationVital')
.withRum(RUM_CONFIG)
.withRumInit((configuration) => {
window.DD_RUM!.init(configuration)

function testHandlingStack() {
const ref = window.DD_RUM!.startDurationVital('test-vital')
window.DD_RUM!.stopDurationVital(ref)
}

testHandlingStack()
})
.run(async ({ intakeRegistry, flushEvents }) => {
await flushEvents()

const event = intakeRegistry.rumVitalEvents.find((event) => event.vital.name === 'test-vital')

expect(event).toBeTruthy()
expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX)
})

test.describe('console apis', () => {
createTest('expose handling stack for console.log')
.withLogs(LOGS_CONFIG)
Expand Down Expand Up @@ -375,5 +416,31 @@ test.describe('microfrontend', () => {

expect(longTaskEvent).toMatchObject({ service: 'mf-service', version: '0.1.0' })
})

createTest('manual views should have service and version from source code context')
.withRum(RUM_CONFIG)
.withBody(createBody("window.DD_RUM.startView({ name: 'test-view' })"))
.run(async ({ intakeRegistry, flushEvents, page, baseUrl }) => {
await setSourceCodeContext(page, baseUrl)
await page.locator('button').click()
await flushEvents()

const viewEvent = intakeRegistry.rumViewEvents.find((event) => event.view.name === 'test-view')
expect(viewEvent).toMatchObject({ service: 'mf-service', version: '0.1.0' })
})

createTest('duration vitals should have service and version from source code context')
.withRum(RUM_CONFIG)
.withBody(
createBody("const ref = window.DD_RUM.startDurationVital('test-vital'); window.DD_RUM.stopDurationVital(ref)")
)
.run(async ({ intakeRegistry, flushEvents, page, baseUrl }) => {
await setSourceCodeContext(page, baseUrl)
await page.locator('button').click()
await flushEvents()

const vitalEvent = intakeRegistry.rumVitalEvents.find((event) => event.vital.name === 'test-vital')
expect(vitalEvent).toMatchObject({ service: 'mf-service', version: '0.1.0' })
})
})
})