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
6 changes: 5 additions & 1 deletion packages/debugger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ datadogDebugger.init({
site: '<DATADOG_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].
Expand Down
87 changes: 87 additions & 0 deletions packages/debugger/src/entries/main.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,98 @@
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),
version: jasmine.any(String),
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()
}
}
80 changes: 51 additions & 29 deletions packages/debugger/src/entries/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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<BrowserWindow>().__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<DatadogDebugger>({
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<BrowserWindow>()
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,
})
},
})
Expand All @@ -167,8 +193,4 @@ function makeDebuggerPublicApi(): DatadogDebugger {
*/
export const datadogDebugger = makeDebuggerPublicApi()

export interface BrowserWindow extends Window {
DD_DEBUGGER?: DatadogDebugger
}

defineGlobal(getGlobalObject<BrowserWindow>(), 'DD_DEBUGGER', datadogDebugger)
Loading