Skip to content

Commit 1a6bded

Browse files
committed
Default debugger init version from build metadata
Use build-plugin injected Live Debugger metadata as the default runtime `init().version`, and warn when an explicit init version disagrees. Document the fallback behavior and cover the new version resolution path in unit tests.
1 parent 13d0744 commit 1a6bded

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
/**
@@ -96,39 +93,68 @@ export interface DatadogDebugger extends PublicApi {
9693
* service: 'my-app',
9794
* site: 'datadoghq.com',
9895
* env: 'production'
96+
* version: 'my-deployed-build-version',
9997
* })
10098
* ```
10199
*/
102100
init: (initConfiguration: DebuggerInitConfiguration) => void
103101
}
104102

103+
export interface BrowserWindow extends Window {
104+
DD_DEBUGGER?: DatadogDebugger
105+
__DD_LIVE_DEBUGGER_BUILD__?: DebuggerBuildMetadata
106+
$dd_entry?: typeof onEntry
107+
$dd_return?: typeof onReturn
108+
$dd_throw?: typeof onThrow
109+
$dd_probes?: typeof getProbes
110+
}
111+
112+
function resolveDebuggerVersion(initConfiguration: DebuggerInitConfiguration): string | undefined {
113+
const buildVersion = getGlobalObject<BrowserWindow>().__DD_LIVE_DEBUGGER_BUILD__?.version
114+
115+
if (
116+
initConfiguration.version !== undefined &&
117+
buildVersion !== undefined &&
118+
initConfiguration.version !== buildVersion
119+
) {
120+
display.warn(
121+
`Debugger: init version "${initConfiguration.version}" does not match the build-plugin version "${buildVersion}". Using the init version.`
122+
)
123+
}
124+
125+
return initConfiguration.version ?? buildVersion
126+
}
127+
105128
/**
106129
* Create the public API for the Live Debugger
107130
*/
108131
function makeDebuggerPublicApi(): DatadogDebugger {
109132
return makePublicApi<DatadogDebugger>({
110133
init: (initConfiguration: DebuggerInitConfiguration) => {
134+
const resolvedConfiguration = {
135+
...initConfiguration,
136+
version: resolveDebuggerVersion(initConfiguration),
137+
}
138+
111139
// Initialize debugger's own transport
112-
const batch = startDebuggerBatch(initConfiguration)
113-
initDebuggerTransport(initConfiguration, batch)
140+
const batch = mockable(startDebuggerBatch)(resolvedConfiguration)
141+
mockable(initDebuggerTransport)(resolvedConfiguration, batch)
114142

115143
// Expose internal hooks on globalThis for instrumented code
116-
if (typeof globalThis !== 'undefined') {
117-
const debuggerGlobal = globalThis as DebuggerInstrumentationGlobal
118-
debuggerGlobal.$dd_entry = onEntry
119-
debuggerGlobal.$dd_return = onReturn
120-
debuggerGlobal.$dd_throw = onThrow
121-
debuggerGlobal.$dd_probes = getProbes
122-
}
123-
124-
startDeliveryApiPolling({
125-
service: initConfiguration.service,
126-
clientToken: initConfiguration.clientToken,
127-
site: initConfiguration.site,
128-
proxy: initConfiguration.proxy,
129-
env: initConfiguration.env,
130-
version: initConfiguration.version,
131-
pollInterval: initConfiguration.pollInterval,
144+
const debuggerGlobal = getGlobalObject<BrowserWindow>()
145+
debuggerGlobal.$dd_entry = onEntry
146+
debuggerGlobal.$dd_return = onReturn
147+
debuggerGlobal.$dd_throw = onThrow
148+
debuggerGlobal.$dd_probes = getProbes
149+
150+
mockable(startDeliveryApiPolling)({
151+
service: resolvedConfiguration.service,
152+
clientToken: resolvedConfiguration.clientToken,
153+
site: resolvedConfiguration.site,
154+
proxy: resolvedConfiguration.proxy,
155+
env: resolvedConfiguration.env,
156+
version: resolvedConfiguration.version,
157+
pollInterval: resolvedConfiguration.pollInterval,
132158
})
133159
},
134160
})
@@ -143,8 +169,4 @@ function makeDebuggerPublicApi(): DatadogDebugger {
143169
*/
144170
export const datadogDebugger = makeDebuggerPublicApi()
145171

146-
export interface BrowserWindow extends Window {
147-
DD_DEBUGGER?: DatadogDebugger
148-
}
149-
150172
defineGlobal(getGlobalObject<BrowserWindow>(), 'DD_DEBUGGER', datadogDebugger)

0 commit comments

Comments
 (0)