Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
82c25ab
Integrate dd-trace for HTTP resource tracing and automatic preload in…
cdn34dd Apr 12, 2026
c692ba6
Update playground app to add more testing options
cdn34dd Apr 13, 2026
010b631
Send all dd-trace spans to span intake with electron context enrichement
cdn34dd Apr 22, 2026
be03846
Remove `tracing` configuration & have it enabled by default
cdn34dd Apr 22, 2026
f9fbf0a
Remove SDK's preload registration in favor of dd-trace's approach
cdn34dd Apr 23, 2026
09b829e
Rename `init` entry point to `instrument`
cdn34dd Apr 23, 2026
87e3537
Update e2e apps to use instrument entry point for preload injection
cdn34dd Apr 23, 2026
34ec724
Move dd-trace from devDependencies to dependencies
cdn34dd Apr 30, 2026
81d91f4
Add Vite bundler plugin for dd-trace integration
cdn34dd Apr 30, 2026
de4a136
Add Webpack bundler plugin for dd-trace integration
cdn34dd Apr 30, 2026
28a1837
Add build config and package exports for bundler plugins
cdn34dd Apr 30, 2026
5d56960
Update e2e integration apps to use bundler plugins
cdn34dd Apr 30, 2026
6bb0117
Update documentation for bundler plugins
cdn34dd Apr 30, 2026
d347711
Remove SDK preload scripts in favor of dd-trace's preload injection
cdn34dd Apr 30, 2026
26da0c8
Fix ResourceConverter: precise SDK filtering and consistent service name
cdn34dd Apr 30, 2026
393ef1e
Add unit tests for ResourceConverter
cdn34dd Apr 30, 2026
2e3262b
Fix ESM compatibility for require('dd-trace') calls
cdn34dd May 20, 2026
f2ce07a
Add esbuild plugin
cdn34dd May 20, 2026
2ae8e60
Switch dd-trace from local tgz to published 5.103.0
cdn34dd May 20, 2026
8fa4262
Add externalization and node_modules copy to DatadogWebpackPlugin
cdn34dd May 20, 2026
a85724b
Merge branch 'main' into carlosnogueira/RUM-15104/integrate-dd-trace
bcaudan May 20, 2026
5766b26
Fix ESM preload injection in esbuild plugin
cdn34dd May 20, 2026
2ad720f
👌 rework README
bcaudan May 20, 2026
6c9beed
👌 rework architecture
bcaudan May 20, 2026
7c1effe
Make bundler plugins self-contained: no manual instrument import needed
cdn34dd May 21, 2026
03a5a5c
✅ add e2e tests for main-process resource collection
bcaudan May 20, 2026
35e20f2
✨ align proxy URL with ddforward pattern
bcaudan May 20, 2026
d34a414
✅ ensure traces are sent for network calls
bcaudan May 21, 2026
9486b5f
✅ ensure traces are sent for ipc calls
bcaudan May 21, 2026
437a5f6
✅ add integration scenario for main-process fetch trace
bcaudan May 21, 2026
989ba5d
Add ESM output support to vite plugin
cdn34dd May 21, 2026
bb1f449
Add electron-vite ESM integration test app
cdn34dd May 21, 2026
52195f0
Add mainFetch IPC to electron-vite-esm app and increase intake timeout
cdn34dd May 21, 2026
2b0073c
✨ add esbuild integration apps for CJS and ESM
bcaudan May 21, 2026
d0e4bbf
PR review fixes
cdn34dd May 21, 2026
2ded3ee
Rename ResourceConverter to SpanProcessor and extract methods
cdn34dd May 21, 2026
2065973
👌 filtering span corresponding to the intake
bcaudan May 21, 2026
6fb7ace
♻️ use process.getSystemVersion for macOS user agent
bcaudan May 21, 2026
7ecfce1
👌 do not consider dd-trace as third party
bcaudan May 21, 2026
3249740
👌 avoid type duplication
bcaudan May 21, 2026
539d819
👌 Discard span when no session or view active
bcaudan May 22, 2026
e61fcd4
👌 fix + enrich dd-trace links
bcaudan May 22, 2026
4076197
👌 rework typings
bcaudan May 22, 2026
f4b6afb
🔥 remove dd-trace.tgz
bcaudan May 22, 2026
620088f
👌 add todo for dd-trace internal access
bcaudan May 22, 2026
2f0d983
🐛 date format issue broke span ingestion
bcaudan May 22, 2026
107a457
Update architecture documentation for dd-trace integration
cdn34dd May 25, 2026
8b4d100
Patch dd-trace span parenting for HTTP requests inside ipcMain.handle…
cdn34dd May 26, 2026
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
1 change: 1 addition & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ npm-dev,@electron-forge/plugin-webpack,MIT,Copyright (c) 2019 The Electron Forge
npm-dev,@eslint/js,MIT,Copyright OpenJS Foundation and other contributors
npm-dev,@playwright/test,Apache-2.0,Copyright Microsoft Corporation
npm-dev,@rollup/plugin-commonjs,MIT,Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors)
npm-dev,@rollup/plugin-json,MIT,Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors)
npm-dev,@rollup/plugin-node-resolve,MIT,Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors)
npm-dev,@rollup/plugin-replace,MIT,Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors)
npm-dev,@rollup/plugin-typescript,MIT,Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors)
Expand Down
114 changes: 90 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Real User Monitoring for Electron applications.

### Prerequisites

- Node.js 25+
- Electron 39+

### Install
Expand All @@ -19,9 +18,53 @@ yarn add @datadog/electron-sdk
npm install @datadog/electron-sdk
```

### Initialize
### Setup

The Electron SDK uses dd-trace under the hood to monitor the main process and relies on the browser SDK to monitor renderer processes.

```mermaid
graph TB
subgraph Electron Application
subgraph Renderer Process
BP[Browser SDK]
end

subgraph Main Process
DDT[dd-trace]
SDK[Electron SDK]
end
end

DD[(Datadog)]
BP --> SDK
DDT --> SDK
SDK --> DD

%% Styling
classDef sdk fill:#fce8e6,stroke:#d93025
classDef trace fill:#e6f4ea,stroke:#137333
classDef browser fill:#fef7e0,stroke:#e37400
classDef ext fill:#f3e8fd,stroke:#7627bb

class BP browser
class DDT trace
class SDK sdk
class DD ext
```

#### Main process setup

Import the instrumentation entry point **before** `electron` in your main process:

```ts
// src/main.ts
import '@datadog/electron-sdk/instrument';
import { app, BrowserWindow } from 'electron';
```

This initializes dd-trace and automatically instruments the needed APIs.

Call `init` in your **main process** before creating any browser windows:
Then initialize the Electron SDK by calling `init` before creating any browser windows:

```ts
import { init } from '@datadog/electron-sdk';
Expand All @@ -34,12 +77,55 @@ await init({
});
```

#### Renderer process setup

In order to monitor the renderer process, you must [set up the Browser SDK](https://docs.datadoghq.com/real_user_monitoring/application_monitoring/browser/setup/) in pages loaded by the renderer.

#### Bundler plugins

dd-trace instruments `require('electron')` at runtime, which requires correct module loading order. The SDK provides bundler plugins to ensure this works in all environments:

**Vite** (including Electron Forge with Vite and electron-vite):

```ts
// vite config
import { datadogVitePlugin } from '@datadog/electron-sdk/vite-plugin';

export default defineConfig({
plugins: [datadogVitePlugin()],
});
```

**Webpack** (including Electron Forge with Webpack):

```ts
// webpack config
const { DatadogWebpackPlugin } = require('@datadog/electron-sdk/webpack-plugin');

module.exports = {
plugins: [new DatadogWebpackPlugin()],
};
```

**ESBuild**

```ts
// esbuild config
import { datadogEsbuildPlugin } from '@datadog/electron-sdk/esbuild-plugin';

await esbuild.build({
plugins: [datadogEsbuildPlugin()],
});
```

## Available Features

- **Sessions** — Session-based event grouping
- **RUM Views** — One view per main process instance
- **RUM Errors** — Capture Node errors and crashes in main process
- **Renderer Bridge** — Capture RUM events from renderer processes via the browser SDK
- **RUM Resources** — Capture RUM resources from main process network calls
- **Traces** — Capture traces for network calls, command execution, IPC messages on main process
- **Renderer Events** — Capture RUM events from renderer processes via the browser SDK
- **Operation Monitoring** _(experimental)_ — Track start / succeed / fail steps of critical user-facing workflows

### Operation Monitoring _(experimental)_
Expand Down Expand Up @@ -69,26 +155,6 @@ failOperation('upload', 'abandoned', { operationKey: 'cover_photo' });

The renderer process keeps using `@datadog/browser-rum` directly (with the `feature_operation_vital` experimental flag enabled on its init). API signatures match exactly, so you can start an operation in one process and complete it in the other — the backend correlates steps by `name` + `operationKey`.

### Renderer Process Support

In order to monitor the renderer process, the [Browser SDK](https://docs.datadoghq.com/real_user_monitoring/application_monitoring/browser/setup/) must be setup in pages loaded by the renderer.
The Electron SDK exposes a `DatadogEventBridge` to every renderer process via a preload script. When present, the Browser SDK detects the bridge and routes events through IPC to the Electron SDK instead of sending them directly to Datadog servers.

#### Unbundled apps

For apps that don't bundle the main process, the SDK automatically registers the preload script. No additional setup is needed.

#### Bundled apps (Vite, Webpack)

If your main process is bundled (e.g. Electron Forge with the Vite or Webpack plugin), automatic preload injection won't work. Instead, add the following import to your preload script:

```ts
// src/preload.ts
import '@datadog/electron-sdk/preload';
```

This ensures the bridge code is bundled into your app's `preload.js` and included in the final package.

## API

### `init(config: InitConfiguration): Promise<boolean>`
Expand Down
162 changes: 156 additions & 6 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,59 @@

Describes general patterns with examples — detailed component documentation lives as JSDoc on the classes themselves (e.g., `SessionManager`, `ViewCollection`).

## Overview
## Monitoring Architecture

```mermaid
graph TB
subgraph Electron App
subgraph Renderer Process
BP[Browser SDK]
end

subgraph Main Process
DDT[dd-trace]
SDK[Electron SDK]
end
end

DD[(Datadog)]

%% Browser SDK → Electron SDK via bridge
BP -->|"RUM events<br/>(IPC bridge)"| SDK

%% dd-trace → Electron SDK via diagnostic channel
DDT -->|"HTTP spans<br/>IPC spans<br/>(diagnostics_channel)"| SDK

%% Electron SDK internal sources
SDK -->|"span → resource conversion<br/>RUM data collection<br/>RUM APIs<br/>session + main process view"| SDK

%% Electron SDK → Datadog
SDK -->|"enriched events<br/>(HTTP)"| DD

%% Styling
classDef sdk fill:#fce8e6,stroke:#d93025
classDef trace fill:#e6f4ea,stroke:#137333
classDef browser fill:#fef7e0,stroke:#e37400
classDef ext fill:#f3e8fd,stroke:#7627bb

class BP browser
class DDT trace
class SDK sdk
class DD ext
```

### Main Process

The Electron SDK captures RUM sessions, collects RUM events, and forwards them to Datadog, enriching events from dd-trace and the Browser SDK with RUM and Electron context along the way. dd-trace instruments network requests, command executions, and IPC calls, then forwards spans to the Electron SDK through the `diagnostics_channel`.

More details in the [How tracing works](#how-tracing-works) section.

### Renderer Process

dd-trace exposes a `DatadogEventBridge` to every renderer process via a preload script. When present, the Browser SDK detects the bridge and routes events through IPC to the Electron SDK instead of sending them directly to Datadog servers.
More details in the [Preload injection](#preload-injection) section.

## Event Pipeline

```mermaid
flowchart LR
Expand Down Expand Up @@ -42,27 +94,27 @@ flowchart LR
BC -. "send" .-> INT[HTTP intake]
```

## Event Pipeline
### Event Manager

The `EventManager` provides a handler-based pipeline for processing events.

### Event Kinds
#### Event Kinds

- **`RawEvent`** — Emitted by domain code, contains event-specific data, a source (`MAIN` | `RENDERER`), and a format (`RUM` | `TELEMETRY`).
- **`ServerEvent`** — Ready for transport, tagged with a track (`RUM` | `LOGS`).
- **`LifecycleEvent`** — Internal signals (e.g., `END_USER_ACTIVITY`, `SESSION_RENEW`), not sent to intake.

### Handler Pattern
#### Handler Pattern

Handlers register on `EventManager` with `canHandle` (type guard) and `handle` (processing + optional `notify` callback to emit derived events).

See `src/event/` and `src/domain/assembly.ts`.

## Assembly and Format Hooks
### Assembly and Format Hooks

The `Assembly` handler transforms `RawEvent` into `ServerEvent` by enriching raw data with contextual properties via format hooks.

### Format Hooks
#### Format Hooks

`createFormatHooks()` creates per-format hook pairs (`registerRum`/`triggerRum`, `registerTelemetry`/`triggerTelemetry`). Each hook callback can return:

Expand All @@ -84,6 +136,104 @@ Internal observability for the SDK itself. Captures SDK errors and sends them as

See `src/domain/telemetry/`.

## APM Tracing (dd-trace integration)

The SDK integrates with dd-trace (bundled) for span collection, HTTP resource tracing, and automatic preload injection.

dd-trace links:

- [electron plugin (net, ipc)](https://github.com/DataDog/dd-trace-js/tree/master/packages/datadog-plugin-electron/src)
- [electron instrumentation (preload bridge)](https://github.com/DataDog/dd-trace-js/tree/master/packages/datadog-instrumentations/src/electron)
- [electron exporter](https://github.com/DataDog/dd-trace-js/blob/master/packages/dd-trace/src/exporters/electron/index.js)

### Instrumentation (`@datadog/electron-sdk/instrument`)

dd-trace instruments modules by hooking `require()`. For this to work, it must be initialized **before** `require('electron')`. The SDK provides a dedicated entry point for this:

```typescript
import '@datadog/electron-sdk/instrument'; // must be first
import { app, BrowserWindow } from 'electron';
```

This entry point initializes dd-trace with the `electron` exporter and silently no-ops if dd-trace is unavailable. Because it runs before `electron` is imported, dd-trace can:

- Hook `require('electron')` to wrap `BrowserWindow` for automatic preload injection
- Instrument Electron's `net` module, `ipcMain`, and Node.js `http` for span collection

### How tracing works
Comment thread
bcaudan marked this conversation as resolved.

dd-trace's `electron` exporter publishes normalized spans to a Node.js diagnostics channel (`datadog:apm:electron:export`) instead of sending them to a local Datadog Agent. The `SpanProcessor` subscribes to this channel and:

1. **Filters** SDK-internal requests (intake/proxy) to prevent self-reporting loops
2. **Enriches** all spans with electron context (application, session, view)
3. **Emits** RUM resource events for HTTP spans
4. **Forwards** all spans to the spans intake grouped per trace

```
Instrumented code (fetch, net.request, ipcMain.handle, http)
dd-trace creates spans
ElectronExporter → diagnostics channel 'datadog:apm:electron:export'
SpanProcessor (filters, enriches, emits)
All spans → Transport → /api/v2/spans
HTTP spans → Assembly → Transport → /api/v2/rum (as RUM resources)
```

All spans are enriched with electron context (`_dd.application.id`, `_dd.session.id`, `_dd.view.id`) via the span assembly hook. Trace and span IDs are converted to **hexadecimal strings** for the spans intake.

### Preload injection

dd-trace wraps `BrowserWindow` to automatically inject a preload script via `session.registerPreloadScript()`. This preload sets up the `DatadogEventBridge` in every renderer process.

For this to work, dd-trace must hook `require('electron')` **before** electron is loaded. This is straightforward in non-bundled environments but requires bundler plugins for Vite and Webpack:

- **Vite** hoists all `require()` calls to the top of the bundle, breaking import order. The `datadogVitePlugin` (`@datadog/electron-sdk/vite-plugin`) fixes this by externalizing dd-trace, prepending initialization before hoisted requires, and copying dd-trace's runtime dependencies into the build output for packaged apps.
- **Webpack** preserves module execution order (lazy evaluation via `__webpack_require__`), so the import order in source code is maintained. The `DatadogWebpackPlugin` (`@datadog/electron-sdk/webpack-plugin`) copies dd-trace's preload script into the webpack output at the fallback path dd-trace expects in packaged apps.
- **esbuild** preserves module execution order (like Webpack), so `import '@datadog/electron-sdk/instrument'` runs before `import 'electron'` without special hoisting tricks. The `datadogEsbuildPlugin` (`@datadog/electron-sdk/esbuild-plugin`) externalizes dd-trace and prepends an initialization banner. Unlike the Vite and Webpack plugins, it does **not** copy dependencies into the build output (esbuild lacks an equivalent post-emit hook) — the packaging tool (e.g., Electron Forge, electron-builder) must ensure `node_modules` is available at runtime.

See `src/domain/tracing/`, `src/entries/instrument.ts`, `src/entries/vite-plugin.ts`, `src/entries/webpack-plugin.ts`, and `src/entries/esbuild-plugin.ts`.

### dd-trace as a bundled dependency

dd-trace is declared as a **direct runtime dependency** in `package.json`, not as an optional or peer dependency. When customers install `@datadog/electron-sdk`, they get dd-trace automatically.

#### Why bundle it

dd-trace's module hooking must initialize before `require('electron')`. This creates tight coupling between the SDK and dd-trace:

- The SDK's `instrument` entry point calls `tracer.init({ exporter: 'electron' })` — a custom exporter built specifically for the Electron SDK
- The `SpanProcessor` subscribes to a specific diagnostics channel (`datadog:apm:electron:export`) that dd-trace publishes to
- Bundler plugins know dd-trace's internal layout (e.g., the preload script path `dd-trace/packages/datadog-instrumentations/src/electron/preload.js`)

Making it a direct dependency ensures a single, tested version is always present. The alternatives were considered:

| Approach | Pros | Cons |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direct dependency** (current) | Guaranteed compatible version; no setup burden on customers; deterministic behavior | Larger install footprint; version locked to SDK releases |
| **Peer dependency** | Customer controls version; smaller SDK package | Version mismatch risk; customer must install separately; hard to guarantee the custom `electron` exporter exists in their version |
| **Optional dependency** | — | SDK does not work without dd-trace; same mismatch risk as peer; confusing DX |
| **Vendored / embedded in SDK bundle** (POC approach) | Single file, no transitive deps | Fragile — dd-trace uses dynamic requires, native module loading, and runtime path resolution that break when bundled into a single file; would need constant re-vendoring |

#### Optional dependencies are stripped

dd-trace declares optional dependencies (OpenTelemetry bindings, OpenFeature, ASM, IAST, etc.) that are irrelevant for Electron:
These optional dependencies may or may not install in the customer's `node_modules` depending on platform and package manager behavior. Critically, the **bundler plugins only copy `dependencies`, not `optionalDependencies`**, when populating the build output's `node_modules`. This means they are always excluded from the packaged app.

#### Dependency size

| What | Size | Notes |
| ------------------------------------------------------------------------ | --------- | ------------------------------------------------- |
| dd-trace (stripped, no optional deps) | ~7 MB | The core dd-trace package |
| Runtime transitive deps (dc-polyfill, import-in-the-middle, acorn, etc.) | ~1 MB | Required by dd-trace at runtime |
| **Total copied to packaged app** | **~8 MB** | What bundler plugins copy via `copyPackageTree` |
| electron-sdk own dist | ~4 MB | SDK code + WASM chunks |
| dd-trace optional deps (NOT copied) | Unknown | Native modules, etc — excluded from packaged apps |

The `copyPackageTree` function in the Vite and Webpack plugins walks only the `dependencies` field of each package's `package.json`, so the ~84 MB of optional native modules never end up in the packaged app.

## Two-Tier Configuration

`InitConfiguration` (user API) → `buildConfiguration()` → `Configuration` (internal, validated).
Expand Down
1 change: 1 addition & 0 deletions e2e/app/src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ declare global {
generateUnhandledRejection: () => Promise<void>;
generateManualError: (startTime?: number) => Promise<void>;
crash: () => Promise<void>;
ping: () => Promise<string>;
openBridgeFileWindow: () => Promise<void>;
openBridgeFileWindowNoIsolation: () => Promise<void>;
openBridgeHttpWindow: () => Promise<void>;
Expand Down
Loading