docs(rum): add Electron SDK integration docs (zh + en) - #222
Draft
Fiona2016 wants to merge 7 commits into
Draft
Conversation
Electron is the first RUM platform that requires a two-process
integration: `@flashcatcloud/electron-sdk` in the main process and
`@flashcatcloud/browser-rum` in renderers, bridged over IPC through the
`DatadogEventBridge` object that dd-trace injects via preload. Integrating
only one side is the failure mode these pages are written to prevent.
Adds four pages per language under `rum/sdk/electron/`, mirroring the
harmony/flutter page set, and registers the group in docs.json for both
languages:
- sdk-integration: install, the `instrument` entry point that must precede
`require('electron')`, bundler plugins (vite / webpack / esbuild), renderer
setup, and the `allowedWebViewHosts` allowlist that gates the bridge
- advanced-config: full init options, batching, proxy, manual reporting,
operation monitoring, source map upload
- compatible: support scope and v1 limits
- data-collection: per-process event types, fields, session rules, upload
behavior
Content is derived from the SDK source rather than upstream Datadog docs,
since the fork changed intake URL rules, the site allowlist, and dropped the
spans track.
v1 limits documented explicitly: no native crash symbolication (crashes are
stored and shown as raw addresses), no Session Replay, no APM/distributed
tracing, and no source map resolution for main-process stacks. The
`file://` install-path instability that breaks source map matching for
`loadFile()` builds is called out with the custom-protocol workaround.
Doc paths match the console's `utils/docs.ts` mapping for the electron
platform. Package versions are left unpinned, per npm platform convention.
Verified with `mint broken-links`.
Two factual errors from the first commit, both found by W1's staging smoke
test and confirmed against the source.
1. The bridge is not opt-in. The earlier text read `validateAllowedWebViewHosts()`
returning `[]` for `undefined` as "the bridge is disabled by default", but
that is the SDK-side config default, not the allowlist the browser SDK
actually sees. dd-trace's preload builds it as:
const allowedHosts = [...new Set([location.hostname, ...configuredHosts])]
The window's own hostname is always included, so a window's own page always
self-matches and the bridge works with no configuration. `file://` included:
`location.hostname` is `""` there, the allowlist becomes `[""]`, and
`canUseEventBridge("")` still matches. `allowedWebViewHosts` is for
additional third-party hosts in `<webview>` / `BrowserView`, not a switch.
2. Bridged renderer events keep `source: browser`. `Assembly.assembleRendererRumEvent()`
overrides only `session.id` and `application.id` and adds
`container.{source, view.id}` — the renderer's own `source` is preserved.
Main-process events are `source: electron` with
`view.url: electron://main-process`. Filtering on `source:electron` alone
therefore returns main-process events only; the correct filter is
`source:electron OR container.source:electron`.
Changes:
- Replace the "enable the bridge" sections with "the bridge needs no
configuration" plus a "what a broken bridge looks like" section, since the
real failure mode is a missing preload injection (main process not
integrated, or bundled without the plugin), diagnosable via a missing
`container.source`
- Add a source/container.source/view.url table to both the integration and
data-collection pages, and correct the verification steps to use the OR filter
- Drop `allowedWebViewHosts` from the basic init examples; it is not needed
- Decouple the `app://` custom-protocol recommendation from the bridge. It now
stands only on source map path stability, with an explicit note that the
bridge works fine under `file://`
- Mark the `file://` install-path limitation as affecting source map
resolution only, not collection
- Align the proxy section with the README: `site` stays required but is unused
for URL building once `proxy` is set, and name the self-hosted use case
Verified with `mint broken-links`.
Fiona settled the open question: normalize stack paths in `beforeSend`. This is not a new proposal — it is the workaround already given to customers — so the section states it directly rather than weighing alternatives. Restructures the source map section around the matching rule, in her framing: the uploaded minified prefix and the path in the stack must correspond. That resolves into two steps, and the whole section is organized around them: 1. Run the CLI where the source maps are and declare a prefix with `--minified-path-prefix` 2. Rewrite the stack path in the renderer's `beforeSend` to align with it Both halves are shown as copy-pasteable code, and the prefix is lifted into a `MINIFIED_PATH_PREFIX` constant so the two places that must agree are visually obvious, with a warning naming them as the only coupling point. Adds a "why the second step is needed" table: stack frames carry the runtime install path, which is unknowable at build time — the macOS install location is the user's choice, Windows embeds the user name, and Linux AppImage mounts somewhere new on every launch. Uploading an install path as the prefix would match exactly one machine. The regex was checked against the real stack shapes measured during the W5 verification (`at r @ file:///…/dist/renderer.js:1:21`), for all three platforms; `[^\s()]*?` rather than `\S*?` so a V8-format frame keeps its enclosing paren. The `app://` custom protocol drops to an optional tip under "when normalization is unnecessary", alongside dev-server and remote-page setups whose paths are already stable. It no longer leads, since `beforeSend` solves the same problem without asking anyone to restructure page loading. Main-process stacks are stated as unsupported for v1 across all six affected pages: they are raw V8 format and the backend parser extracts zero frames. Verified with `mint broken-links`.
v1 drops the hardcoded site allowlist: `site` becomes optional, defaults to `browser.flashcat.cloud`, and is only checked for non-emptiness. The docs no longer tell self-hosted users to pass a SaaS host as a placeholder and route everything through `proxy`. Self-hosted now splits into two cases, which is the part worth getting right: - HTTPS intake: just set `site` to your own domain, no proxy involved - Plain-HTTP intake: `site` cannot help, because the upload URL is built from the template `https://<site>/api/v2/rum` and the scheme is hardcoded. This needs `proxy` That second case is easy to miss once the allowlist is gone — removing it looks like it unblocks arbitrary endpoints, and it does not. It is called out in all six affected pages: as a note under the `site` parameter, as its own "when a proxy is required" subsection, and as a row in the limits table. Also aligns two details with decisions from the release work: - The console special-cases the synthetic main-process view and hides the performance section, so LCP/FCP no longer render as zero. Noted where the docs explain that the main process has no Web Vitals - Section renames moved four anchors; all inbound links updated Verified with `mint broken-links`.
…e, crash resilience Review of PR#1..#5 on `origin/publish`, which landed after these pages were written. Three capability changes and one stale claim. Main-process stacks are now symbolicatable. `ErrorCollection.formatError()` runs `toStackTraceString(computeStackTrace(error))`, so main-process stacks come out in the backend's `at <fn> @ <url>:<line>:<col>` frame format instead of V8's native shape, and frame URLs are the absolute paths of the bundled main-process code — the same key source map upload uses. The docs said the opposite in six places; all corrected, and users are now told to upload the main-process bundle's source maps too. Two caveats kept, because they are still true and easy to over-read: - Native crash stacks remain address-based and unsymbolicated - `beforeSend` is a renderer-only hook, so main-process path normalization depends on the install path being predictable. Called out in advanced-config and in the limits table rather than left implicit `ProcessGoneCollection` was entirely undocumented. Added a data-collection section and a compatibility row covering `render-process-gone` / `child-process-gone`: the full `meta` shape, `is_crash: false` and why (the host app is alive, and the backend escalates every `is_crash` to a critical alert), the absence of a stack, `container.view.id` attribution for renderers, and the deduplication rule — dump-producing reasons (`crashed`, `oom`) are left to crash collection, `clean-exit` is silent, everything else including `killed` is reported. Crash collection degradation: dumps with no exception stream are now reported with threads and binary images rather than dropped, and dumps are deleted whether or not parsing succeeded. Both documented, with the field table noting which values go missing. dd-trace's instrumentation telemetry is off by default. Documented because it is a network-behavior fact worth knowing (`127.0.0.1:8126`, and a direct Datadog fallback when `DD_API_KEY` is set), including that it is controlled by environment variable — `telemetry: false` on `tracer.init()` is silently ignored on dd-trace 5.x — and that an explicit host setting is preserved. Re-verified the `site` changes from the previous round against `config.ts`: optional, `DEFAULT_SITE`, non-empty validation only. Already accurate. The source map section keeps "why the second step is needed" as its own subsection, so the `beforeSend` flow can be demoted to an alternative once SDK-side path normalization lands without restructuring the page. zh/en section counts match on all four pages. Verified with `mint broken-links`.
…c correction Two capabilities landed on the SDK's publish branch that the Electron docs did not cover. `correctPrewarmedViewTimings` (boolean, default true): a new section explains why a window created with `show: false` and navigated ahead of time reports an FCP/LCP inflated by the whole pre-warm interval, the activationStart formula the correction applies, and a table of exactly when it fires — including the two cases that surprise people: metrics are discarded for a window that was never shown, and nothing happens for WebContentsView / <webview>. `normalizeStackPaths` (boolean, default true) and `normalizeStackPath` (callback): stacks from both processes now anchor on the application root as `app:///<relative path>`, so the source map section is restructured around "upload with the matching prefix" instead of the previous two-step recipe. Added the table of which path shapes are and are not rewritten, and made the prefix derivation explicit (app:///dist/x.js -> /dist), including the separate uploads main-process and renderer bundles need. The manual `beforeSend` rewrite is demoted from required step to an optional advanced path, with the cases that still need it and a warning that a hand-written regex typically covers a single platform and fails silently elsewhere. It is kept working: the built-in normalization is a strict no-op on already-relative paths. Also removed the two limits these features close (file:// source map matching, main-process path normalization) and replaced them with the honest ones that remain (code outside the app root, BrowserWindow-only visibility tracking). Verified with `mint broken-links` plus an anchor cross-check; zh/en heading structure is identical line for line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
End-to-end validation surfaced three integration mistakes that produce no error at all — the app keeps running, the SDK stays quiet, and only the data is missing. Document each with its symptom, why it stays silent, and the fix: - CSP silently kills Session Replay. Recording creates a blob Worker in the renderer and posts segments straight to the intake, so a common `script-src 'self'` policy blocks the whole pipeline: `session.has_replay` stays 0 with no segments, indistinguishable from never enabling recording. Requires `worker-src blob:` plus the intake origin in `connect-src`. - Source map `--release-version` must match the renderer's `version`. Renderer events take `version` from `flashcatRum.init()` only; the main-process `init()` value never reaches them. A mismatch resolves nothing and reports nothing. - Replay segments are lost during a network outage. They bypass the main-process disk-backed retry, and the browser transport only queues a retry when `navigator.onLine === false`, so an unreachable intake on a live network drops them outright. The first segment after recovery carries no full snapshot, leaving a garbled stretch in playback. Session Replay is documented as supported (via `sessionReplayDirectUpload`) in place of the previous "not supported" entries, since both replay pitfalls depend on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds the Electron platform to the RUM SDK docs.
zh/rum/sdk/anden/rum/sdk/previously coveredweb / android / ios / flutter / harmony / wechat-miniprogram— there was noelectron.Four pages per language under
rum/sdk/electron/, mirroring the harmony/flutter page set, registered indocs.jsonfor both languages:sdk-integrationinstrumententry point, bundler plugins, renderer setup, bridge behavioradvanced-configcompatibledata-collectionContent is derived from the SDK source on
origin/publishrather than upstream Datadog docs — the fork changed intake URL rules, switched to an NDJSON body, and dropped the spans track. Package versions are left unpinned, per the convention of the other npm-based platform docs. Console doc links inutils/docs.tsmap torum/sdk/electron/sdk-integrationandrum/sdk/electron/data-collection; both paths exist here.Why this platform needed more than a copy-paste
Electron is the first RUM platform with a genuinely two-process integration:
@flashcatcloud/electron-sdk, whose@flashcatcloud/electron-sdk/instrumententry point must be imported beforerequire('electron')— otherwise dd-trace cannot hookBrowserWindowand preload injection silently fails.@flashcatcloud/browser-rumand hand events to the main process over IPC through theDatadogEventBridgeobject the preload exposes. This needs no configuration — the preload always allows the window's own host.sdk-integrationanddata-collectionare written to prevent it.The two
sourcevaluesThe subtlest thing here.
Assembly.assembleRendererRumEvent()overrides onlysession.idandapplication.idon bridged renderer events and addscontainer— the renderer's ownsourceis preserved:sourcecontainer.sourceview.urlelectronelectron://main-processbrowserelectronFiltering on
source:electronalone returns main-process events only. The docs consistently usesource:electron OR container.source:electron.container.sourcealso doubles as the diagnostic for a broken bridge — if renderer events lack it, the preload was never injected.Error and crash coverage
Three distinct paths, documented separately because they behave differently:
uncaughtException,unhandledRejection,addError)@frame format, source-map resolvablecrashReporterminidumps)is_crash: true, address-based stack, threads, binary images. Parsed on next startuprender-process-gone/child-process-gone)is_crash: false, no stack,metawith process/reason/exit code/urlis_crash: falseon terminations is deliberate — the host app is alive, and the backend escalates everyis_crashto a critical alert. Dedup between the last two is documented: dump-producing reasons (crashed,oom) are left to crash collection,clean-exitis silent, everything else includingkilledis reported.Source maps: the SDK now normalizes stack paths
The matching rule is unchanged — the uploaded prefix and the path in the stack must correspond, and the backend matches on the URL path portion only. What changed is who does the normalizing.
normalizeStackPaths(defaulttrue) rewrites every frame below the application root toapp:///<path relative to the app root>, for main-process and renderer stacks alike. Frames carry the runtime install path — chosen by the user on macOS, embedding the user name on Windows, remounted on every launch for a Linux AppImage — so previously the only way to get a stable path was to rewrite it yourself.The section is restructured accordingly:
app:///dist/x.js->/dist)node:internal/...,http(s),app.asar.unpacked, already-relative paths, andview.urlare all left alone/dist), notapp:///dist: the CLI requires either a URL with a host or a leading/, andapp:///disthas an empty hostnormalizeStackPath(callback, optional) is documented for build layouts a single application root cannot express — output under<app root>/public/distuploaded as/dist, a linked internal package. It runs before the built-in step, applies to both processes, and a callback that throws falls back rather than taking reporting down.beforeSenddemoted, not deletedThe manual two-step
beforeSendrewrite moves to "Advanced: rewriting paths yourself (optional)", with the three cases that still justify it: mapping that depends on other event fields, renderer-only changes, and existing implementations not ready to migrate.The demotion is safe by construction and the docs say so: the built-in normalization is a strict no-op on paths that are already relative, so an existing
beforeSendkeeps working and its output is never rewritten twice.The section carries one warning worth the space — a hand-written regex typically covers one platform and fails silently on the others. The example given matches only the Windows packaged shape and no-ops on macOS, AppImage, and dev builds, with no error anywhere; the stacks just never resolve. Readers who need custom mapping are pointed at
normalizeStackPath, which receives an already-unified absolute path.Paint metrics of pre-warmed windows
correctPrewarmedViewTimings(defaulttrue) is new and needed a section of its own, because the symptom is confusing rather than obviously broken.Pre-creating
new BrowserWindow({ show: false })and navigating it ahead of time is standard Electron practice, andpaintWhenInitiallyHiddendefaults totrue, so the page paints while hidden and never firesvisibilitychange. A first render deferred untilshow()therefore reports paint metrics inflated by the whole pre-warm interval — one measured case had a perfectly normal FCP of 404ms next to an LCP of 8084ms, because LCP keeps updating until a first interaction that cannot happen while hidden.The docs give the
activationStartformula (the same one the Paint Timing spec applies to prerendered pages) and a table of exactly when the correction fires, including the two non-obvious outcomes:WebContentsView/<webview>are left untouched, since "not observed" must not be read as "never visible"Both are also listed under limits, so someone hunting for missing paint metrics finds the reason.
Self-hosted: two cases
siteis optional, defaults tobrowser.flashcat.cloud, and is only checked for non-emptiness.siteto your own domain, no proxysitecannot help; the upload URL is built fromhttps://<site>/api/v2/rumwith the scheme hardcoded, so this requiresproxyDropping the allowlist looks like it unblocks arbitrary endpoints, and it does not. Stated in all six affected places.
v1 limits documented explicitly
defaultPrivacyLevelforwarded but inert)Two limits were removed this round because the features above close them: source map matching for
file://pages, and main-process path normalization. They are replaced by the ones that genuinely remain — code outside the application root is not normalized, and visibility tracking coversBrowserWindowonly.Also documents that dd-trace's own instrumentation telemetry is disabled by default (
127.0.0.1:8126, with a direct-to-Datadog fallback whenDD_API_KEYis set), and that it is env-var controlled sincetelemetry: falseis ignored on dd-trace 5.x.Review note
The history contains two correction passes against evolving SDK code, so reviewing the squashed diff is more useful than commit by commit. Commit 2 fixed two errors caught by the staging smoke test (the bridge is not opt-in; renderer events keep
source: browser). Commit 4 re-synced againstpublishafter PR#1..#5 — most notably main-process stacks became symbolicatable, reversing an earlier claim. Commit 5 covers PR#6 and PR#7 (paint metric correction, stack path normalization) and is what demotes thebeforeSendrecipe.Verified with
mint broken-linksplus a cross-check that every anchor used by the Electron pages resolves to a real heading. The zh and enadvanced-configpages have identical heading structure, line for line.Two changes are known to be coming and are deliberately not reflected yet: FCP/LCP correction (
correctPrewarmedViewTimings) and SDK-side stack path normalization. The source map section keeps "why the second step is needed" as its own subsection so thebeforeSendflow can be demoted without restructuring the page.Verification
mint broken-linkspasses; zh/en section counts match on all four pages.🤖 Generated with Claude Code