Skip to content

Commit f855f78

Browse files
✨[MFE] Source code context event enrichment (#4255)
1 parent 21f643f commit f855f78

File tree

4 files changed

+134
-173
lines changed

4 files changed

+134
-173
lines changed

packages/core/src/tools/experimentalFeatures.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export enum ExperimentalFeature {
2121
START_STOP_ACTION = 'start_stop_action',
2222
START_STOP_RESOURCE = 'start_stop_resource',
2323
USE_CHANGE_RECORDS = 'use_change_records',
24-
SOURCE_CODE_CONTEXT = 'source_code_context',
2524
LCP_SUBPARTS = 'lcp_subparts',
2625
INP_SUBPARTS = 'inp_subparts',
2726
}
Lines changed: 120 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { addExperimentalFeatures, ExperimentalFeature, HookNames } from '@datadog/browser-core'
1+
import { HookNames } from '@datadog/browser-core'
22
import type { RelativeTime } from '@datadog/browser-core'
33
import type { AssembleHookParams, Hooks } from '../hooks'
44
import { createHooks } from '../hooks'
@@ -36,177 +36,150 @@ describe('sourceCodeContext', () => {
3636
})
3737
}
3838

39-
describe('assemble hook when FF disabled', () => {
40-
it('should not add source code context', () => {
41-
setupBrowserWindowWithContext()
42-
startSourceCodeContext(hooks)
43-
44-
const result = hooks.triggerHook(HookNames.Assemble, {
45-
eventType: 'error',
46-
startTime: 0 as RelativeTime,
47-
domainContext: {},
48-
rawRumEvent: {
49-
type: 'error',
50-
error: {
51-
stack: MATCHING_TEST_STACK,
52-
},
39+
it('should add source code context matching the error stack first frame URL', () => {
40+
setupBrowserWindowWithContext()
41+
startSourceCodeContext(hooks)
42+
43+
const result = hooks.triggerHook(HookNames.Assemble, {
44+
eventType: 'error',
45+
startTime: 0 as RelativeTime,
46+
domainContext: {},
47+
rawRumEvent: {
48+
type: 'error',
49+
error: {
50+
stack: MATCHING_TEST_STACK,
5351
},
54-
} as AssembleHookParams)
52+
},
53+
} as AssembleHookParams)
5554

56-
expect(result).toBeUndefined()
55+
expect(result).toEqual({
56+
type: 'error',
57+
service: 'my-service',
58+
version: '1.0.0',
5759
})
5860
})
5961

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

65-
it('should add source code context matching the error stack first frame URL', () => {
66-
setupBrowserWindowWithContext()
67-
startSourceCodeContext(hooks)
68-
69-
const result = hooks.triggerHook(HookNames.Assemble, {
70-
eventType: 'error',
71-
startTime: 0 as RelativeTime,
72-
domainContext: {},
73-
rawRumEvent: {
74-
type: 'error',
75-
error: {
76-
stack: MATCHING_TEST_STACK,
77-
},
78-
},
79-
} as AssembleHookParams)
66+
const result = hooks.triggerHook(HookNames.Assemble, {
67+
eventType: 'action',
68+
startTime: 0 as RelativeTime,
69+
rawRumEvent: {
70+
type: 'action',
71+
},
72+
domainContext: {
73+
handlingStack: MATCHING_TEST_STACK,
74+
},
75+
} as AssembleHookParams)
8076

81-
expect(result).toEqual({
82-
type: 'error',
83-
service: 'my-service',
84-
version: '1.0.0',
85-
})
77+
expect(result).toEqual({
78+
type: 'action',
79+
service: 'my-service',
80+
version: '1.0.0',
8681
})
82+
})
8783

88-
it('should add source code context matching the handling_stack first frame URL', () => {
89-
setupBrowserWindowWithContext()
90-
startSourceCodeContext(hooks)
84+
it('should add source code context matching the LoAF first script source URL', () => {
85+
setupBrowserWindowWithContext()
86+
startSourceCodeContext(hooks)
9187

92-
const result = hooks.triggerHook(HookNames.Assemble, {
93-
eventType: 'action',
94-
startTime: 0 as RelativeTime,
95-
rawRumEvent: {
96-
type: 'action',
97-
},
98-
domainContext: {
99-
handlingStack: MATCHING_TEST_STACK,
88+
const result = hooks.triggerHook(HookNames.Assemble, {
89+
eventType: 'long_task',
90+
startTime: 0 as RelativeTime,
91+
domainContext: {},
92+
rawRumEvent: {
93+
type: 'long_task',
94+
long_task: {
95+
entry_type: 'long-animation-frame',
96+
scripts: [
97+
{
98+
source_url: 'http://localhost:8080/file.js',
99+
},
100+
],
100101
},
101-
} as AssembleHookParams)
102+
} as RawRumLongAnimationFrameEvent,
103+
} as AssembleHookParams)
102104

103-
expect(result).toEqual({
104-
type: 'action',
105-
service: 'my-service',
106-
version: '1.0.0',
107-
})
105+
expect(result).toEqual({
106+
type: 'long_task',
107+
service: 'my-service',
108+
version: '1.0.0',
108109
})
110+
})
109111

110-
it('should add source code context matching the LoAF first script source URL', () => {
111-
setupBrowserWindowWithContext()
112-
startSourceCodeContext(hooks)
113-
114-
const result = hooks.triggerHook(HookNames.Assemble, {
115-
eventType: 'long_task',
116-
startTime: 0 as RelativeTime,
117-
domainContext: {},
118-
rawRumEvent: {
119-
type: 'long_task',
120-
long_task: {
121-
entry_type: 'long-animation-frame',
122-
scripts: [
123-
{
124-
source_url: 'http://localhost:8080/file.js',
125-
},
126-
],
127-
},
128-
} as RawRumLongAnimationFrameEvent,
129-
} as AssembleHookParams)
130-
131-
expect(result).toEqual({
132-
type: 'long_task',
133-
service: 'my-service',
134-
version: '1.0.0',
135-
})
136-
})
112+
it('should not add source code context matching no stack', () => {
113+
setupBrowserWindowWithContext()
114+
startSourceCodeContext(hooks)
137115

138-
it('should not add source code context matching no stack', () => {
139-
setupBrowserWindowWithContext()
140-
startSourceCodeContext(hooks)
141-
142-
const result = hooks.triggerHook(HookNames.Assemble, {
143-
eventType: 'error',
144-
startTime: 0 as RelativeTime,
145-
domainContext: {},
146-
rawRumEvent: {
147-
type: 'error',
148-
error: {
149-
stack: `Error: Another error
116+
const result = hooks.triggerHook(HookNames.Assemble, {
117+
eventType: 'error',
118+
startTime: 0 as RelativeTime,
119+
domainContext: {},
120+
rawRumEvent: {
121+
type: 'error',
122+
error: {
123+
stack: `Error: Another error
150124
at anotherFunction (http://localhost:8080/another-file.js:41:27)`,
151-
},
152125
},
153-
} as AssembleHookParams)
126+
},
127+
} as AssembleHookParams)
154128

155-
expect(result).toBeUndefined()
156-
})
129+
expect(result).toBeUndefined()
130+
})
157131

158-
it('should support late updates to DD_SOURCE_CODE_CONTEXT', () => {
159-
startSourceCodeContext(hooks)
160-
161-
// Add context AFTER initialization
162-
setupBrowserWindowWithContext()
163-
164-
const result = hooks.triggerHook(HookNames.Assemble, {
165-
eventType: 'error',
166-
startTime: 0 as RelativeTime,
167-
domainContext: {},
168-
rawRumEvent: {
169-
type: 'error',
170-
error: {
171-
stack: TEST_STACK,
172-
},
173-
},
174-
} as AssembleHookParams)
132+
it('should support late updates to DD_SOURCE_CODE_CONTEXT', () => {
133+
startSourceCodeContext(hooks)
175134

176-
expect(result).toEqual({
135+
// Add context AFTER initialization
136+
setupBrowserWindowWithContext()
137+
138+
const result = hooks.triggerHook(HookNames.Assemble, {
139+
eventType: 'error',
140+
startTime: 0 as RelativeTime,
141+
domainContext: {},
142+
rawRumEvent: {
177143
type: 'error',
178-
service: 'my-service',
179-
version: '1.0.0',
180-
})
144+
error: {
145+
stack: TEST_STACK,
146+
},
147+
},
148+
} as AssembleHookParams)
149+
150+
expect(result).toEqual({
151+
type: 'error',
152+
service: 'my-service',
153+
version: '1.0.0',
181154
})
155+
})
182156

183-
it('should ignore updates to existing source code context after initialization', () => {
184-
setupBrowserWindowWithContext()
185-
startSourceCodeContext(hooks)
186-
187-
// Update existing entry
188-
browserWindow.DD_SOURCE_CODE_CONTEXT![TEST_STACK] = {
189-
service: 'updated-service',
190-
version: '1.1.0',
191-
}
192-
193-
const result = hooks.triggerHook(HookNames.Assemble, {
194-
eventType: 'error',
195-
startTime: 0 as RelativeTime,
196-
domainContext: {},
197-
rawRumEvent: {
198-
type: 'error',
199-
error: {
200-
stack: TEST_STACK,
201-
},
202-
},
203-
} as AssembleHookParams)
157+
it('should ignore updates to existing source code context after initialization', () => {
158+
setupBrowserWindowWithContext()
159+
startSourceCodeContext(hooks)
160+
161+
// Update existing entry
162+
browserWindow.DD_SOURCE_CODE_CONTEXT![TEST_STACK] = {
163+
service: 'updated-service',
164+
version: '1.1.0',
165+
}
204166

205-
expect(result).toEqual({
167+
const result = hooks.triggerHook(HookNames.Assemble, {
168+
eventType: 'error',
169+
startTime: 0 as RelativeTime,
170+
domainContext: {},
171+
rawRumEvent: {
206172
type: 'error',
207-
service: 'my-service',
208-
version: '1.0.0',
209-
})
173+
error: {
174+
stack: TEST_STACK,
175+
},
176+
},
177+
} as AssembleHookParams)
178+
179+
expect(result).toEqual({
180+
type: 'error',
181+
service: 'my-service',
182+
version: '1.0.0',
210183
})
211184
})
212185
})

packages/rum-core/src/domain/contexts/sourceCodeContext.ts

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
import {
2-
SKIPPED,
3-
computeStackTrace,
4-
objectEntries,
5-
addTelemetryError,
6-
HookNames,
7-
isExperimentalFeatureEnabled,
8-
ExperimentalFeature,
9-
} from '@datadog/browser-core'
1+
import { SKIPPED, computeStackTrace, objectEntries, addTelemetryError, HookNames } from '@datadog/browser-core'
102
import type { Hooks, DefaultRumEventAttributes, AssembleHookParams } from '../hooks'
113

124
interface SourceCodeContext {
@@ -20,10 +12,6 @@ export interface BrowserWindow {
2012
type StackFrameUrl = string
2113

2214
export function startSourceCodeContext(hooks: Hooks) {
23-
if (!isExperimentalFeatureEnabled(ExperimentalFeature.SOURCE_CODE_CONTEXT)) {
24-
return
25-
}
26-
2715
const browserWindow = window as BrowserWindow
2816
const contextByFile = new Map<StackFrameUrl, SourceCodeContext>()
2917

@@ -54,19 +42,22 @@ export function startSourceCodeContext(hooks: Hooks) {
5442
hooks.register(HookNames.Assemble, ({ domainContext, rawRumEvent }): DefaultRumEventAttributes | SKIPPED => {
5543
buildContextByFile()
5644

45+
if (contextByFile.size === 0) {
46+
return SKIPPED
47+
}
48+
5749
const url = getSourceUrl(domainContext, rawRumEvent)
50+
const context = url && contextByFile.get(url)
5851

59-
if (url) {
60-
const context = contextByFile.get(url)
61-
if (context) {
62-
return {
63-
type: rawRumEvent.type,
64-
service: context.service,
65-
version: context.version,
66-
}
67-
}
52+
if (!context) {
53+
return SKIPPED
54+
}
55+
56+
return {
57+
type: rawRumEvent.type,
58+
service: context.service,
59+
version: context.version,
6860
}
69-
return SKIPPED
7061
})
7162
}
7263

test/e2e/scenario/microfrontend.scenario.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ import type { RumEvent, RumEventDomainContext, RumInitConfiguration } from '@dat
22
import type { LogsEvent, LogsInitConfiguration, LogsEventDomainContext } from '@datadog/browser-logs'
33
import type { Page } from '@playwright/test'
44
import { test, expect } from '@playwright/test'
5-
import { ExperimentalFeature } from '@datadog/browser-core'
65
import { createTest, html } from '../lib/framework'
76

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

109
const RUM_CONFIG: Partial<RumInitConfiguration> = {
1110
service: 'main-service',
1211
version: '1.0.0',
13-
enableExperimentalFeatures: [ExperimentalFeature.SOURCE_CODE_CONTEXT],
1412
beforeSend: (event: RumEvent, domainContext: RumEventDomainContext) => {
1513
if ('handlingStack' in domainContext) {
1614
event.context!.handlingStack = domainContext.handlingStack

0 commit comments

Comments
 (0)