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
1 change: 0 additions & 1 deletion packages/core/src/tools/experimentalFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export enum ExperimentalFeature {
START_STOP_ACTION = 'start_stop_action',
START_STOP_RESOURCE = 'start_stop_resource',
USE_CHANGE_RECORDS = 'use_change_records',
SOURCE_CODE_CONTEXT = 'source_code_context',
LCP_SUBPARTS = 'lcp_subparts',
INP_SUBPARTS = 'inp_subparts',
}
Expand Down
267 changes: 120 additions & 147 deletions packages/rum-core/src/domain/contexts/sourceCodeContext.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addExperimentalFeatures, ExperimentalFeature, HookNames } from '@datadog/browser-core'
import { HookNames } from '@datadog/browser-core'
import type { RelativeTime } from '@datadog/browser-core'
import type { AssembleHookParams, Hooks } from '../hooks'
import { createHooks } from '../hooks'
Expand Down Expand Up @@ -36,177 +36,150 @@ describe('sourceCodeContext', () => {
})
}

describe('assemble hook when FF disabled', () => {
it('should not add source code context', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)

const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'error',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'error',
error: {
stack: MATCHING_TEST_STACK,
},
it('should add source code context matching the error stack first frame URL', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)

const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'error',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'error',
error: {
stack: MATCHING_TEST_STACK,
},
} as AssembleHookParams)
},
} as AssembleHookParams)

expect(result).toBeUndefined()
expect(result).toEqual({
type: 'error',
service: 'my-service',
version: '1.0.0',
})
})

describe('assemble hook when FF enabled', () => {
beforeEach(() => {
addExperimentalFeatures([ExperimentalFeature.SOURCE_CODE_CONTEXT])
})
it('should add source code context matching the handling_stack first frame URL', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)

it('should add source code context matching the error stack first frame URL', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)

const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'error',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'error',
error: {
stack: MATCHING_TEST_STACK,
},
},
} as AssembleHookParams)
const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'action',
startTime: 0 as RelativeTime,
rawRumEvent: {
type: 'action',
},
domainContext: {
handlingStack: MATCHING_TEST_STACK,
},
} as AssembleHookParams)

expect(result).toEqual({
type: 'error',
service: 'my-service',
version: '1.0.0',
})
expect(result).toEqual({
type: 'action',
service: 'my-service',
version: '1.0.0',
})
})

it('should add source code context matching the handling_stack first frame URL', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)
it('should add source code context matching the LoAF first script source URL', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)

const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'action',
startTime: 0 as RelativeTime,
rawRumEvent: {
type: 'action',
},
domainContext: {
handlingStack: MATCHING_TEST_STACK,
const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'long_task',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'long_task',
long_task: {
entry_type: 'long-animation-frame',
scripts: [
{
source_url: 'http://localhost:8080/file.js',
},
],
},
} as AssembleHookParams)
} as RawRumLongAnimationFrameEvent,
} as AssembleHookParams)

expect(result).toEqual({
type: 'action',
service: 'my-service',
version: '1.0.0',
})
expect(result).toEqual({
type: 'long_task',
service: 'my-service',
version: '1.0.0',
})
})

it('should add source code context matching the LoAF first script source URL', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)

const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'long_task',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'long_task',
long_task: {
entry_type: 'long-animation-frame',
scripts: [
{
source_url: 'http://localhost:8080/file.js',
},
],
},
} as RawRumLongAnimationFrameEvent,
} as AssembleHookParams)

expect(result).toEqual({
type: 'long_task',
service: 'my-service',
version: '1.0.0',
})
})
it('should not add source code context matching no stack', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)

it('should not add source code context matching no stack', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)

const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'error',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'error',
error: {
stack: `Error: Another error
const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'error',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'error',
error: {
stack: `Error: Another error
at anotherFunction (http://localhost:8080/another-file.js:41:27)`,
},
},
} as AssembleHookParams)
},
} as AssembleHookParams)

expect(result).toBeUndefined()
})
expect(result).toBeUndefined()
})

it('should support late updates to DD_SOURCE_CODE_CONTEXT', () => {
startSourceCodeContext(hooks)

// Add context AFTER initialization
setupBrowserWindowWithContext()

const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'error',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'error',
error: {
stack: TEST_STACK,
},
},
} as AssembleHookParams)
it('should support late updates to DD_SOURCE_CODE_CONTEXT', () => {
startSourceCodeContext(hooks)

expect(result).toEqual({
// Add context AFTER initialization
setupBrowserWindowWithContext()

const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'error',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'error',
service: 'my-service',
version: '1.0.0',
})
error: {
stack: TEST_STACK,
},
},
} as AssembleHookParams)

expect(result).toEqual({
type: 'error',
service: 'my-service',
version: '1.0.0',
})
})

it('should ignore updates to existing source code context after initialization', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)

// Update existing entry
browserWindow.DD_SOURCE_CODE_CONTEXT![TEST_STACK] = {
service: 'updated-service',
version: '1.1.0',
}

const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'error',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'error',
error: {
stack: TEST_STACK,
},
},
} as AssembleHookParams)
it('should ignore updates to existing source code context after initialization', () => {
setupBrowserWindowWithContext()
startSourceCodeContext(hooks)

// Update existing entry
browserWindow.DD_SOURCE_CODE_CONTEXT![TEST_STACK] = {
service: 'updated-service',
version: '1.1.0',
}

expect(result).toEqual({
const result = hooks.triggerHook(HookNames.Assemble, {
eventType: 'error',
startTime: 0 as RelativeTime,
domainContext: {},
rawRumEvent: {
type: 'error',
service: 'my-service',
version: '1.0.0',
})
error: {
stack: TEST_STACK,
},
},
} as AssembleHookParams)

expect(result).toEqual({
type: 'error',
service: 'my-service',
version: '1.0.0',
})
})
})
4 changes: 0 additions & 4 deletions packages/rum-core/src/domain/contexts/sourceCodeContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ export interface BrowserWindow {
type StackFrameUrl = string

export function startSourceCodeContext(hooks: Hooks) {
if (!isExperimentalFeatureEnabled(ExperimentalFeature.SOURCE_CODE_CONTEXT)) {
return
}

const browserWindow = window as BrowserWindow
Comment on lines 22 to 23

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip source-context hook when no context is configured

Removing the early return in startSourceCodeContext means every RUM session now registers this assemble hook, even when window.DD_SOURCE_CODE_CONTEXT is never provided; in that case the hook still runs for each event and calls getSourceUrl, which parses stack input on the hot path with no chance of producing enrichment. On high-event pages this introduces avoidable per-event CPU overhead for all customers who are not using source-code-context enrichment.

Useful? React with 👍 / 👎.

const contextByFile = new Map<StackFrameUrl, SourceCodeContext>()

Expand Down
2 changes: 0 additions & 2 deletions test/e2e/scenario/microfrontend.scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import type { RumEvent, RumEventDomainContext, RumInitConfiguration } from '@dat
import type { LogsEvent, LogsInitConfiguration, LogsEventDomainContext } from '@datadog/browser-logs'
import type { Page } from '@playwright/test'
import { test, expect } from '@playwright/test'
import { ExperimentalFeature } from '@datadog/browser-core'
import { createTest, html } from '../lib/framework'

const HANDLING_STACK_REGEX = /^HandlingStack: .*\n\s+at testHandlingStack @/

const RUM_CONFIG: Partial<RumInitConfiguration> = {
service: 'main-service',
version: '1.0.0',
enableExperimentalFeatures: [ExperimentalFeature.SOURCE_CODE_CONTEXT],
beforeSend: (event: RumEvent, domainContext: RumEventDomainContext) => {
if ('handlingStack' in domainContext) {
event.context!.handlingStack = domainContext.handlingStack
Expand Down