Skip to content

Add Live Debugger package#4449

Open
watson wants to merge 4 commits intomainfrom
watson/DEBUG-5296/add-live-debugger
Open

Add Live Debugger package#4449
watson wants to merge 4 commits intomainfrom
watson/DEBUG-5296/add-live-debugger

Conversation

@watson
Copy link
Copy Markdown
Collaborator

@watson watson commented Apr 7, 2026

Motivation

Introduce the @datadog/browser-debugger package to enable Live Debugger in browser applications. This gives frontend developers the ability to add log probes to running applications, evaluate conditions, and inspect runtime state — all without redeploying or modifying source code.

Note: This package is intended for internal Datadog use only until validated in production. It follows the same convention as @datadog/browser-core, @datadog/browser-rum-core, and @datadog/browser-worker — published to npm with each release (to keep versions in sync), but excluded from generated documentation via typedoc.json.

Changes

New package: packages/debugger

A new @datadog/browser-debugger package that provides the full probe execution pipeline:

  • domain/api.ts — Core instrumentation hooks (onEntry, onReturn, onThrow) that execute probes when instrumented functions are called, including condition evaluation, snapshot capture, template message rendering, and rate limiting.
  • domain/activeEntries.ts — Tracks per-probe execution stacks for correlating entry/return/throw events, extracted to break the dependency cycle between api.ts and probes.ts.
  • domain/probes.ts — Probe lifecycle management (add, remove, clear) with per-probe and global snapshot rate limiting. Compiles probe conditions and template segments on registration.
  • domain/capture.ts — Deep value capture for arguments, locals, return values, and thrown errors with configurable reference depth, collection size limits, and string length limits.
  • domain/expression.ts — Expression compiler that parses JSON expression trees (comparisons, logical operators, member access, string operations, etc.) into executable functions.
  • domain/condition.ts — Probe condition evaluator that compiles and caches condition expressions.
  • domain/template.ts — Template segment compiler and evaluator for rendering dynamic probe messages with runtime context.
  • domain/stacktrace.ts — Stack trace capture and parsing from Error objects.
  • domain/deliveryApi.ts — Polling-based probe delivery client that fetches probe updates/deletions from the Delivery API using a cursor for incremental sync.
  • transport/startDebuggerBatch.ts — Transport layer that reuses @datadog/browser-core's batch/flush infrastructure to send debugger snapshots to the logs intake.
  • entries/main.ts — Public API surface (datadogDebugger.init()). Exposes $dd_entry/$dd_return/$dd_throw/$dd_probes hooks on globalThis for instrumented code. Defines the global DD_DEBUGGER object.

Changes to @datadog/browser-core

  • Added 'dd_debugger' as a valid source in configuration and transport types, mapped to 'browser' for the SDK source.
  • Exported computeTransportConfiguration and the Batch type so the debugger package can create its own transport.

E2E test framework and scenarios

  • test/e2e/scenario/debugger.scenario.ts — 7 E2E test scenarios covering: basic snapshot sending, argument/return value capture, exception capture on throw, template message evaluation with expression segments, condition evaluation (both met and not met), and RUM correlation.
  • E2E framework extensions — Added .withDebugger() builder method to createTest(), DebuggerIntakeRequest type and intakeRegistry.debuggerEvents for asserting on debugger events, debugger page setups for CDN/bundle/npm modes, and default debugger configuration.
  • test/apps/vanilla/app.ts — Added @datadog/browser-debugger import and DEBUGGER_INIT support so debugger E2E tests work in the npm setup.

Performance benchmarks

  • test/apps/instrumentation-overhead/ — Webpack test app for measuring instrumentation overhead with instrumented vs. uninstrumented function variants.
  • test/performance/scenarios/instrumentationOverhead.scenario.ts — Benchmark scenario that stress-tests 10M function calls to measure the overhead of debugger instrumentation hooks.
  • test/performance/createBenchmarkTest.ts — Extended with instrumented_no_probes and instrumented_with_probes scenario configurations and a dedicated injectDebugger function.

Tooling

  • Updated scripts/build/build-test-apps.ts to include the new test app and to use resolution paths when installing peer dependencies (needed for unpublished packages like @datadog/browser-debugger that only exist locally as .tgz files).
  • Updated scripts/dev-server/lib/server.ts to serve the debugger bundle.
  • Added debugger entry point to ESLint side-effects allowlist.

Test instructions

  1. Unit tests: yarn test:unit --spec "packages/debugger/src/**/*.spec.ts"
  2. E2E tests: yarn test:e2e:init && yarn test:e2e -g "debugger"
  3. Performance testing: yarn build:apps && yarn test:performance

Checklist

  • Tested locally
  • Tested on staging — N/A, this is a new pre-production package not yet deployed to any environment
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated documentation and/or relevant AGENTS.md file

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 7, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

Copy link
Copy Markdown
Collaborator Author

watson commented Apr 7, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@datadog-datadog-prod-us1
Copy link
Copy Markdown

datadog-datadog-prod-us1 Bot commented Apr 7, 2026

Tests

🎉 All green!

❄️ No new flaky tests detected
🧪 All tests passed

🎯 Code Coverage (details)
Patch Coverage: 71.94%
Overall Coverage: 76.69% (-0.32%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 8130067 | Docs | Datadog PR Page | Give us feedback!

@watson watson force-pushed the watson/DEBUG-5296/add-live-debugger branch from 4df303f to b539c14 Compare April 7, 2026 13:30
@cit-pr-commenter-54b7da
Copy link
Copy Markdown

cit-pr-commenter-54b7da Bot commented Apr 7, 2026

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum N/A 179.42 KiB N/A N/A N/A
Rum Profiler N/A 6.16 KiB N/A N/A N/A
Rum Recorder N/A 27.03 KiB N/A N/A N/A
Logs N/A 56.80 KiB N/A N/A N/A
Rum Slim N/A 135.26 KiB N/A N/A N/A
Worker N/A 23.63 KiB N/A N/A N/A
🚀 CPU Performance
Action Name Base CPU Time (ms) Local CPU Time (ms) 𝚫%
addglobalcontext N/A 0.0049 N/A
addaction N/A 0.0136 N/A
adderror N/A 0.013 N/A
addtiming N/A 0.0028 N/A
startview N/A 0.0124 N/A
startstopsessionreplayrecording N/A 0.0007 N/A
logmessage N/A 0.0145 N/A
🧠 Memory Performance
Action Name Base Memory Consumption Local Memory Consumption 𝚫
addglobalcontext N/A 31.53 KiB N/A
addaction N/A 101.84 KiB N/A
addtiming N/A 32.46 KiB N/A
adderror N/A 104.34 KiB N/A
startstopsessionreplayrecording N/A 32.01 KiB N/A
startview N/A 489.22 KiB N/A
logmessage N/A 99.21 KiB N/A

🔗 RealWorld

@watson
Copy link
Copy Markdown
Collaborator Author

watson commented Apr 7, 2026

I have read the CLA Document and I hereby sign the CLA

@watson watson force-pushed the watson/DEBUG-5296/add-live-debugger branch 6 times, most recently from 3042bfe to 98490f7 Compare April 7, 2026 15:45
@watson watson marked this pull request as ready for review April 7, 2026 15:56
@watson watson requested a review from a team as a code owner April 7, 2026 15:56
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98490f775f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/debugger/src/domain/capture.ts Outdated
Comment thread packages/debugger/src/domain/api.ts Outdated
@watson watson force-pushed the watson/DEBUG-5296/add-live-debugger branch from bf9a162 to b9da4f3 Compare April 21, 2026 07:36
@watson watson changed the base branch from main to graphite-base/4449 April 21, 2026 08:17
@watson watson force-pushed the watson/DEBUG-5296/add-live-debugger branch from b9da4f3 to 0040ee8 Compare April 21, 2026 08:17
@watson watson changed the base branch from graphite-base/4449 to watson/DEBUG-5296/fix-lint April 21, 2026 08:17
Base automatically changed from watson/DEBUG-5296/fix-lint to main April 21, 2026 09:11
@watson watson force-pushed the watson/DEBUG-5296/add-live-debugger branch from 0040ee8 to 653aee9 Compare April 21, 2026 09:13
Comment thread packages/debugger/src/domain/api.ts Outdated
Comment thread packages/debugger/src/domain/stacktrace.ts
@watson watson requested a review from mormubis April 22, 2026 12:25
@watson watson force-pushed the watson/DEBUG-5296/add-live-debugger branch 2 times, most recently from a90f65e to 43903ba Compare April 22, 2026 12:47
watson added 3 commits April 22, 2026 15:48
Introduce the browser debugger SDK and probe execution pipeline so
browser code can evaluate conditions, capture snapshots, and render
probe messages at runtime. Add Delivery API polling plus sandbox and
performance tooling to support probe delivery and testing.
@watson watson force-pushed the watson/DEBUG-5296/add-live-debugger branch from 43903ba to 7d1efe7 Compare April 22, 2026 14:24
Copy link
Copy Markdown
Contributor

@mormubis mormubis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd get another approval before merging. We usually look for at least two approvals.

Comment on lines +340 to +342
function toSdkSource(source: TransportConfiguration['source']): SdkSource {
return source === 'dd_debugger' ? 'browser' : source
}
Copy link
Copy Markdown
Collaborator

@thomas-lebeau thomas-lebeau Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ question: ‏why setting the source to dd_debugger to set it back to browser later?

I think we should keep dd_debugger (or just debugger for consistency) and your team should maintain their own monitors, dashboards, ...

Comment on lines 7 to +9
DD_LOGS?: LogsGlobal
DD_RUM?: RumGlobal
DD_DEBUGGER?: DebuggerPublicApi
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: the types LogsGlobal and RumGlobal are deprecated in favor of DatadogLogs and DatadogRum. what about using DatadogDebugger for consistency?

})
```

If [Datadog RUM][3] is also initialized on the page, debugger snapshots automatically include RUM context (session, view, user action) without any additional configuration.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ question: ‏How does this works? I must have missed something, I didn't see how this sdk collects sessions

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the text here should probably be slightly modified to read better, but the gist is that we tie the probe result to the current trace/span: https://github.com/DataDog/browser-sdk/pull/4449/files#diff-88cda10a3adeb04ab7401bf422650f85908ff2261c31b4411959b7c80be817d4R330-R341

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 these fields (traceId and spanId) don't exist in RumInternalContext, is it really working?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha interesting. I was actually looking for ways I could test this in an integration test without mocking it, but couldn't find a good way to do it, so I settled for a unit test after my AI had added it. Turns out my gut feeling was correct that this needed better testing

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use session.id to correlate RUM event with logs, profiles and session replay.

Use the existing debugger service configuration when polling for probes so
browser callers keep sending a valid delivery identity after the
applicationId rollback. This also removes the stale applicationId init
examples and test fixtures from the debugger package.

Made-with: Cursor
Comment on lines +331 to +340
const rumContext = globalObj.DD_RUM?.getInternalContext?.()
const traceId = getStringContextValue(rumContext, 'trace_id')
const spanId = getStringContextValue(rumContext, 'span_id')

return traceId && spanId
? {
trace_id: traceId,
span_id: spanId,
}
: undefined
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: trace_id and span_id don't exist in getInternalContext

Comment on lines +20 to +23
const hasReplaceAll = typeof (String.prototype as any).replaceAll === 'function'
const replaceDots = hasReplaceAll
? (str: string) => (str as string & { replaceAll: (s: string, r: string) => string }).replaceAll('.', '_')
: (str: string) => str.replace(/\./g, '_')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const hasReplaceAll = typeof (String.prototype as any).replaceAll === 'function'
const replaceDots = hasReplaceAll
? (str: string) => (str as string & { replaceAll: (s: string, r: string) => string }).replaceAll('.', '_')
: (str: string) => str.replace(/\./g, '_')
const replaceDots = (str: string) => str.replace(/\./g, '_')

let fn = functionCache.get(cacheKey)
if (!fn) {
// eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval
fn = new Function(...contextKeys, fnBody) as (...args: any[]) => boolean
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: eval is evil, we shouldn't rely on eval if possible. Best practice is often to use CSP that block such functionality.

Is there something specific in the language that requires "inline" JavaScript code? Else we can probably parse its AST and run it using JS without actually generating JS code.


declare const __BUILD_ENV__SDK_VERSION__: string

const DELIVERY_API_PATH = '/api/ui/debugger/probe-delivery'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: what is this route? Is this supposed to live on the customer website?

Comment on lines +2 to +3
"name": "@datadog/browser-debugger",
"version": "6.32.0",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion:: mark this as "private": true so we don't publish it until it's ready for customer consumpiton

;(window as any).USE_INSTRUMENTED = true
})

// Load debugger SDK (using local build for now)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: why not using the actual SDK?

createTest('send debugger snapshot when instrumented function is called')
.withDebugger()
.run(async ({ intakeRegistry, flushEvents, page, browserName, servers }) => {
test.skip(browserName !== 'chromium', 'Debugger tests require Chromium')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question why debugger tests require Chromium?

}
}

async function readDebuggerIntakeRequest(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: this is a copy of the readRumOrLogsIntakeRequest function. You can just re-use it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants