diff --git a/.github/workflows/apm-integrations.yml b/.github/workflows/apm-integrations.yml
index 493bcc7cfea..ff4e4e01f91 100644
--- a/.github/workflows/apm-integrations.yml
+++ b/.github/workflows/apm-integrations.yml
@@ -393,6 +393,22 @@ jobs:
api_key: ${{ secrets.DD_API_KEY }}
service: dd-trace-js-tests
+ electron:
+ runs-on: ubuntu-latest
+ env:
+ PLUGINS: electron
+ steps:
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
+ - uses: ./.github/actions/testagent/start
+ - uses: ./.github/actions/node/latest
+ - uses: ./.github/actions/install
+ - run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
+ - run: |
+ sudo apt-get update
+ sudo apt-get install -y xvfb
+ Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
+ - run: DISPLAY=:99 yarn test:plugins:ci
+
express:
runs-on: ubuntu-latest
env:
diff --git a/docs/test.ts b/docs/test.ts
index 5a6edcbb219..a41be5f2240 100644
--- a/docs/test.ts
+++ b/docs/test.ts
@@ -323,6 +323,8 @@ tracer.use('cucumber', { service: 'cucumber-service' });
tracer.use('dns');
tracer.use('elasticsearch');
tracer.use('elasticsearch', elasticsearchOptions);
+tracer.use('electron');
+tracer.use('electron', { net: httpClientOptions });
tracer.use('express');
tracer.use('express', httpServerOptions);
tracer.use('fastify');
diff --git a/index.d.ts b/index.d.ts
index 83e8ded0b4c..558434d74d7 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -201,6 +201,7 @@ interface Plugins {
"cypress": tracer.plugins.cypress;
"dns": tracer.plugins.dns;
"elasticsearch": tracer.plugins.elasticsearch;
+ "electron": tracer.plugins.electron;
"express": tracer.plugins.express;
"fastify": tracer.plugins.fastify;
"fetch": tracer.plugins.fetch;
@@ -1814,6 +1815,14 @@ declare namespace tracer {
};
}
+ /**
+ * This plugin automatically instruments the
+ * [electron](https://github.com/electron/electron) module.
+ */
+ interface electron extends Instrumentation {
+ net?: HttpClient
+ }
+
/**
* This plugin automatically instruments the
* [express](http://expressjs.com/) module.
diff --git a/packages/datadog-instrumentations/src/electron.js b/packages/datadog-instrumentations/src/electron.js
new file mode 100644
index 00000000000..335530b1668
--- /dev/null
+++ b/packages/datadog-instrumentations/src/electron.js
@@ -0,0 +1,241 @@
+'use strict'
+
+const { join } = require('path')
+const { wrap } = require('../../datadog-shimmer')
+const { addHook, channel, tracingChannel } = require('./helpers/instrument')
+
+const requestCh = tracingChannel('apm:electron:net:request')
+const mainReceiveCh = tracingChannel('apm:electron:ipc:main:receive')
+const mainHandleCh = tracingChannel('apm:electron:ipc:main:handle')
+const mainSendCh = tracingChannel('apm:electron:ipc:main:send')
+const rendererPatchedCh = channel('apm:electron:ipc:renderer:patched')
+const rendererReceiveCh = tracingChannel('apm:electron:ipc:renderer:receive')
+const rendererSendCh = tracingChannel('apm:electron:ipc:renderer:send')
+
+const listeners = {}
+const handlers = {}
+
+function createWrapRequest (ch) {
+ return function wrapRequest (request) {
+ return function (...args) {
+ if (!ch.start.hasSubscribers) return request.apply(this, arguments)
+
+ const ctx = { args }
+
+ return ch.start.runStores(ctx, () => {
+ try {
+ const req = request.apply(this, ctx.args)
+ const emit = req.emit
+
+ ctx.req = req
+
+ req.emit = function (eventName, arg) {
+ /* eslint-disable no-fallthrough */
+ switch (eventName) {
+ case 'response':
+ ctx.res = arg
+ ctx.res.on('error', error => {
+ ctx.error = error
+ ch.error.publish(ctx)
+ ch.asyncStart.publish(ctx)
+ })
+ ctx.res.on('end', () => ch.asyncStart.publish(ctx))
+ break
+ case 'error':
+ ctx.error = arg
+ ch.error.publish(ctx)
+ case 'abort':
+ ch.asyncStart.publish(ctx)
+ }
+
+ return emit.apply(this, arguments)
+ }
+
+ return req
+ } catch (e) {
+ ctx.error = e
+ ch.error.publish(ctx)
+ throw e
+ } finally {
+ ch.end.publish(ctx)
+ }
+ })
+ }
+ }
+}
+
+function createWrapAddListener (ch, mappings) {
+ return function wrapAddListener (addListener) {
+ return function (channel, listener) {
+ const wrappedListener = (event, ...args) => {
+ const ctx = { args, channel, event }
+
+ return ch.tracePromise(() => listener.call(this, event, ...args), ctx)
+ }
+
+ const mapping = mappings[channel] || new WeakMap()
+ const wrapper = mapping.get(listener) || wrappedListener
+
+ mapping.set(listener, wrapper)
+
+ return addListener.call(this, channel, wrappedListener)
+ }
+ }
+}
+
+function createWrapRemoveListener (mappings) {
+ return function wrapRemoveListener (removeListener) {
+ return function (channel, listener) {
+ const mapping = mappings[channel]
+
+ if (mapping) {
+ const wrapper = mapping.get(listener)
+
+ if (wrapper) {
+ return removeListener.call(this, channel, wrapper)
+ }
+ }
+
+ return removeListener.call(this, channel, listener)
+ }
+ }
+}
+
+function createWrapRemoveAllListeners (mappings) {
+ return function wrapRemoveAllListeners (removeAllListeners) {
+ return function (channel) {
+ if (channel) {
+ delete mappings[channel]
+ } else {
+ Object.keys(mappings).forEach(key => delete mappings[key])
+ }
+
+ return removeAllListeners.apply(this, channel)
+ }
+ }
+}
+
+function createWrapSend (ch, promise = false) {
+ const trace = (promise ? ch.tracePromise : ch.traceSync).bind(ch)
+
+ return function wrapSend (send) {
+ return function (channel, ...args) {
+ const ctx = { args, channel, self: this }
+
+ return trace(() => send.call(this, channel, ...args), ctx)
+ }
+ }
+}
+
+function wrapSendToFrame (send) {
+ return function (frameId, channel, ...args) {
+ const ctx = { args, channel, frameId, self: this }
+
+ return mainSendCh.traceSync(() => send.call(this, frameId, channel, ...args), ctx)
+ }
+}
+
+function wrapBrowserWindow (electron) {
+ const moduleExports = {}
+
+ class DatadogBrowserWindow extends electron.BrowserWindow {
+ constructor (options = {}) {
+ const win = super(options)
+
+ // TODO: Move this to plugin?
+ win.webContents.session.registerPreloadScript({
+ type: 'frame', // TODO: service-worker
+ filePath: join(__dirname, 'electron', 'preload.js')
+ })
+
+ // BrowserWindow doesn't support subclassing because it's all native code
+ // so we return an instance of it instead of the subclass.
+ return win
+ }
+ }
+
+ Object.defineProperty(moduleExports, 'BrowserWindow', {
+ enumerable: true,
+ get: () => DatadogBrowserWindow,
+ configurable: false
+ })
+
+ for (const key of Reflect.ownKeys(electron)) {
+ const descriptor = Reflect.getOwnPropertyDescriptor(electron, key)
+
+ if (key === 'BrowserWindow') continue
+
+ Object.defineProperty(moduleExports, key, descriptor)
+ }
+
+ return moduleExports
+}
+
+function wrapWebContents (proto) {
+ const descriptor = Object.getOwnPropertyDescriptor(proto, 'webContents')
+ const wrapped = new WeakSet()
+ const wrapSend = createWrapSend(mainSendCh)
+
+ Object.defineProperty(proto, 'webContents', {
+ get () {
+ const webContents = descriptor.get.apply(this)
+
+ if (!wrapped.has(webContents)) {
+ // wrap(webContents, 'postMessage', wrapSend)
+ wrap(webContents, 'send', wrapSend)
+ wrap(webContents, 'sendToFrame', wrapSendToFrame)
+
+ wrapped.add(webContents)
+ }
+
+ return webContents
+ }
+ })
+}
+
+addHook({ name: 'electron', versions: ['>=37.0.0'] }, electron => {
+ // Electron exports a string in Node and an object in Electron.
+ if (typeof electron === 'string') return electron
+
+ const { BrowserWindow, ipcMain, ipcRenderer, net } = electron
+
+ if (net) {
+ // This also covers `fetch` as it uses `request` under the hood.
+ wrap(net, 'request', createWrapRequest(requestCh))
+ }
+
+ if (ipcRenderer) {
+ wrap(ipcRenderer, 'invoke', createWrapSend(rendererSendCh, true))
+ // wrap(ipcRenderer, 'postMessage', createWrapSend(rendererSendCh))
+ wrap(ipcRenderer, 'send', createWrapSend(rendererSendCh))
+ wrap(ipcRenderer, 'sendSync', createWrapSend(rendererSendCh))
+ wrap(ipcRenderer, 'sendToHost', createWrapSend(rendererSendCh))
+
+ wrap(ipcRenderer, 'addListener', createWrapAddListener(rendererReceiveCh, listeners))
+ wrap(ipcRenderer, 'off', createWrapRemoveListener(listeners))
+ wrap(ipcRenderer, 'on', createWrapAddListener(rendererReceiveCh, listeners))
+ wrap(ipcRenderer, 'once', createWrapAddListener(rendererReceiveCh, listeners))
+ wrap(ipcRenderer, 'removeListener', createWrapRemoveListener(listeners))
+ wrap(ipcRenderer, 'removeAllListeners', createWrapRemoveAllListeners(listeners))
+
+ ipcRenderer.send('datadog:apm:renderer:patched')
+ } else {
+ wrap(ipcMain, 'addListener', createWrapAddListener(mainReceiveCh, listeners))
+ wrap(ipcMain, 'handle', createWrapAddListener(mainHandleCh, handlers))
+ wrap(ipcMain, 'handleOnce', createWrapAddListener(mainHandleCh, handlers))
+ wrap(ipcMain, 'off', createWrapRemoveListener(listeners))
+ wrap(ipcMain, 'on', createWrapAddListener(mainReceiveCh, listeners))
+ wrap(ipcMain, 'once', createWrapAddListener(mainReceiveCh, listeners))
+ wrap(ipcMain, 'removeAllListeners', createWrapRemoveAllListeners(listeners))
+ wrap(ipcMain, 'removeHandler', createWrapRemoveAllListeners(handlers))
+ wrap(ipcMain, 'removeListener', createWrapRemoveListener(listeners))
+
+ ipcMain.once('datadog:apm:renderer:patched', event => rendererPatchedCh.publish(event))
+
+ wrapWebContents(BrowserWindow.prototype)
+
+ electron = wrapBrowserWindow(electron)
+ }
+
+ return electron
+})
diff --git a/packages/datadog-instrumentations/src/electron/preload.js b/packages/datadog-instrumentations/src/electron/preload.js
new file mode 100644
index 00000000000..e99b6519382
--- /dev/null
+++ b/packages/datadog-instrumentations/src/electron/preload.js
@@ -0,0 +1,5 @@
+/* eslint-disable unicorn/no-empty-file */
+
+'use strict'
+
+// TODO: Expose main process APIs through `contextBridge`.
diff --git a/packages/datadog-instrumentations/src/helpers/hooks.js b/packages/datadog-instrumentations/src/helpers/hooks.js
index c46827b3537..3b2ef166ac2 100644
--- a/packages/datadog-instrumentations/src/helpers/hooks.js
+++ b/packages/datadog-instrumentations/src/helpers/hooks.js
@@ -52,6 +52,7 @@ module.exports = {
'dd-trace-api': () => require('../dd-trace-api'),
dns: () => require('../dns'),
elasticsearch: () => require('../elasticsearch'),
+ electron: () => require('../electron'),
express: () => require('../express'),
'express-mongo-sanitize': () => require('../express-mongo-sanitize'),
'express-session': () => require('../express-session'),
diff --git a/packages/datadog-instrumentations/src/helpers/register.js b/packages/datadog-instrumentations/src/helpers/register.js
index 6e5f0f3985a..21bc4556c9a 100644
--- a/packages/datadog-instrumentations/src/helpers/register.js
+++ b/packages/datadog-instrumentations/src/helpers/register.js
@@ -135,18 +135,23 @@ for (const packageName of names) {
}
try {
- loadChannel.publish({ name, version, file })
- // Send the name and version of the module back to the callback because now addHook
- // takes in an array of names so by passing the name the callback will know which module name is being used
- // TODO(BridgeAR): This is only true in case the name is identical
- // in all loads. If they deviate, the deviating name would not be
- // picked up due to the unification. Check what modules actually use the name.
- // TODO(BridgeAR): Only replace moduleExports if the hook returns a new value.
- // This allows to reduce the instrumentation code (no return needed).
-
- moduleExports = hook(moduleExports, version, name, isIitm) ?? moduleExports
- // Set the moduleExports in the hooks WeakSet
- hook[HOOK_SYMBOL].add(moduleExports)
+ // Electron exports a string in Node which is not supported by
+ // WeakSets.
+ if (typeof moduleExports !== 'string') {
+ loadChannel.publish({ name, version, file })
+ // Send the name and version of the module back to the callback
+ // because now addHook takes in an array of names so by passing
+ // the name the callback will know which module name is being used
+ // TODO(BridgeAR): This is only true in case the name is identical
+ // in all loads. If they deviate, the deviating name would not be
+ // picked up due to the unification. Check what modules actually use the name.
+ // TODO(BridgeAR): Only replace moduleExports if the hook returns a new value.
+ // This allows to reduce the instrumentation code (no return needed).
+
+ moduleExports = hook(moduleExports, version, name, isIitm) ?? moduleExports
+ // Set the moduleExports in the hooks WeakSet
+ hook[HOOK_SYMBOL].add(moduleExports)
+ }
} catch (e) {
log.info('Error during ddtrace instrumentation of application, aborting.', e)
telemetry('error', [
diff --git a/packages/datadog-instrumentations/src/jest.js b/packages/datadog-instrumentations/src/jest.js
index 95db563d24d..1f0ca91f45d 100644
--- a/packages/datadog-instrumentations/src/jest.js
+++ b/packages/datadog-instrumentations/src/jest.js
@@ -883,7 +883,7 @@ function getCliWrapper (isNewJestVersion) {
const timeoutPromise = new Promise((resolve) => {
timeoutId = setTimeout(() => {
resolve('timeout')
- }, FLUSH_TIMEOUT).unref()
+ }, FLUSH_TIMEOUT).unref?.()
})
testSessionFinishCh.publish({
diff --git a/packages/datadog-plugin-electron/src/index.js b/packages/datadog-plugin-electron/src/index.js
new file mode 100644
index 00000000000..df720d480cf
--- /dev/null
+++ b/packages/datadog-plugin-electron/src/index.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const CompositePlugin = require('../../dd-trace/src/plugins/composite')
+const ElectronIpcPlugin = require('./ipc')
+const ElectronNetPlugin = require('./net')
+
+class ElectronPlugin extends CompositePlugin {
+ static id = 'electron'
+ static get plugins () {
+ return {
+ net: ElectronNetPlugin,
+ ipc: ElectronIpcPlugin
+ }
+ }
+}
+
+module.exports = ElectronPlugin
diff --git a/packages/datadog-plugin-electron/src/ipc.js b/packages/datadog-plugin-electron/src/ipc.js
new file mode 100644
index 00000000000..b0f1ec8b226
--- /dev/null
+++ b/packages/datadog-plugin-electron/src/ipc.js
@@ -0,0 +1,137 @@
+'use strict'
+
+const CompositePlugin = require('../../dd-trace/src/plugins/composite')
+const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
+const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
+
+class ElectronIpcPlugin extends CompositePlugin {
+ static id = 'electron:ipc'
+ static get plugins () {
+ return {
+ main: ElectronMainPlugin,
+ renderer: ElectronRendererPlugin
+ }
+ }
+}
+
+class ElectronMainPlugin extends CompositePlugin {
+ static id = 'electron:ipc:main'
+ static get plugins () {
+ return {
+ receive: ElectronMainReceivePlugin,
+ send: ElectronMainSendPlugin
+ }
+ }
+}
+
+class ElectronRendererPlugin extends CompositePlugin {
+ static id = 'electron:ipc:renderer'
+ static get plugins () {
+ return {
+ receive: ElectronRendererReceivePlugin,
+ send: ElectronRendererSendPlugin
+ }
+ }
+}
+
+class ElectronRendererReceivePlugin extends ConsumerPlugin {
+ static id = 'electron:ipc:renderer:receive'
+ static component = 'electron'
+ static operation = 'receive'
+ static prefix = 'tracing:apm:electron:ipc:renderer:receive'
+
+ bindStart (ctx) {
+ const { args, channel } = ctx
+
+ if (channel?.startsWith('datadog:')) return
+
+ const childOf = this._tracer.extract('text_map', args[args.length - 1])
+
+ if (childOf) {
+ args.pop()
+ }
+
+ this.startSpan({
+ childOf,
+ resource: channel,
+ type: 'worker',
+ meta: {}
+ }, ctx)
+
+ return ctx.currentStore
+ }
+
+ asyncEnd (ctx) {
+ this.finish(ctx)
+ }
+}
+
+class ElectronRendererSendPlugin extends ProducerPlugin {
+ static id = 'electron:ipc:renderer:send'
+ static component = 'electron'
+ static operation = 'send'
+ static prefix = 'tracing:apm:electron:ipc:renderer:send'
+
+ bindStart (ctx) {
+ const { args, channel } = ctx
+
+ if (channel?.startsWith('datadog:')) return
+
+ const span = this.startSpan({
+ resource: channel,
+ meta: {}
+ }, ctx)
+
+ if (this._shouldInject(ctx)) {
+ const carrier = {}
+
+ this._tracer.inject(span, 'text_map', carrier)
+
+ args.push(carrier)
+ }
+
+ return ctx.currentStore
+ }
+
+ end (ctx) {
+ if (ctx.hasOwnProperty('result')) {
+ this.finish(ctx)
+ }
+ }
+
+ asyncEnd (ctx) {
+ this.finish(ctx)
+ }
+
+ // Renderer can always inject since main is guaranteed to be patched.
+ _shouldInject () {
+ return true
+ }
+}
+
+class ElectronMainReceivePlugin extends ElectronRendererReceivePlugin {
+ static id = 'electron:ipc:main:receive'
+ static prefix = 'tracing:apm:electron:ipc:main:receive'
+}
+
+class ElectronMainSendPlugin extends ElectronRendererSendPlugin {
+ static id = 'electron:ipc:main:send'
+ static prefix = 'tracing:apm:electron:ipc:main:send'
+
+ constructor (...args) {
+ super(...args)
+
+ this._renderers = new WeakSet()
+
+ this.addSub('apm:electron:ipc:renderer:patched', event => {
+ this._renderers.add(event.sender)
+ })
+ }
+
+ // Only inject when the renderer was patched.
+ _shouldInject ({ self }) {
+ return this._renderers.has(self)
+ }
+}
+
+module.exports = ElectronIpcPlugin
diff --git a/packages/datadog-plugin-electron/src/net.js b/packages/datadog-plugin-electron/src/net.js
new file mode 100644
index 00000000000..1a7af2f38a8
--- /dev/null
+++ b/packages/datadog-plugin-electron/src/net.js
@@ -0,0 +1,82 @@
+'use strict'
+
+const HttpClientPlugin = require('../../datadog-plugin-http/src/client')
+const CompositePlugin = require('../../dd-trace/src/plugins/composite')
+
+class ElectronNetPlugin extends CompositePlugin {
+ static id = 'electron:net'
+ static get plugins () {
+ return {
+ request: ElectronRequestPlugin
+ }
+ }
+}
+
+class ElectronRequestPlugin extends HttpClientPlugin {
+ static id = 'electron:net:request'
+ static component = 'electron'
+ static operation = 'request'
+ static prefix = 'tracing:apm:electron:net:request'
+
+ bindStart (ctx) {
+ const args = ctx.args
+
+ let options = args[0]
+
+ if (typeof options === 'string') {
+ options = args[0] = { url: options }
+ } else if (!options) {
+ options = args[0] = {}
+ }
+
+ const headers = options.headers || {}
+
+ try {
+ if (typeof options === 'string') {
+ options = new URL(options)
+ } else if (options.url) {
+ options = new URL(options.url)
+ }
+ } catch {
+ // leave options as-is
+ }
+
+ options.headers = headers
+ ctx.args = { options }
+
+ const store = super.bindStart(ctx)
+
+ ctx.args = args
+
+ for (const name in options.headers) {
+ if (!headers[name]) {
+ args[0].headers ??= {}
+ args[0].headers[name] = options.headers[name]
+ }
+ }
+
+ return store
+ }
+
+ asyncStart (ctx) {
+ const reqHeaders = {}
+ const resHeaders = {}
+ const responseHead = ctx.res?._responseHead
+ const { statusCode } = responseHead || {}
+
+ for (const header in ctx.req._urlLoaderOptions?.headers || {}) {
+ reqHeaders[header.name] = header.value
+ }
+
+ for (const header in responseHead?.rawHeaders || {}) {
+ resHeaders[header.name] = header.value
+ }
+
+ ctx.req = { headers: reqHeaders }
+ ctx.res = { headers: resHeaders, statusCode }
+
+ this.finish(ctx)
+ }
+}
+
+module.exports = ElectronNetPlugin
diff --git a/packages/datadog-plugin-electron/test/app/index.html b/packages/datadog-plugin-electron/test/app/index.html
new file mode 100644
index 00000000000..1411fe6ba72
--- /dev/null
+++ b/packages/datadog-plugin-electron/test/app/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ Hello World!
+
+
+
+
+
diff --git a/packages/datadog-plugin-electron/test/app/main.js b/packages/datadog-plugin-electron/test/app/main.js
new file mode 100644
index 00000000000..4705e11c760
--- /dev/null
+++ b/packages/datadog-plugin-electron/test/app/main.js
@@ -0,0 +1,78 @@
+'use strict'
+
+/* eslint-disable no-console */
+
+const { BrowserWindow, app, ipcMain, net } = require('electron/main')
+const { join } = require('path')
+
+app.on('ready', () => {
+ process.send('ready')
+ process.on('message', msg => {
+ try {
+ switch (msg.name) {
+ case 'quit': return app.quit()
+ case 'fetch': return onFetch(msg)
+ case 'request': return onRequest(msg)
+ case 'send': return onSend(msg)
+ case 'receive': return onReceive(msg)
+ }
+ } catch (e) {
+ console.error(e)
+ }
+ })
+
+ ipcMain.on('datadog:log', (_event, level, ...args) => {
+ console.log('datadog:log')
+ console[level](...args)
+ })
+})
+
+function onFetch ({ url }) {
+ net.fetch(url)
+}
+
+function onRequest ({ options }) {
+ const req = net.request(options)
+
+ req.on('error', e => console.error(e))
+ req.on('response', res => {
+ res.on('data', () => {})
+ })
+
+ req.end()
+}
+
+function onSend () {
+ loadWindow(win => {
+ win.webContents.send('update-counter', 1)
+ })
+}
+
+function onReceive () {
+ const listener = () => {
+ ipcMain.off('set-title', listener)
+ }
+
+ ipcMain.on('set-title', listener)
+
+ loadWindow(win => {
+ win.webContents.send('datadog:test:send')
+ })
+}
+
+function loadWindow (onShow) {
+ const mainWindow = new BrowserWindow({
+ show: false,
+ webPreferences: {
+ nodeIntegration: true,
+ preload: join(__dirname, 'preload.js')
+ }
+ })
+
+ ipcMain.on('datadog:test:log', (_event, ...args) => {
+ console.log(...args)
+ })
+
+ mainWindow.loadFile('index.html')
+ mainWindow.once('ready-to-show', () => onShow?.(mainWindow))
+}
diff --git a/packages/datadog-plugin-electron/test/app/preload.js b/packages/datadog-plugin-electron/test/app/preload.js
new file mode 100644
index 00000000000..89220f3b0b6
--- /dev/null
+++ b/packages/datadog-plugin-electron/test/app/preload.js
@@ -0,0 +1,20 @@
+'use strict'
+
+const { ipcRenderer } = require('electron/renderer')
+
+if (globalThis.logger) {
+ globalThis.logger.debug = (...args) => ipcRenderer.send('datadog:log', 'debug', ...args)
+ globalThis.logger.info = (...args) => ipcRenderer.send('datadog:log', 'info', ...args)
+ globalThis.logger.warn = (...args) => ipcRenderer.send('datadog:log', 'warn', ...args)
+ globalThis.logger.error = (...args) => ipcRenderer.send('datadog:log', 'error', ...args)
+}
+
+function updateCounter () {
+ ipcRenderer.off('update-counter', updateCounter)
+}
+
+ipcRenderer.on('update-counter', updateCounter)
+
+ipcRenderer.on('datadog:test:send', () => {
+ setImmediate(() => ipcRenderer.send('set-title', 'Test'))
+})
diff --git a/packages/datadog-plugin-electron/test/app/renderer.js b/packages/datadog-plugin-electron/test/app/renderer.js
new file mode 100644
index 00000000000..8dbfc9d685b
--- /dev/null
+++ b/packages/datadog-plugin-electron/test/app/renderer.js
@@ -0,0 +1,3 @@
+'use strict'
+
+// TODO: Test automatic injection of APM<->RUM bridge.
diff --git a/packages/datadog-plugin-electron/test/index.spec.js b/packages/datadog-plugin-electron/test/index.spec.js
new file mode 100644
index 00000000000..b4f20d9352e
--- /dev/null
+++ b/packages/datadog-plugin-electron/test/index.spec.js
@@ -0,0 +1,195 @@
+'use strict'
+
+const assert = require('assert')
+const proc = require('child_process')
+const http = require('http')
+const { afterEach, beforeEach, describe, it } = require('mocha')
+const { join } = require('path')
+const agent = require('../../dd-trace/test/plugins/agent')
+const { withVersions } = require('../../dd-trace/test/setup/mocha')
+
+describe('Plugin', () => {
+ let child
+ let listener
+ let port
+
+ before(done => {
+ const server = http.createServer((req, res) => {
+ res.writeHead(200)
+ res.end()
+ })
+
+ listener = server.listen(0, '127.0.0.1', () => {
+ port = listener.address().port
+ done()
+ })
+ })
+
+ after(done => {
+ listener.close(done)
+ })
+
+ withVersions('electron', ['electron'], version => {
+ const startApp = done => {
+ const electron = require(`../../../versions/electron@${version}`).get()
+
+ child = proc.spawn(electron, [join(__dirname, 'app', 'main')], {
+ env: {
+ ...process.env,
+ NODE_OPTIONS: `-r ${join(__dirname, 'tracer')}`,
+ DD_TRACE_AGENT_PORT: agent.port
+ },
+ stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
+ windowsHide: true
+ })
+
+ child.on('error', done)
+ child.on('message', msg => msg === 'ready' && done())
+ }
+
+ describe('electron', () => {
+ describe('without configuration', () => {
+ beforeEach(() => agent.load('electron'))
+ beforeEach(done => startApp(done))
+
+ afterEach(() => agent.close({ ritmReset: false }))
+ afterEach(done => {
+ child.send({ name: 'quit' })
+ child.on('close', () => done())
+ })
+
+ it('should do automatic instrumentation for fetch', done => {
+ agent
+ .assertSomeTraces(traces => {
+ const span = traces[0][0]
+ const { meta } = span
+
+ assert.strictEqual(span.type, 'http')
+ assert.strictEqual(span.name, 'http.request')
+ assert.strictEqual(span.resource, 'GET')
+ assert.strictEqual(span.service, 'test')
+ assert.strictEqual(span.error, 0)
+
+ assert.strictEqual(meta.component, 'electron')
+ assert.strictEqual(meta['span.kind'], 'client')
+ assert.strictEqual(meta['http.url'], `http://127.0.0.1:${port}/`)
+ assert.strictEqual(meta['http.method'], 'GET')
+ assert.strictEqual(meta['http.status_code'], '200')
+ })
+ .then(done)
+ .catch(done)
+
+ child.send({ name: 'fetch', url: `http://127.0.0.1:${port}` })
+ })
+
+ it('should do automatic instrumentation for request', done => {
+ agent
+ .assertSomeTraces(traces => {
+ const span = traces[0][0]
+ const { meta } = span
+
+ assert.strictEqual(span.type, 'http')
+ assert.strictEqual(span.name, 'http.request')
+ assert.strictEqual(span.resource, 'GET')
+ assert.strictEqual(span.service, 'test')
+ assert.strictEqual(span.error, 0)
+
+ assert.strictEqual(meta.component, 'electron')
+ assert.strictEqual(meta['span.kind'], 'client')
+ assert.strictEqual(meta['http.url'], `http://127.0.0.1:${port}/`)
+ assert.strictEqual(meta['http.method'], 'GET')
+ assert.strictEqual(meta['http.status_code'], '200')
+ })
+ .then(done)
+ .catch(done)
+
+ child.send({ name: 'request', options: `http://127.0.0.1:${port}/` })
+ })
+
+ it('should do automatic instrumentation for main IPC when receiving', done => {
+ agent
+ .assertSomeTraces(traces => {
+ const span = traces[0][0]
+ const { meta } = span
+
+ assert.strictEqual(span.type, 'worker')
+ assert.strictEqual(span.name, 'electron.main.receive')
+ assert.strictEqual(span.resource, 'set-title')
+ assert.strictEqual(span.service, 'test')
+ assert.strictEqual(span.error, 0)
+ assert.strictEqual(span.parent_id, span.trace_id)
+
+ assert.strictEqual(meta.component, 'electron')
+ assert.strictEqual(meta['span.kind'], 'consumer')
+ })
+ .then(done)
+ .catch(done)
+
+ child.send({ name: 'receive' })
+ })
+
+ it('should do automatic instrumentation for main IPC when sending', done => {
+ agent
+ .assertSomeTraces(traces => {
+ const span = traces[0][0]
+ const { meta } = span
+
+ assert.strictEqual(span.name, 'electron.main.send')
+ assert.strictEqual(span.resource, 'update-counter')
+ assert.strictEqual(span.service, 'test')
+ assert.strictEqual(span.error, 0)
+
+ assert.strictEqual(meta.component, 'electron')
+ assert.strictEqual(meta['span.kind'], 'producer')
+ })
+ .then(done)
+ .catch(done)
+
+ child.send({ name: 'send' })
+ })
+
+ it('should do automatic instrumentation for renderer IPC when receiving', done => {
+ agent
+ .assertSomeTraces(traces => {
+ const span = traces[0][0]
+ const { meta } = span
+
+ assert.strictEqual(span.type, 'worker')
+ assert.strictEqual(span.name, 'electron.renderer.receive')
+ assert.strictEqual(span.resource, 'update-counter')
+ assert.strictEqual(span.service, 'test')
+ assert.strictEqual(span.error, 0)
+ assert.strictEqual(span.parent_id, span.trace_id)
+
+ assert.strictEqual(meta.component, 'electron')
+ assert.strictEqual(meta['span.kind'], 'consumer')
+ })
+ .then(done)
+ .catch(done)
+
+ child.send({ name: 'send' })
+ })
+
+ it('should do automatic instrumentation for renderer IPC when sending', done => {
+ agent
+ .assertSomeTraces(traces => {
+ const span = traces[0][0]
+ const { meta } = span
+
+ assert.strictEqual(span.name, 'electron.renderer.send')
+ assert.strictEqual(span.resource, 'set-title')
+ assert.strictEqual(span.service, 'test')
+ assert.strictEqual(span.error, 0)
+
+ assert.strictEqual(meta.component, 'electron')
+ assert.strictEqual(meta['span.kind'], 'producer')
+ })
+ .then(done)
+ .catch(done)
+
+ child.send({ name: 'receive' })
+ })
+ })
+ })
+ })
+})
diff --git a/packages/datadog-plugin-electron/test/tracer.js b/packages/datadog-plugin-electron/test/tracer.js
new file mode 100644
index 00000000000..b2e3cdf8f44
--- /dev/null
+++ b/packages/datadog-plugin-electron/test/tracer.js
@@ -0,0 +1,20 @@
+'use strict'
+
+/* eslint-disable no-console */
+
+const logger = globalThis.logger = {
+ debug: (...args) => console.debug(...args),
+ info: (...args) => console.info(...args),
+ warn: (...args) => console.warn(...args),
+ error: (...args) => console.error(...args),
+}
+
+require('../../dd-trace')
+ .init({
+ service: 'test',
+ env: 'tester',
+ logger,
+ flushInterval: 0,
+ plugins: false
+ })
+ .use('electron', true)
diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js
index 12e7b5020da..193b5be1034 100644
--- a/packages/datadog-plugin-http/src/client.js
+++ b/packages/datadog-plugin-http/src/client.js
@@ -38,9 +38,9 @@ class HttpClientPlugin extends ClientPlugin {
// TODO delegate to super.startspan
const span = this.startSpan(this.operationName(), {
childOf,
- integrationName: this.constructor.id,
+ integrationName: this.component,
meta: {
- [COMPONENT]: this.constructor.id,
+ [COMPONENT]: this.component,
'span.kind': 'client',
'service.name': this.serviceName({ pluginConfig: this.config, sessionDetails: extractSessionDetails(options) }),
'resource.name': method,
diff --git a/packages/datadog-plugin-openai/src/services.js b/packages/datadog-plugin-openai/src/services.js
index 1a07b10a3e2..b1bec40ac39 100644
--- a/packages/datadog-plugin-openai/src/services.js
+++ b/packages/datadog-plugin-openai/src/services.js
@@ -35,7 +35,7 @@ module.exports.init = function (tracerConfig) {
interval = setInterval(() => {
metrics.flush()
- }, FLUSH_INTERVAL).unref()
+ }, FLUSH_INTERVAL).unref?.()
return { metrics, logger }
}
diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js b/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js
index 277797f9400..f2037e45aef 100644
--- a/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js
+++ b/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js
@@ -213,8 +213,8 @@ let enableEsmRewriter = function (telemetryVerbosity) {
}
})
- port1.unref()
- port2.unref()
+ port1.unref?.()
+ port2.unref?.()
try {
Module.register('./rewriter-esm.mjs', {
diff --git a/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js b/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js
index 73ac442b3c4..6e5bf954772 100644
--- a/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js
+++ b/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js
@@ -100,7 +100,7 @@ function clearCache () { // only for test purposes
function startClearCacheTimer () {
resetVulnerabilityCacheTimer = setInterval(clearCache, RESET_VULNERABILITY_CACHE_INTERVAL)
- resetVulnerabilityCacheTimer.unref()
+ resetVulnerabilityCacheTimer.unref?.()
}
function stopClearCacheTimer () {
diff --git a/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js b/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js
index e7f98765e54..4557a686633 100644
--- a/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js
+++ b/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js
@@ -114,7 +114,7 @@ class TestVisDynamicInstrumentation {
})
// Allow the parent to exit even if the worker is still running
- this.worker.unref()
+ this.worker.unref?.()
this.breakpointSetChannel.port2.on('message', (probeId) => {
const resolve = probeIdToResolveBreakpointSet.get(probeId)
@@ -122,7 +122,7 @@ class TestVisDynamicInstrumentation {
resolve()
probeIdToResolveBreakpointSet.delete(probeId)
}
- }).unref()
+ }).unref?.()
this.breakpointHitChannel.port2.on('message', ({ snapshot }) => {
const { probe: { id: probeId } } = snapshot
@@ -132,7 +132,7 @@ class TestVisDynamicInstrumentation {
} else {
log.warn('Received a breakpoint hit for an unknown probe')
}
- }).unref()
+ }).unref?.()
this.breakpointRemoveChannel.port2.on('message', (probeId) => {
const resolve = probeIdToResolveBreakpointRemove.get(probeId)
@@ -140,7 +140,7 @@ class TestVisDynamicInstrumentation {
resolve()
probeIdToResolveBreakpointRemove.delete(probeId)
}
- }).unref()
+ }).unref?.()
}
}
diff --git a/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js b/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js
index 474a1114644..c5f09409c5a 100644
--- a/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js
+++ b/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js
@@ -47,11 +47,11 @@ class CiVisibilityExporter extends AgentInfoExporter {
const gitUploadTimeoutId = setTimeout(() => {
this._resolveGit(new Error('Timeout while uploading git metadata'))
- }, GIT_UPLOAD_TIMEOUT).unref()
+ }, GIT_UPLOAD_TIMEOUT).unref?.()
const canUseCiVisProtocolTimeoutId = setTimeout(() => {
this._resolveCanUseCiVisProtocol(false)
- }, CAN_USE_CI_VIS_PROTOCOL_TIMEOUT).unref()
+ }, CAN_USE_CI_VIS_PROTOCOL_TIMEOUT).unref?.()
this._gitUploadPromise = new Promise(resolve => {
this._resolveGit = (err) => {
diff --git a/packages/dd-trace/src/datastreams/processor.js b/packages/dd-trace/src/datastreams/processor.js
index f3c59bbe232..0e7569f0e6a 100644
--- a/packages/dd-trace/src/datastreams/processor.js
+++ b/packages/dd-trace/src/datastreams/processor.js
@@ -154,7 +154,7 @@ class DataStreamsProcessor {
if (this.enabled) {
this.timer = setInterval(this.onInterval.bind(this), flushInterval)
- this.timer.unref()
+ this.timer.unref?.()
}
process.once('beforeExit', () => this.onInterval())
}
diff --git a/packages/dd-trace/src/debugger/devtools_client/source-maps.js b/packages/dd-trace/src/debugger/devtools_client/source-maps.js
index c2294ac8f73..1d935e72815 100644
--- a/packages/dd-trace/src/debugger/devtools_client/source-maps.js
+++ b/packages/dd-trace/src/debugger/devtools_client/source-maps.js
@@ -54,7 +54,7 @@ function setCacheTTL () {
// Clear cache a few seconds after it was last used
cache.clear()
}
- }, 5000).unref()
+ }, 5000).unref?.()
}
function loadInlineSourceMap (data) {
diff --git a/packages/dd-trace/src/debugger/devtools_client/state.js b/packages/dd-trace/src/debugger/devtools_client/state.js
index 4c83a1cf2cc..61679ad4307 100644
--- a/packages/dd-trace/src/debugger/devtools_client/state.js
+++ b/packages/dd-trace/src/debugger/devtools_client/state.js
@@ -173,7 +173,7 @@ session.on('Debugger.scriptParsed', ({ params }) => {
if (reEvaluateProbesTimer === null) {
reEvaluateProbesTimer = setTimeout(() => {
session.emit('scriptLoadingStabilized')
- }, 500).unref()
+ }, 500).unref?.()
} else {
reEvaluateProbesTimer.refresh()
}
diff --git a/packages/dd-trace/src/debugger/index.js b/packages/dd-trace/src/debugger/index.js
index 955243e55ad..d19aab32eac 100644
--- a/packages/dd-trace/src/debugger/index.js
+++ b/packages/dd-trace/src/debugger/index.js
@@ -99,13 +99,13 @@ function start (config, rc) {
}
})
- worker.unref()
- probeChannel.port1.unref()
- probeChannel.port2.unref()
- logChannel.port1.unref()
- logChannel.port2.unref()
- configChannel.port1.unref()
- configChannel.port2.unref()
+ worker.unref?.()
+ probeChannel.port1.unref?.()
+ probeChannel.port2.unref?.()
+ logChannel.port1.unref?.()
+ logChannel.port2.unref?.()
+ configChannel.port1.unref?.()
+ configChannel.port2.unref?.()
}
function configure (config) {
diff --git a/packages/dd-trace/src/dogstatsd.js b/packages/dd-trace/src/dogstatsd.js
index b33fcfde271..3be3979d0e9 100644
--- a/packages/dd-trace/src/dogstatsd.js
+++ b/packages/dd-trace/src/dogstatsd.js
@@ -152,7 +152,7 @@ class DogStatsDClient {
const socket = dgram.createSocket(type)
socket.on('error', () => {})
- socket.unref()
+ socket.unref?.()
return socket
}
@@ -357,7 +357,7 @@ class CustomMetrics {
const flush = this.flush.bind(this)
// TODO(bengl) this magic number should be configurable
- setInterval(flush, 10 * 1000).unref()
+ setInterval(flush, 10 * 1000).unref?.()
process.once('beforeExit', flush)
}
diff --git a/packages/dd-trace/src/exporters/agent/index.js b/packages/dd-trace/src/exporters/agent/index.js
index 4e6d852c29a..b8f1c7f6e6f 100644
--- a/packages/dd-trace/src/exporters/agent/index.js
+++ b/packages/dd-trace/src/exporters/agent/index.js
@@ -57,7 +57,7 @@ class AgentExporter {
this.#timer = setTimeout(() => {
this._writer.flush()
this.#timer = undefined
- }, flushInterval).unref()
+ }, flushInterval).unref?.()
}
}
diff --git a/packages/dd-trace/src/exporters/common/agent-info-exporter.js b/packages/dd-trace/src/exporters/common/agent-info-exporter.js
index b36a582156f..6ff3e942784 100644
--- a/packages/dd-trace/src/exporters/common/agent-info-exporter.js
+++ b/packages/dd-trace/src/exporters/common/agent-info-exporter.js
@@ -66,7 +66,7 @@ class AgentInfoExporter {
this[timerKey] = setTimeout(() => {
writer.flush()
this[timerKey] = undefined
- }, flushInterval).unref()
+ }, flushInterval).unref?.()
}
}
diff --git a/packages/dd-trace/src/external-logger/src/index.js b/packages/dd-trace/src/external-logger/src/index.js
index 77f5523648a..b0bc5e5707c 100644
--- a/packages/dd-trace/src/external-logger/src/index.js
+++ b/packages/dd-trace/src/external-logger/src/index.js
@@ -27,7 +27,7 @@ class ExternalLogger {
}
this.timer = setInterval(() => {
this.flush()
- }, this.interval).unref()
+ }, this.interval).unref?.()
tracerLogger.debug(`started log writer to https://${this.intake}${this.endpoint}`)
}
diff --git a/packages/dd-trace/src/llmobs/writers/base.js b/packages/dd-trace/src/llmobs/writers/base.js
index 8f4e84b6e61..56a83f283ac 100644
--- a/packages/dd-trace/src/llmobs/writers/base.js
+++ b/packages/dd-trace/src/llmobs/writers/base.js
@@ -32,7 +32,7 @@ class BaseLLMObsWriter {
this._periodic = setInterval(() => {
this.flush()
- }, this._interval).unref()
+ }, this._interval).unref?.()
this._beforeExitHandler = () => {
this.destroy()
diff --git a/packages/dd-trace/src/openfeature/writers/base.js b/packages/dd-trace/src/openfeature/writers/base.js
index 462280bf5d8..f523166ca66 100644
--- a/packages/dd-trace/src/openfeature/writers/base.js
+++ b/packages/dd-trace/src/openfeature/writers/base.js
@@ -54,7 +54,7 @@ class BaseFFEWriter {
this._periodic = setInterval(() => {
this.flush()
- }, this._interval).unref()
+ }, this._interval).unref?.()
this._beforeExitHandler = () => {
this.destroy()
diff --git a/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js b/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js
index 8abea441ad5..7386cb1d48a 100644
--- a/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js
+++ b/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js
@@ -142,7 +142,7 @@ class PeriodicMetricReader {
this.#timer = setInterval(() => {
this.#collectAndExport()
- }, this.#exportInterval).unref()
+ }, this.#exportInterval).unref?.()
}
/**
diff --git a/packages/dd-trace/src/plugins/index.js b/packages/dd-trace/src/plugins/index.js
index 091fb24f9ed..7af5de9456f 100644
--- a/packages/dd-trace/src/plugins/index.js
+++ b/packages/dd-trace/src/plugins/index.js
@@ -43,6 +43,7 @@ module.exports = {
get dns () { return require('../../../datadog-plugin-dns/src') },
get 'dd-trace-api' () { return require('../../../datadog-plugin-dd-trace-api/src') },
get elasticsearch () { return require('../../../datadog-plugin-elasticsearch/src') },
+ get electron () { return require('../../../datadog-plugin-electron/src') },
get express () { return require('../../../datadog-plugin-express/src') },
get fastify () { return require('../../../datadog-plugin-fastify/src') },
get 'find-my-way' () { return require('../../../datadog-plugin-find-my-way/src') },
diff --git a/packages/dd-trace/src/profiling/profiler.js b/packages/dd-trace/src/profiling/profiler.js
index 8cdb55556be..aac8f816ed4 100644
--- a/packages/dd-trace/src/profiling/profiler.js
+++ b/packages/dd-trace/src/profiling/profiler.js
@@ -215,7 +215,7 @@ class Profiler extends EventEmitter {
this.#lastStart = start
if (!this.#timer || timeout !== this._timeoutInterval) {
this.#timer = setTimeout(() => this._collect(snapshotKinds.PERIODIC), timeout)
- this.#timer.unref()
+ this.#timer.unref?.()
} else {
this.#timer.refresh()
}
diff --git a/packages/dd-trace/src/profiling/profilers/wall.js b/packages/dd-trace/src/profiling/profilers/wall.js
index 859d5686867..aafb4b15cb5 100644
--- a/packages/dd-trace/src/profiling/profilers/wall.js
+++ b/packages/dd-trace/src/profiling/profilers/wall.js
@@ -219,7 +219,7 @@ class NativeWallProfiler {
asyncContextsLiveGauge.mark(totalAsyncContextCount)
asyncContextsUsedGauge.mark(usedAsyncContextCount)
}, this.#telemetryHeartbeatIntervalMillis)
- this._contextCountGaugeUpdater.unref()
+ this._contextCountGaugeUpdater.unref?.()
}
#enter () {
diff --git a/packages/dd-trace/src/profiling/ssi-heuristics.js b/packages/dd-trace/src/profiling/ssi-heuristics.js
index 994cf7d6a46..598eb5c5aa9 100644
--- a/packages/dd-trace/src/profiling/ssi-heuristics.js
+++ b/packages/dd-trace/src/profiling/ssi-heuristics.js
@@ -36,7 +36,7 @@ class SSIHeuristics {
setTimeout(() => {
this.shortLived = false
this._maybeTriggered()
- }, this.longLivedThreshold).unref()
+ }, this.longLivedThreshold).unref?.()
this._onSpanCreated = this._onSpanCreated.bind(this)
dc.subscribe('dd-trace:span:start', this._onSpanCreated)
diff --git a/packages/dd-trace/src/remote_config/scheduler.js b/packages/dd-trace/src/remote_config/scheduler.js
index 65460a1e4e3..1bb365e1d4a 100644
--- a/packages/dd-trace/src/remote_config/scheduler.js
+++ b/packages/dd-trace/src/remote_config/scheduler.js
@@ -17,7 +17,7 @@ class Scheduler {
runAfterDelay (interval = this._interval) {
this._timer = setTimeout(this._callback, interval, () => this.runAfterDelay())
- this._timer.unref && this._timer.unref()
+ this._timer.unref && this._timer.unref?.()
}
stop () {
diff --git a/packages/dd-trace/src/runtime_metrics/runtime_metrics.js b/packages/dd-trace/src/runtime_metrics/runtime_metrics.js
index fc9cb546adc..107911c2aa0 100644
--- a/packages/dd-trace/src/runtime_metrics/runtime_metrics.js
+++ b/packages/dd-trace/src/runtime_metrics/runtime_metrics.js
@@ -92,7 +92,7 @@ module.exports = {
}, INTERVAL)
}
- interval.unref()
+ interval.unref?.()
},
stop () {
diff --git a/packages/dd-trace/src/service-naming/schemas/v0/messaging.js b/packages/dd-trace/src/service-naming/schemas/v0/messaging.js
index 37b0a437c3a..e1f7a281bfc 100644
--- a/packages/dd-trace/src/service-naming/schemas/v0/messaging.js
+++ b/packages/dd-trace/src/service-naming/schemas/v0/messaging.js
@@ -24,6 +24,14 @@ const messaging = {
opName: () => 'azure.servicebus.send',
serviceName: ({ tracerService }) => `${tracerService}-azure-service-bus`
},
+ 'electron:ipc:main:send': {
+ opName: () => 'electron.main.send',
+ serviceName: identityService
+ },
+ 'electron:ipc:renderer:send': {
+ opName: () => 'electron.renderer.send',
+ serviceName: identityService
+ },
'google-cloud-pubsub': {
opName: () => 'pubsub.request',
serviceName: ({ tracerService }) => `${tracerService}-pubsub`
@@ -58,6 +66,14 @@ const messaging = {
opName: () => 'amqp.receive',
serviceName: amqpServiceName
},
+ 'electron:ipc:main:receive': {
+ opName: () => 'electron.main.receive',
+ serviceName: identityService
+ },
+ 'electron:ipc:renderer:receive': {
+ opName: () => 'electron.renderer.receive',
+ serviceName: identityService
+ },
'google-cloud-pubsub': {
opName: () => 'pubsub.receive',
serviceName: identityService
diff --git a/packages/dd-trace/src/service-naming/schemas/v0/web.js b/packages/dd-trace/src/service-naming/schemas/v0/web.js
index 23046f8ce8d..0adf615c74e 100644
--- a/packages/dd-trace/src/service-naming/schemas/v0/web.js
+++ b/packages/dd-trace/src/service-naming/schemas/v0/web.js
@@ -35,6 +35,10 @@ const web = {
undici: {
opName: () => 'undici.request',
serviceName: httpPluginClientService
+ },
+ 'electron:net:request': {
+ opName: () => 'http.request',
+ serviceName: httpPluginClientService
}
},
server: {
diff --git a/packages/dd-trace/src/service-naming/schemas/v1/messaging.js b/packages/dd-trace/src/service-naming/schemas/v1/messaging.js
index 7fa6fd9d72d..5a63ee24834 100644
--- a/packages/dd-trace/src/service-naming/schemas/v1/messaging.js
+++ b/packages/dd-trace/src/service-naming/schemas/v1/messaging.js
@@ -24,6 +24,14 @@ const messaging = {
opName: () => 'azure.eventhubs.send',
serviceName: identityService
},
+ 'electron:ipc:main:send': {
+ opName: () => 'electron.main.send',
+ serviceName: identityService
+ },
+ 'electron:ipc:renderer:send': {
+ opName: () => 'electron.renderer.send',
+ serviceName: identityService
+ },
'google-cloud-pubsub': {
opName: () => 'gcp.pubsub.send',
serviceName: identityService
@@ -49,6 +57,14 @@ const messaging = {
consumer: {
amqplib: amqpInbound,
amqp10: amqpInbound,
+ 'electron:ipc:main:receive': {
+ opName: () => 'electron.main.receive',
+ serviceName: identityService
+ },
+ 'electron:ipc:renderer:receive': {
+ opName: () => 'electron.renderer.receive',
+ serviceName: identityService
+ },
'google-cloud-pubsub': {
opName: () => 'gcp.pubsub.process',
serviceName: identityService
diff --git a/packages/dd-trace/src/service-naming/schemas/v1/web.js b/packages/dd-trace/src/service-naming/schemas/v1/web.js
index 66b1afee22f..d625122ecd2 100644
--- a/packages/dd-trace/src/service-naming/schemas/v1/web.js
+++ b/packages/dd-trace/src/service-naming/schemas/v1/web.js
@@ -35,6 +35,10 @@ const web = {
undici: {
opName: () => 'undici.request',
serviceName: httpPluginClientService
+ },
+ 'electron:net:request': {
+ opName: () => 'http.client.request',
+ serviceName: httpPluginClientService
}
},
server: {
diff --git a/packages/dd-trace/src/span_stats.js b/packages/dd-trace/src/span_stats.js
index 1bf0d378a6b..75cba51c9c1 100644
--- a/packages/dd-trace/src/span_stats.js
+++ b/packages/dd-trace/src/span_stats.js
@@ -149,7 +149,7 @@ class SpanStatsProcessor {
if (this.enabled) {
this.timer = setInterval(this.onInterval.bind(this), interval * 1e3)
- this.timer.unref()
+ this.timer.unref?.()
}
}
diff --git a/packages/dd-trace/src/supported-configurations.json b/packages/dd-trace/src/supported-configurations.json
index 3600de13c1e..bbe85397ed6 100644
--- a/packages/dd-trace/src/supported-configurations.json
+++ b/packages/dd-trace/src/supported-configurations.json
@@ -271,6 +271,7 @@
"DD_TRACE_ELASTIC_ELASTICSEARCH_ENABLED": ["A"],
"DD_TRACE_ELASTIC_TRANSPORT_ENABLED": ["A"],
"DD_TRACE_ELASTICSEARCH_ENABLED": ["A"],
+ "DD_TRACE_ELECTRON_ENABLED": ["A"],
"DD_TRACE_ENABLED": ["A"],
"DD_TRACE_ENCODING_DEBUG": ["A"],
"DD_TRACE_EXPERIMENTAL_B3_ENABLED": ["A"],
diff --git a/packages/dd-trace/src/telemetry/dependencies.js b/packages/dd-trace/src/telemetry/dependencies.js
index 27c2f707921..42b0c9c2e28 100644
--- a/packages/dd-trace/src/telemetry/dependencies.js
+++ b/packages/dd-trace/src/telemetry/dependencies.js
@@ -75,7 +75,7 @@ function waitAndSend (config, application, host) {
if (savedDependenciesToSend.size > 0) {
waitAndSend(config, application, host)
}
- }).unref()
+ }).unref?.()
}
function loadAllTheLoadedModules () {
diff --git a/packages/dd-trace/src/telemetry/endpoints.js b/packages/dd-trace/src/telemetry/endpoints.js
index 60db5004008..a08d1d65637 100644
--- a/packages/dd-trace/src/telemetry/endpoints.js
+++ b/packages/dd-trace/src/telemetry/endpoints.js
@@ -29,7 +29,7 @@ function endpointKey (method, path) {
function scheduleFlush () {
if (flushScheduled) return
flushScheduled = true
- setImmediate(flushAndSend).unref()
+ setImmediate(flushAndSend).unref?.()
}
function recordEndpoint (method, path) {
diff --git a/packages/dd-trace/src/telemetry/telemetry.js b/packages/dd-trace/src/telemetry/telemetry.js
index 55e6f1d6ae6..f39801c3c53 100644
--- a/packages/dd-trace/src/telemetry/telemetry.js
+++ b/packages/dd-trace/src/telemetry/telemetry.js
@@ -221,7 +221,7 @@ function heartbeat (config, application, host) {
const { reqType, payload } = createPayload('app-heartbeat')
sendData(config, application, host, reqType, payload, updateRetryData)
heartbeat(config, application, host)
- }, heartbeatInterval).unref()
+ }, heartbeatInterval).unref?.()
return heartbeatTimeout
}
@@ -234,7 +234,7 @@ function extendedHeartbeat (config) {
}
sendData(config, application, host, 'app-extended-heartbeat', payload)
Object.keys(extendedHeartbeatPayload).forEach(key => delete extendedHeartbeatPayload[key])
- }, 1000 * 60 * 60 * 24).unref()
+ }, 1000 * 60 * 60 * 24).unref?.()
return extendedInterval
}
diff --git a/packages/dd-trace/test/plugins/agent.js b/packages/dd-trace/test/plugins/agent.js
index 299b2cd63f2..7331bca3b76 100644
--- a/packages/dd-trace/test/plugins/agent.js
+++ b/packages/dd-trace/test/plugins/agent.js
@@ -487,7 +487,7 @@ module.exports = {
const promise = /** @type {Promise} */ (new Promise((resolve, _reject) => {
listener = server.listen(0, () => {
- const port = listener.address().port
+ const port = this.port = listener.address().port
tracer.init(Object.assign({}, {
service: 'test',
@@ -671,6 +671,7 @@ module.exports = {
return /** @type {Promise} */ (new Promise((resolve, reject) => {
this.server.on('close', () => {
this.server = null
+ this.port = null
resolve()
})
diff --git a/packages/dd-trace/test/plugins/versions/package.json b/packages/dd-trace/test/plugins/versions/package.json
index 00b221d3f86..fb417ebd25c 100644
--- a/packages/dd-trace/test/plugins/versions/package.json
+++ b/packages/dd-trace/test/plugins/versions/package.json
@@ -96,6 +96,7 @@
"dd-trace-api": "1.0.0",
"ejs": "3.1.10",
"elasticsearch": "16.7.3",
+ "electron": "39.2.4",
"esbuild": "0.27.0",
"express": "5.1.0",
"express-mongo-sanitize": "2.2.0",