Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new initializer file #4334

Merged
merged 2 commits into from
May 23, 2024
Merged
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
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
!index.js
!esbuild.js
!init.js
!initialize.mjs
!loader-hook.mjs
!register.js
!package.json
Expand Down
50 changes: 50 additions & 0 deletions initialize.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* This file serves one of two purposes, depending on how it's used.
*
* If used with --import, it will import init.js and register the loader hook.
* If used with --loader, it will act as the loader hook, except that it will
* also import init.js inside the source code of the entrypoint file.
*
* The result is that no matter how this file is used, so long as it's with
* one of the two flags, the tracer will always be initialized, and the loader
* hook will always be active for ESM support.
*/

import { isMainThread } from 'worker_threads'
import { register } from 'node:module';

import { fileURLToPath } from 'node:url'
import {
load as origLoad,
resolve as origResolve,
getFormat as origGetFormat,
getSource as origGetSource
} from 'import-in-the-middle/hook.mjs'

let hasInsertedInit = false
function insertInit (result) {
if (!hasInsertedInit) {
hasInsertedInit = true
result.source = `
import '${fileURLToPath(new URL('./init.js', import.meta.url))}';
${result.source}`
}
return result
}

export async function load (...args) {
return insertInit(await origLoad(...args))
}

export const resolve = origResolve

export const getFormat = origGetFormat

export async function getSource (...args) {
return insertInit(await origGetSource(...args))
}

if (isMainThread) {
await import('./init.js')
register('./loader-hook.mjs', import.meta.url)
}
4 changes: 2 additions & 2 deletions integration-tests/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,12 @@ function spawnProc (filename, options = {}, stdioHandler) {
stdioHandler(data)
}
// eslint-disable-next-line no-console
console.log(data.toString())
if (!options.silent) console.log(data.toString())
})

proc.stderr.on('data', data => {
// eslint-disable-next-line no-console
console.error(data.toString())
if (!options.silent) console.error(data.toString())
})
})
}
Expand Down
112 changes: 80 additions & 32 deletions integration-tests/init.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,99 @@ const path = require('path')

const DD_INJECTION_ENABLED = 'tracing'

describe('init.js', () => {
let cwd, proc, sandbox

async function runTest (cwd, env, expected) {
return new Promise((resolve, reject) => {
spawnProc(path.join(cwd, 'init/index.js'), { cwd, env }, data => {
try {
assert.strictEqual(data.toString(), expected)
resolve()
} catch (e) {
reject(e)
}
}).then(subproc => {
proc = subproc
})
})
}
let cwd, proc, sandbox

before(async () => {
sandbox = await createSandbox()
cwd = sandbox.folder
})
afterEach(() => {
proc && proc.kill()
})
after(() => {
return sandbox.remove()
async function runTest (cwd, file, env, expected) {
return new Promise((resolve, reject) => {
spawnProc(path.join(cwd, file), { cwd, env, silent: true }, data => {
try {
assert.strictEqual(data.toString(), expected)
resolve()
} catch (e) {
reject(e)
}
}).then(subproc => {
proc = subproc
})
})
}

function testInjectionScenarios (arg, filename, esmWorks = false) {
context('when dd-trace is not in the app dir', () => {
const NODE_OPTIONS = `--require ${path.join(__dirname, '..', 'init.js')}`
const NODE_OPTIONS = `--no-warnings --${arg} ${path.join(__dirname, '..', filename)}`
it('should initialize the tracer, if no DD_INJECTION_ENABLED', () => {
return runTest(cwd, { NODE_OPTIONS }, 'true\n')
return runTest(cwd, 'init/trace.js', { NODE_OPTIONS }, 'true\n')
})
it('should not initialize the tracer, if DD_INJECTION_ENABLED', () => {
return runTest(cwd, { NODE_OPTIONS, DD_INJECTION_ENABLED }, 'false\n')
return runTest(cwd, 'init/trace.js', { NODE_OPTIONS, DD_INJECTION_ENABLED }, 'false\n')
})
it('should initialize instrumentation, if no DD_INJECTION_ENABLED', () => {
return runTest(cwd, 'init/instrument.js', { NODE_OPTIONS }, 'true\n')
})
it('should not initialize instrumentation, if DD_INJECTION_ENABLED', () => {
return runTest(cwd, 'init/instrument.js', { NODE_OPTIONS, DD_INJECTION_ENABLED }, 'false\n')
})
it(`should ${esmWorks ? '' : 'not '}initialize ESM instrumentation, if no DD_INJECTION_ENABLED`, () => {
return runTest(cwd, 'init/instrument.mjs', { NODE_OPTIONS }, `${esmWorks}\n`)
})
it('should not initialize ESM instrumentation, if DD_INJECTION_ENABLED', () => {
return runTest(cwd, 'init/instrument.mjs', { NODE_OPTIONS, DD_INJECTION_ENABLED }, 'false\n')
})
})
context('when dd-trace in the app dir', () => {
const NODE_OPTIONS = '--require dd-trace/init.js'
const NODE_OPTIONS = `--no-warnings --${arg} dd-trace/${filename}`
it('should initialize the tracer, if no DD_INJECTION_ENABLED', () => {
return runTest(cwd, { NODE_OPTIONS }, 'true\n')
return runTest(cwd, 'init/trace.js', { NODE_OPTIONS }, 'true\n')
})
it('should initialize the tracer, if DD_INJECTION_ENABLED', () => {
return runTest(cwd, { NODE_OPTIONS, DD_INJECTION_ENABLED }, 'true\n')
return runTest(cwd, 'init/trace.js', { NODE_OPTIONS, DD_INJECTION_ENABLED }, 'true\n')
})
it('should initialize instrumentation, if no DD_INJECTION_ENABLED', () => {
return runTest(cwd, 'init/instrument.js', { NODE_OPTIONS }, 'true\n')
})
it('should initialize instrumentation, if DD_INJECTION_ENABLED', () => {
return runTest(cwd, 'init/instrument.js', { NODE_OPTIONS, DD_INJECTION_ENABLED }, 'true\n')
})
it(`should ${esmWorks ? '' : 'not '}initialize ESM instrumentation, if no DD_INJECTION_ENABLED`, () => {
return runTest(cwd, 'init/instrument.mjs', { NODE_OPTIONS }, `${esmWorks}\n`)
})
it(`should ${esmWorks ? '' : 'not '}initialize ESM instrumentation, if DD_INJECTION_ENABLED`, () => {
return runTest(cwd, 'init/instrument.mjs', { NODE_OPTIONS, DD_INJECTION_ENABLED }, `${esmWorks}\n`)
})
})
}

describe('init.js', () => {
before(async () => {
sandbox = await createSandbox()
cwd = sandbox.folder
})
afterEach(() => {
proc && proc.kill()
})
after(() => {
return sandbox.remove()
})

testInjectionScenarios('require', 'init.js', false)
})

describe('initialize.mjs', () => {
before(async () => {
sandbox = await createSandbox()
cwd = sandbox.folder
})
afterEach(() => {
proc && proc.kill()
})
after(() => {
return sandbox.remove()
})

context('as --loader', () => {
testInjectionScenarios('loader', 'initialize.mjs', true)
})
context('as --import', () => {
testInjectionScenarios('import', 'initialize.mjs', true)
})
})
21 changes: 21 additions & 0 deletions integration-tests/init/instrument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const http = require('http')
const dc = require('dc-polyfill')

let gotEvent = false
dc.subscribe('apm:http:client:request:start', (event) => {
gotEvent = true
})

const server = http.createServer((req, res) => {
res.end('Hello World')
}).listen(0, () => {
http.get(`http://localhost:${server.address().port}`, (res) => {
res.on('data', () => {})
res.on('end', () => {
server.close()
// eslint-disable-next-line no-console
console.log(gotEvent)
process.exit()
})
})
})
21 changes: 21 additions & 0 deletions integration-tests/init/instrument.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import http from 'http'
import dc from 'dc-polyfill'

let gotEvent = false
dc.subscribe('apm:http:client:request:start', (event) => {
gotEvent = true
})

const server = http.createServer((req, res) => {
res.end('Hello World')
}).listen(0, () => {
http.get(`http://localhost:${server.address().port}`, (res) => {
res.on('data', () => {})
res.on('end', () => {
server.close()
// eslint-disable-next-line no-console
console.log(gotEvent)
process.exit()
})
})
})
File renamed without changes.
Loading