Skip to content

Commit 433144b

Browse files
authored
⚗️ Default debugger version and embed build identity in snapshots (#4511)
Co-authored-by: thomas.watson <thomas.watson@datadoghq.com>
1 parent 3f15a95 commit 433144b

3 files changed

Lines changed: 143 additions & 30 deletions

File tree

packages/debugger/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ datadogDebugger.init({
1616
site: '<DATADOG_SITE>',
1717
service: 'my-web-application',
1818
// env: 'production',
19-
// version: '1.0.0',
19+
// version: 'my-deployed-build-version',
2020
})
2121
```
2222

23+
When you also use the Datadog Live Debugger build plugin, `init().version` defaults to the build-time `liveDebugger.version` metadata injected into the bundle. If you pass both values explicitly and they differ, the SDK keeps the `init()` value and logs a warning.
24+
25+
If provided, `version` should be set to the immutable deployed browser build identifier used for source map upload and browser build resolution. If omitted, debugger delivery and snapshots still work, but browser build lookup and source-aware resolution may be unavailable.
26+
2327
## Troubleshooting
2428

2529
Need help? Contact [Datadog Support][3].
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,98 @@
1+
import { display } from '@datadog/browser-core'
2+
import { registerCleanupTask, replaceMockableWithSpy } from '@datadog/browser-core/test'
3+
import { initDebuggerTransport } from '../domain/api'
4+
import { startDeliveryApiPolling } from '../domain/deliveryApi'
5+
import { startDebuggerBatch } from '../transport/startDebuggerBatch'
6+
import type { BrowserWindow } from './main'
17
import { datadogDebugger } from './main'
28

39
describe('datadogDebugger', () => {
10+
const browserWindow: BrowserWindow = window
11+
12+
beforeEach(() => {
13+
delete browserWindow.__DD_LIVE_DEBUGGER_BUILD__
14+
delete browserWindow.$dd_entry
15+
delete browserWindow.$dd_return
16+
delete browserWindow.$dd_throw
17+
delete browserWindow.$dd_probes
18+
19+
registerCleanupTask(() => {
20+
delete browserWindow.__DD_LIVE_DEBUGGER_BUILD__
21+
delete browserWindow.$dd_entry
22+
delete browserWindow.$dd_return
23+
delete browserWindow.$dd_throw
24+
delete browserWindow.$dd_probes
25+
})
26+
})
27+
428
it('should only expose init, version, and onReady', () => {
529
expect(datadogDebugger).toEqual({
630
init: jasmine.any(Function),
731
version: jasmine.any(String),
832
onReady: jasmine.any(Function),
933
})
1034
})
35+
36+
it('should default the init version from build-plugin metadata', async () => {
37+
browserWindow.__DD_LIVE_DEBUGGER_BUILD__ = { version: 'build-version' }
38+
replaceMockableWithSpy(startDebuggerBatch).and.callFake(() => ({
39+
flushController: undefined as any,
40+
add: () => undefined,
41+
flush: () => undefined,
42+
stop: () => undefined,
43+
upsert: () => undefined,
44+
}))
45+
const initTransportSpy = replaceMockableWithSpy(initDebuggerTransport)
46+
const startDeliveryApiPollingSpy = replaceMockableWithSpy(startDeliveryApiPolling)
47+
48+
datadogDebugger.init({
49+
clientToken: 'client-token',
50+
service: 'service-name',
51+
env: 'staging',
52+
})
53+
54+
await flushPromises()
55+
56+
expect(initTransportSpy).toHaveBeenCalledWith(
57+
jasmine.objectContaining({ version: 'build-version' }),
58+
jasmine.anything()
59+
)
60+
expect(startDeliveryApiPollingSpy).toHaveBeenCalledWith(jasmine.objectContaining({ version: 'build-version' }))
61+
expect(browserWindow.$dd_entry).toBeDefined()
62+
expect(browserWindow.$dd_return).toBeDefined()
63+
expect(browserWindow.$dd_throw).toBeDefined()
64+
expect(browserWindow.$dd_probes).toBeDefined()
65+
})
66+
67+
it('should warn when the explicit init version mismatches build-plugin metadata', async () => {
68+
browserWindow.__DD_LIVE_DEBUGGER_BUILD__ = { version: 'build-version' }
69+
replaceMockableWithSpy(startDebuggerBatch).and.callFake(() => ({
70+
flushController: undefined as any,
71+
add: () => undefined,
72+
flush: () => undefined,
73+
stop: () => undefined,
74+
upsert: () => undefined,
75+
}))
76+
replaceMockableWithSpy(initDebuggerTransport)
77+
const startDeliveryApiPollingSpy = replaceMockableWithSpy(startDeliveryApiPolling)
78+
const warnSpy = spyOn(display, 'warn')
79+
80+
datadogDebugger.init({
81+
clientToken: 'client-token',
82+
service: 'service-name',
83+
env: 'staging',
84+
version: 'runtime-version',
85+
})
86+
87+
await flushPromises()
88+
89+
expect(warnSpy).toHaveBeenCalledWith(jasmine.stringMatching(/does not match the build-plugin version/))
90+
expect(startDeliveryApiPollingSpy).toHaveBeenCalledWith(jasmine.objectContaining({ version: 'runtime-version' }))
91+
})
1192
})
93+
94+
async function flushPromises() {
95+
for (let i = 0; i < 10; i++) {
96+
await Promise.resolve()
97+
}
98+
}

packages/debugger/src/entries/main.ts

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,15 @@
66
* @see [Live Debugger Documentation](https://docs.datadoghq.com/tracing/live_debugger/)
77
*/
88

9-
import { defineGlobal, getGlobalObject, makePublicApi } from '@datadog/browser-core'
9+
import { defineGlobal, display, getGlobalObject, makePublicApi, mockable } from '@datadog/browser-core'
1010
import type { PublicApi, Site } from '@datadog/browser-core'
11-
import { onEntry, onReturn, onThrow, initDebuggerTransport } from '../domain/api'
11+
import { initDebuggerTransport, onEntry, onReturn, onThrow } from '../domain/api'
1212
import { startDeliveryApiPolling } from '../domain/deliveryApi'
1313
import { getProbes } from '../domain/probes'
1414
import { startDebuggerBatch } from '../transport/startDebuggerBatch'
1515

16-
type DebuggerInstrumentationGlobal = typeof globalThis & {
17-
$dd_entry?: typeof onEntry
18-
$dd_return?: typeof onReturn
19-
$dd_throw?: typeof onThrow
20-
$dd_probes?: typeof getProbes
16+
export interface DebuggerBuildMetadata {
17+
version?: string
2118
}
2219

2320
/**
@@ -136,39 +133,68 @@ export interface DatadogDebugger extends PublicApi {
136133
* service: 'my-app',
137134
* site: 'datadoghq.com',
138135
* env: 'production'
136+
* version: 'my-deployed-build-version',
139137
* })
140138
* ```
141139
*/
142140
init: (initConfiguration: DebuggerInitConfiguration) => void
143141
}
144142

143+
export interface BrowserWindow extends Window {
144+
DD_DEBUGGER?: DatadogDebugger
145+
__DD_LIVE_DEBUGGER_BUILD__?: DebuggerBuildMetadata
146+
$dd_entry?: typeof onEntry
147+
$dd_return?: typeof onReturn
148+
$dd_throw?: typeof onThrow
149+
$dd_probes?: typeof getProbes
150+
}
151+
152+
function resolveDebuggerVersion(initConfiguration: DebuggerInitConfiguration): string | undefined {
153+
const buildVersion = getGlobalObject<BrowserWindow>().__DD_LIVE_DEBUGGER_BUILD__?.version
154+
155+
if (
156+
initConfiguration.version !== undefined &&
157+
buildVersion !== undefined &&
158+
initConfiguration.version !== buildVersion
159+
) {
160+
display.warn(
161+
`Debugger: init version "${initConfiguration.version}" does not match the build-plugin version "${buildVersion}". Using the init version.`
162+
)
163+
}
164+
165+
return initConfiguration.version ?? buildVersion
166+
}
167+
145168
/**
146169
* Create the public API for the Live Debugger
147170
*/
148171
function makeDebuggerPublicApi(): DatadogDebugger {
149172
return makePublicApi<DatadogDebugger>({
150173
init: (initConfiguration: DebuggerInitConfiguration) => {
174+
const resolvedConfiguration = {
175+
...initConfiguration,
176+
version: resolveDebuggerVersion(initConfiguration),
177+
}
178+
151179
// Initialize debugger's own transport
152-
const batch = startDebuggerBatch(initConfiguration)
153-
initDebuggerTransport(initConfiguration, batch)
180+
const batch = mockable(startDebuggerBatch)(resolvedConfiguration)
181+
mockable(initDebuggerTransport)(resolvedConfiguration, batch)
154182

155183
// Expose internal hooks on globalThis for instrumented code
156-
if (typeof globalThis !== 'undefined') {
157-
const debuggerGlobal = globalThis as DebuggerInstrumentationGlobal
158-
debuggerGlobal.$dd_entry = onEntry
159-
debuggerGlobal.$dd_return = onReturn
160-
debuggerGlobal.$dd_throw = onThrow
161-
debuggerGlobal.$dd_probes = getProbes
162-
}
163-
164-
startDeliveryApiPolling({
165-
service: initConfiguration.service,
166-
clientToken: initConfiguration.clientToken,
167-
site: initConfiguration.site,
168-
proxy: initConfiguration.proxy,
169-
env: initConfiguration.env,
170-
version: initConfiguration.version,
171-
pollInterval: initConfiguration.pollInterval,
184+
const debuggerGlobal = getGlobalObject<BrowserWindow>()
185+
debuggerGlobal.$dd_entry = onEntry
186+
debuggerGlobal.$dd_return = onReturn
187+
debuggerGlobal.$dd_throw = onThrow
188+
debuggerGlobal.$dd_probes = getProbes
189+
190+
mockable(startDeliveryApiPolling)({
191+
service: resolvedConfiguration.service,
192+
clientToken: resolvedConfiguration.clientToken,
193+
site: resolvedConfiguration.site,
194+
proxy: resolvedConfiguration.proxy,
195+
env: resolvedConfiguration.env,
196+
version: resolvedConfiguration.version,
197+
pollInterval: resolvedConfiguration.pollInterval,
172198
})
173199
},
174200
})
@@ -183,8 +209,4 @@ function makeDebuggerPublicApi(): DatadogDebugger {
183209
*/
184210
export const datadogDebugger = makeDebuggerPublicApi()
185211

186-
export interface BrowserWindow extends Window {
187-
DD_DEBUGGER?: DatadogDebugger
188-
}
189-
190212
defineGlobal(getGlobalObject<BrowserWindow>(), 'DD_DEBUGGER', datadogDebugger)

0 commit comments

Comments
 (0)