Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
9 changes: 9 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
67 changes: 67 additions & 0 deletions packages/datadog-instrumentations/src/electron.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use strict'

const shimmer = require('../../datadog-shimmer')
const { createWrapFetch } = require('./helpers/fetch')
const { addHook, tracingChannel } = require('./helpers/instrument')

const fetchCh = tracingChannel('apm:electron:net:fetch')
const requestCh = tracingChannel('apm:electron:net:request')

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)
}
})
}
}
}

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

shimmer.wrap(electron.net, 'fetch', createWrapFetch(globalThis.Request, fetchCh))
shimmer.wrap(electron.net, 'request', createWrapRequest(requestCh))

return electron
})
1 change: 1 addition & 0 deletions packages/datadog-instrumentations/src/helpers/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
91 changes: 91 additions & 0 deletions packages/datadog-plugin-electron/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use strict'

const HttpClientPlugin = require('../../datadog-plugin-http/src/client')
const CompositePlugin = require('../../dd-trace/src/plugins/composite')

class ElectronPlugin extends CompositePlugin {
static id = 'electron'
static get plugins () {
return {
net: ElectronNetPlugin
}
}
}

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 = ElectronPlugin
35 changes: 35 additions & 0 deletions packages/datadog-plugin-electron/test/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict'

/* eslint-disable no-console */

const { app, net } = require('electron')

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)
}
} catch (e) {
console.error(e)
}
})
})

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()
}
111 changes: 111 additions & 0 deletions packages/datadog-plugin-electron/test/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
'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')], {
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}/` })
})
})
})
})
})
14 changes: 14 additions & 0 deletions packages/datadog-plugin-electron/test/tracer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict'

const port = process.env.DD_TRACE_AGENT_PORT

require('../../dd-trace')
.init({
service: 'test',
env: 'tester',
port,
flushInterval: 0,
plugins: false
})
.use('electron', true)
.setUrl(`http://127.0.0.1:${port}`)
4 changes: 2 additions & 2 deletions packages/datadog-plugin-http/src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/dd-trace/src/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') },
Expand Down
4 changes: 4 additions & 0 deletions packages/dd-trace/src/service-naming/schemas/v0/web.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ const web = {
undici: {
opName: () => 'undici.request',
serviceName: httpPluginClientService
},
'electron:net:request': {
opName: () => 'http.request',
serviceName: httpPluginClientService
}
},
server: {
Expand Down
Loading