From 0a9836c6709a8a75836e30d578c5870eab108110 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Thu, 30 Apr 2026 06:48:12 +0200 Subject: [PATCH 1/2] Default args and locals params in debugger snapshot hooks Allow the build plugin to omit empty args/locals arguments from $dd_entry, $dd_return, and $dd_throw calls by defaulting the parameters to {} in onEntry, onReturn, and onThrow. --- packages/debugger/src/domain/api.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/debugger/src/domain/api.ts b/packages/debugger/src/domain/api.ts index cb5ad3f107..7e3ca90367 100644 --- a/packages/debugger/src/domain/api.ts +++ b/packages/debugger/src/domain/api.ts @@ -44,7 +44,7 @@ export function resetDebuggerTransport(): void { * @param self - The 'this' context * @param args - Function arguments */ -export function onEntry(probes: InitializedProbe[], self: any, args: Record): void { +export function onEntry(probes: InitializedProbe[], self: any, args: Record = {}): void { const start = performance.now() const captureCtx: CaptureContext = { deadline: start + SNAPSHOT_TIMEOUT_MS, timedOut: false } @@ -125,8 +125,8 @@ export function onReturn( probes: InitializedProbe[], value: any, self: any, - args: Record, - locals: Record + args: Record = {}, + locals: Record = {} ): any { const end = performance.now() const captureCtx: CaptureContext = { deadline: performance.now() + SNAPSHOT_TIMEOUT_MS, timedOut: false } @@ -195,7 +195,7 @@ export function onReturn( * @param self - The 'this' context * @param args - Function arguments */ -export function onThrow(probes: InitializedProbe[], error: Error, self: any, args: Record): void { +export function onThrow(probes: InitializedProbe[], error: Error, self: any, args: Record = {}): void { const end = performance.now() const captureCtx: CaptureContext = { deadline: performance.now() + SNAPSHOT_TIMEOUT_MS, timedOut: false } From cd05b6a5aeffaefe271bb5507b0b2ce41682ecf7 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Tue, 21 Apr 2026 07:29:46 +0200 Subject: [PATCH 2/2] 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. --- packages/debugger/README.md | 6 +- packages/debugger/src/entries/main.spec.ts | 87 ++++++++++++++++++++++ packages/debugger/src/entries/main.ts | 80 ++++++++++++-------- 3 files changed, 143 insertions(+), 30 deletions(-) diff --git a/packages/debugger/README.md b/packages/debugger/README.md index 463982ce71..cee8044656 100644 --- a/packages/debugger/README.md +++ b/packages/debugger/README.md @@ -16,10 +16,14 @@ datadogDebugger.init({ site: '', service: 'my-web-application', // env: 'production', - // version: '1.0.0', + // version: 'my-deployed-build-version', }) ``` +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. + +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. + ## Troubleshooting Need help? Contact [Datadog Support][3]. diff --git a/packages/debugger/src/entries/main.spec.ts b/packages/debugger/src/entries/main.spec.ts index c75239d842..52495be1ff 100644 --- a/packages/debugger/src/entries/main.spec.ts +++ b/packages/debugger/src/entries/main.spec.ts @@ -1,6 +1,30 @@ +import { display } from '@datadog/browser-core' +import { registerCleanupTask, replaceMockableWithSpy } from '@datadog/browser-core/test' +import { initDebuggerTransport } from '../domain/api' +import { startDeliveryApiPolling } from '../domain/deliveryApi' +import { startDebuggerBatch } from '../transport/startDebuggerBatch' +import type { BrowserWindow } from './main' import { datadogDebugger } from './main' describe('datadogDebugger', () => { + const browserWindow: BrowserWindow = window + + beforeEach(() => { + delete browserWindow.__DD_LIVE_DEBUGGER_BUILD__ + delete browserWindow.$dd_entry + delete browserWindow.$dd_return + delete browserWindow.$dd_throw + delete browserWindow.$dd_probes + + registerCleanupTask(() => { + delete browserWindow.__DD_LIVE_DEBUGGER_BUILD__ + delete browserWindow.$dd_entry + delete browserWindow.$dd_return + delete browserWindow.$dd_throw + delete browserWindow.$dd_probes + }) + }) + it('should only expose init, version, and onReady', () => { expect(datadogDebugger).toEqual({ init: jasmine.any(Function), @@ -8,4 +32,67 @@ describe('datadogDebugger', () => { onReady: jasmine.any(Function), }) }) + + it('should default the init version from build-plugin metadata', async () => { + browserWindow.__DD_LIVE_DEBUGGER_BUILD__ = { version: 'build-version' } + replaceMockableWithSpy(startDebuggerBatch).and.callFake(() => ({ + flushController: undefined as any, + add: () => undefined, + flush: () => undefined, + stop: () => undefined, + upsert: () => undefined, + })) + const initTransportSpy = replaceMockableWithSpy(initDebuggerTransport) + const startDeliveryApiPollingSpy = replaceMockableWithSpy(startDeliveryApiPolling) + + datadogDebugger.init({ + clientToken: 'client-token', + service: 'service-name', + env: 'staging', + }) + + await flushPromises() + + expect(initTransportSpy).toHaveBeenCalledWith( + jasmine.objectContaining({ version: 'build-version' }), + jasmine.anything() + ) + expect(startDeliveryApiPollingSpy).toHaveBeenCalledWith(jasmine.objectContaining({ version: 'build-version' })) + expect(browserWindow.$dd_entry).toBeDefined() + expect(browserWindow.$dd_return).toBeDefined() + expect(browserWindow.$dd_throw).toBeDefined() + expect(browserWindow.$dd_probes).toBeDefined() + }) + + it('should warn when the explicit init version mismatches build-plugin metadata', async () => { + browserWindow.__DD_LIVE_DEBUGGER_BUILD__ = { version: 'build-version' } + replaceMockableWithSpy(startDebuggerBatch).and.callFake(() => ({ + flushController: undefined as any, + add: () => undefined, + flush: () => undefined, + stop: () => undefined, + upsert: () => undefined, + })) + replaceMockableWithSpy(initDebuggerTransport) + const startDeliveryApiPollingSpy = replaceMockableWithSpy(startDeliveryApiPolling) + const warnSpy = spyOn(display, 'warn') + + datadogDebugger.init({ + clientToken: 'client-token', + service: 'service-name', + env: 'staging', + version: 'runtime-version', + }) + + await flushPromises() + + expect(warnSpy).toHaveBeenCalledWith(jasmine.stringMatching(/does not match the build-plugin version/)) + expect(startDeliveryApiPollingSpy).toHaveBeenCalledWith(jasmine.objectContaining({ version: 'runtime-version' })) + }) }) + +async function flushPromises() { + for (let i = 0; i < 10; i++) { + await Promise.resolve() + } +} diff --git a/packages/debugger/src/entries/main.ts b/packages/debugger/src/entries/main.ts index dac1e80de1..f5327d2835 100644 --- a/packages/debugger/src/entries/main.ts +++ b/packages/debugger/src/entries/main.ts @@ -6,18 +6,15 @@ * @see [Live Debugger Documentation](https://docs.datadoghq.com/tracing/live_debugger/) */ -import { defineGlobal, getGlobalObject, makePublicApi } from '@datadog/browser-core' +import { defineGlobal, display, getGlobalObject, makePublicApi, mockable } from '@datadog/browser-core' import type { PublicApi, Site } from '@datadog/browser-core' -import { onEntry, onReturn, onThrow, initDebuggerTransport } from '../domain/api' +import { initDebuggerTransport, onEntry, onReturn, onThrow } from '../domain/api' import { startDeliveryApiPolling } from '../domain/deliveryApi' import { getProbes } from '../domain/probes' import { startDebuggerBatch } from '../transport/startDebuggerBatch' -type DebuggerInstrumentationGlobal = typeof globalThis & { - $dd_entry?: typeof onEntry - $dd_return?: typeof onReturn - $dd_throw?: typeof onThrow - $dd_probes?: typeof getProbes +export interface DebuggerBuildMetadata { + version?: string } /** @@ -120,39 +117,68 @@ export interface DatadogDebugger extends PublicApi { * service: 'my-app', * site: 'datadoghq.com', * env: 'production' + * version: 'my-deployed-build-version', * }) * ``` */ init: (initConfiguration: DebuggerInitConfiguration) => void } +export interface BrowserWindow extends Window { + DD_DEBUGGER?: DatadogDebugger + __DD_LIVE_DEBUGGER_BUILD__?: DebuggerBuildMetadata + $dd_entry?: typeof onEntry + $dd_return?: typeof onReturn + $dd_throw?: typeof onThrow + $dd_probes?: typeof getProbes +} + +function resolveDebuggerVersion(initConfiguration: DebuggerInitConfiguration): string | undefined { + const buildVersion = getGlobalObject().__DD_LIVE_DEBUGGER_BUILD__?.version + + if ( + initConfiguration.version !== undefined && + buildVersion !== undefined && + initConfiguration.version !== buildVersion + ) { + display.warn( + `Debugger: init version "${initConfiguration.version}" does not match the build-plugin version "${buildVersion}". Using the init version.` + ) + } + + return initConfiguration.version ?? buildVersion +} + /** * Create the public API for the Live Debugger */ function makeDebuggerPublicApi(): DatadogDebugger { return makePublicApi({ init: (initConfiguration: DebuggerInitConfiguration) => { + const resolvedConfiguration = { + ...initConfiguration, + version: resolveDebuggerVersion(initConfiguration), + } + // Initialize debugger's own transport - const batch = startDebuggerBatch(initConfiguration) - initDebuggerTransport(initConfiguration, batch) + const batch = mockable(startDebuggerBatch)(resolvedConfiguration) + mockable(initDebuggerTransport)(resolvedConfiguration, batch) // Expose internal hooks on globalThis for instrumented code - if (typeof globalThis !== 'undefined') { - const debuggerGlobal = globalThis as DebuggerInstrumentationGlobal - debuggerGlobal.$dd_entry = onEntry - debuggerGlobal.$dd_return = onReturn - debuggerGlobal.$dd_throw = onThrow - debuggerGlobal.$dd_probes = getProbes - } - - startDeliveryApiPolling({ - service: initConfiguration.service, - clientToken: initConfiguration.clientToken, - site: initConfiguration.site, - proxy: initConfiguration.proxy, - env: initConfiguration.env, - version: initConfiguration.version, - pollInterval: initConfiguration.pollInterval, + const debuggerGlobal = getGlobalObject() + debuggerGlobal.$dd_entry = onEntry + debuggerGlobal.$dd_return = onReturn + debuggerGlobal.$dd_throw = onThrow + debuggerGlobal.$dd_probes = getProbes + + mockable(startDeliveryApiPolling)({ + service: resolvedConfiguration.service, + clientToken: resolvedConfiguration.clientToken, + site: resolvedConfiguration.site, + proxy: resolvedConfiguration.proxy, + env: resolvedConfiguration.env, + version: resolvedConfiguration.version, + pollInterval: resolvedConfiguration.pollInterval, }) }, }) @@ -167,8 +193,4 @@ function makeDebuggerPublicApi(): DatadogDebugger { */ export const datadogDebugger = makeDebuggerPublicApi() -export interface BrowserWindow extends Window { - DD_DEBUGGER?: DatadogDebugger -} - defineGlobal(getGlobalObject(), 'DD_DEBUGGER', datadogDebugger)