From 68a0690593c44922b52159d549bbf35fc5c48979 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Fri, 21 Nov 2025 10:36:18 -0500 Subject: [PATCH 1/3] Mimicked iitm builtin module behaviorin ritm --- .../src/child_process.js | 22 +++--- .../datadog-instrumentations/src/crypto.js | 3 +- packages/datadog-instrumentations/src/dns.js | 3 +- packages/datadog-instrumentations/src/fs.js | 55 ++++++++------- .../src/helpers/hooks.js | 31 ++++----- .../src/http/client.js | 2 +- .../src/http/server.js | 7 +- .../src/http2/client.js | 4 +- .../src/http2/server.js | 4 +- packages/datadog-instrumentations/src/net.js | 4 +- packages/datadog-instrumentations/src/url.js | 3 +- packages/datadog-instrumentations/src/vm.js | 3 +- packages/dd-trace/src/ritm.js | 68 ++++++++++++++----- packages/dd-trace/test/ritm.spec.js | 15 +++- 14 files changed, 122 insertions(+), 102 deletions(-) diff --git a/packages/datadog-instrumentations/src/child_process.js b/packages/datadog-instrumentations/src/child_process.js index 71c199b7991..d2e3fa69937 100644 --- a/packages/datadog-instrumentations/src/child_process.js +++ b/packages/datadog-instrumentations/src/child_process.js @@ -14,8 +14,6 @@ const childProcessChannel = dc.tracingChannel('datadog:child_process:execution') // ignored exec method because it calls to execFile directly const execAsyncMethods = ['execFile', 'spawn'] -const names = ['child_process', 'node:child_process'] - // child_process and node:child_process returns the same object instance, we only want to add hooks once let patched = false @@ -37,18 +35,16 @@ function returnSpawnSyncError (error, context) { return context.result } -names.forEach(name => { - addHook({ name }, childProcess => { - if (!patched) { - patched = true - shimmer.massWrap(childProcess, execAsyncMethods, wrapChildProcessAsyncMethod(childProcess.ChildProcess)) - shimmer.wrap(childProcess, 'execSync', wrapChildProcessSyncMethod(throwSyncError, true)) - shimmer.wrap(childProcess, 'execFileSync', wrapChildProcessSyncMethod(throwSyncError)) - shimmer.wrap(childProcess, 'spawnSync', wrapChildProcessSyncMethod(returnSpawnSyncError)) - } +addHook({ name: 'child_process' }, childProcess => { + if (!patched) { + patched = true + shimmer.massWrap(childProcess, execAsyncMethods, wrapChildProcessAsyncMethod(childProcess.ChildProcess)) + shimmer.wrap(childProcess, 'execSync', wrapChildProcessSyncMethod(throwSyncError, true)) + shimmer.wrap(childProcess, 'execFileSync', wrapChildProcessSyncMethod(throwSyncError)) + shimmer.wrap(childProcess, 'spawnSync', wrapChildProcessSyncMethod(returnSpawnSyncError)) + } - return childProcess - }) + return childProcess }) function normalizeArgs (args, shell) { diff --git a/packages/datadog-instrumentations/src/crypto.js b/packages/datadog-instrumentations/src/crypto.js index 7c95614cee7..3113c16ef1d 100644 --- a/packages/datadog-instrumentations/src/crypto.js +++ b/packages/datadog-instrumentations/src/crypto.js @@ -11,9 +11,8 @@ const cryptoCipherCh = channel('datadog:crypto:cipher:start') const hashMethods = ['createHash', 'createHmac', 'createSign', 'createVerify', 'sign', 'verify'] const cipherMethods = ['createCipheriv', 'createDecipheriv'] -const names = ['crypto', 'node:crypto'] -addHook({ name: names }, crypto => { +addHook({ name: 'crypto' }, crypto => { shimmer.massWrap(crypto, hashMethods, wrapCryptoMethod(cryptoHashCh)) shimmer.massWrap(crypto, cipherMethods, wrapCryptoMethod(cryptoCipherCh)) return crypto diff --git a/packages/datadog-instrumentations/src/dns.js b/packages/datadog-instrumentations/src/dns.js index ac37784c58d..fe0e73804f6 100644 --- a/packages/datadog-instrumentations/src/dns.js +++ b/packages/datadog-instrumentations/src/dns.js @@ -18,9 +18,8 @@ const rrtypes = { } const rrtypeMap = new WeakMap() -const names = ['dns', 'node:dns'] -addHook({ name: names }, dns => { +addHook({ name: 'dns' }, dns => { shimmer.wrap(dns, 'lookup', fn => wrap('apm:dns:lookup', fn, 2)) shimmer.wrap(dns, 'lookupService', fn => wrap('apm:dns:lookup_service', fn, 2)) shimmer.wrap(dns, 'resolve', fn => wrap('apm:dns:resolve', fn, 2)) diff --git a/packages/datadog-instrumentations/src/fs.js b/packages/datadog-instrumentations/src/fs.js index e284fee4d62..b691cd6be10 100644 --- a/packages/datadog-instrumentations/src/fs.js +++ b/packages/datadog-instrumentations/src/fs.js @@ -84,37 +84,36 @@ const paramsByFileHandleMethods = { writeFile: ['data', 'options'], writev: ['buffers', 'position'] } -const names = ['fs', 'node:fs'] -names.forEach(name => { - addHook({ name }, fs => { - const asyncMethods = Object.keys(paramsByMethod) - const syncMethods = asyncMethods.map(name => `${name}Sync`) - - massWrap(fs, asyncMethods, createWrapFunction()) - massWrap(fs, syncMethods, createWrapFunction()) - massWrap(fs.promises, asyncMethods, createWrapFunction('promises.')) - - wrap(fs.realpath, 'native', createWrapFunction('', 'realpath.native')) - wrap(fs.realpathSync, 'native', createWrapFunction('', 'realpath.native')) - wrap(fs.promises.realpath, 'native', createWrapFunction('', 'realpath.native')) - - wrap(fs, 'createReadStream', wrapCreateStream) - wrap(fs, 'createWriteStream', wrapCreateStream) - if (fs.Dir) { - wrap(fs.Dir.prototype, 'close', createWrapFunction('dir.')) - wrap(fs.Dir.prototype, 'closeSync', createWrapFunction('dir.')) - wrap(fs.Dir.prototype, 'read', createWrapFunction('dir.')) - wrap(fs.Dir.prototype, 'readSync', createWrapFunction('dir.')) - wrap(fs.Dir.prototype, Symbol.asyncIterator, createWrapDirAsyncIterator()) - } - wrap(fs, 'unwatchFile', createWatchWrapFunction()) - wrap(fs, 'watch', createWatchWrapFunction()) - wrap(fs, 'watchFile', createWatchWrapFunction()) +addHook({ name: 'fs' }, fs => { + const asyncMethods = Object.keys(paramsByMethod) + const syncMethods = asyncMethods.map(name => `${name}Sync`) + + massWrap(fs, asyncMethods, createWrapFunction()) + massWrap(fs, syncMethods, createWrapFunction()) + massWrap(fs.promises, asyncMethods, createWrapFunction('promises.')) + + wrap(fs.realpath, 'native', createWrapFunction('', 'realpath.native')) + wrap(fs.realpathSync, 'native', createWrapFunction('', 'realpath.native')) + wrap(fs.promises.realpath, 'native', createWrapFunction('', 'realpath.native')) + + wrap(fs, 'createReadStream', wrapCreateStream) + wrap(fs, 'createWriteStream', wrapCreateStream) + if (fs.Dir) { + wrap(fs.Dir.prototype, 'close', createWrapFunction('dir.')) + wrap(fs.Dir.prototype, 'closeSync', createWrapFunction('dir.')) + wrap(fs.Dir.prototype, 'read', createWrapFunction('dir.')) + wrap(fs.Dir.prototype, 'readSync', createWrapFunction('dir.')) + wrap(fs.Dir.prototype, Symbol.asyncIterator, createWrapDirAsyncIterator()) + } + + wrap(fs, 'unwatchFile', createWatchWrapFunction()) + wrap(fs, 'watch', createWatchWrapFunction()) + wrap(fs, 'watchFile', createWatchWrapFunction()) - return fs - }) + return fs }) + function isFirstMethodReturningFileHandle (original) { return !kHandle && original.name === 'open' } diff --git a/packages/datadog-instrumentations/src/helpers/hooks.js b/packages/datadog-instrumentations/src/helpers/hooks.js index c46827b3537..bb4d70d6189 100644 --- a/packages/datadog-instrumentations/src/helpers/hooks.js +++ b/packages/datadog-instrumentations/src/helpers/hooks.js @@ -1,6 +1,18 @@ 'use strict' module.exports = { + // Only list unprefixed node modules. They will automatically be instrumented as prefixed and unprefixed. + child_process: () => require('../child_process'), + crypto: () => require('../crypto'), + dns: () => require('../dns'), + fs: { serverless: false, fn: () => require('../fs') }, + http: () => require('../http'), + http2: () => require('../http2'), + https: () => require('../http'), + net: () => require('../net'), + url: () => require('../url'), + vm: () => require('../vm'), + // Non Node.js modules '@anthropic-ai/sdk': { esmFirst: true, fn: () => require('../anthropic') }, '@apollo/server': () => require('../apollo-server'), '@apollo/gateway': () => require('../apollo'), @@ -42,31 +54,24 @@ module.exports = { 'body-parser': () => require('../body-parser'), bunyan: () => require('../bunyan'), 'cassandra-driver': () => require('../cassandra-driver'), - child_process: () => require('../child_process'), connect: () => require('../connect'), cookie: () => require('../cookie'), 'cookie-parser': () => require('../cookie-parser'), couchbase: () => require('../couchbase'), - crypto: () => require('../crypto'), cypress: () => require('../cypress'), 'dd-trace-api': () => require('../dd-trace-api'), - dns: () => require('../dns'), elasticsearch: () => require('../elasticsearch'), express: () => require('../express'), 'express-mongo-sanitize': () => require('../express-mongo-sanitize'), 'express-session': () => require('../express-session'), fastify: () => require('../fastify'), 'find-my-way': () => require('../find-my-way'), - fs: { serverless: false, fn: () => require('../fs') }, 'generic-pool': () => require('../generic-pool'), graphql: () => require('../graphql'), grpc: () => require('../grpc'), handlebars: () => require('../handlebars'), hapi: () => require('../hapi'), hono: { esmFirst: true, fn: () => require('../hono') }, - http: () => require('../http'), - http2: () => require('../http2'), - https: () => require('../http'), ioredis: () => require('../ioredis'), iovalkey: () => require('../iovalkey'), 'jest-circus': () => require('../jest'), @@ -97,18 +102,8 @@ module.exports = { multer: () => require('../multer'), mysql: () => require('../mysql'), mysql2: () => require('../mysql2'), - net: () => require('../net'), next: () => require('../next'), 'node-serialize': () => require('../node-serialize'), - 'node:child_process': () => require('../child_process'), - 'node:crypto': () => require('../crypto'), - 'node:dns': () => require('../dns'), - 'node:http': () => require('../http'), - 'node:http2': () => require('../http2'), - 'node:https': () => require('../http'), - 'node:net': () => require('../net'), - 'node:url': () => require('../url'), - 'node:vm': () => require('../vm'), nyc: () => require('../nyc'), oracledb: () => require('../oracledb'), openai: { esmFirst: true, fn: () => require('../openai') }, @@ -135,9 +130,7 @@ module.exports = { tedious: () => require('../tedious'), tinypool: { esmFirst: true, fn: () => require('../vitest') }, undici: () => require('../undici'), - url: () => require('../url'), vitest: { esmFirst: true, fn: () => require('../vitest') }, - vm: () => require('../vm'), when: () => require('../when'), winston: () => require('../winston'), workerpool: () => require('../mocha'), diff --git a/packages/datadog-instrumentations/src/http/client.js b/packages/datadog-instrumentations/src/http/client.js index 508675692cc..c43994889ad 100644 --- a/packages/datadog-instrumentations/src/http/client.js +++ b/packages/datadog-instrumentations/src/http/client.js @@ -15,7 +15,7 @@ const endChannel = channel('apm:http:client:request:end') const asyncStartChannel = channel('apm:http:client:request:asyncStart') const errorChannel = channel('apm:http:client:request:error') -const names = ['http', 'https', 'node:http', 'node:https'] +const names = ['http', 'https'] addHook({ name: names }, hookFn) diff --git a/packages/datadog-instrumentations/src/http/server.js b/packages/datadog-instrumentations/src/http/server.js index 0624c886787..bf1ce86eec8 100644 --- a/packages/datadog-instrumentations/src/http/server.js +++ b/packages/datadog-instrumentations/src/http/server.js @@ -16,10 +16,7 @@ const startSetHeaderCh = channel('datadog:http:server:response:set-header:start' const requestFinishedSet = new WeakSet() -const httpNames = ['http', 'node:http'] -const httpsNames = ['https', 'node:https'] - -addHook({ name: httpNames }, http => { +addHook({ name: 'http' }, http => { shimmer.wrap(http.ServerResponse.prototype, 'emit', wrapResponseEmit) shimmer.wrap(http.Server.prototype, 'emit', wrapEmit) shimmer.wrap(http.ServerResponse.prototype, 'writeHead', wrapWriteHead) @@ -34,7 +31,7 @@ addHook({ name: httpNames }, http => { return http }) -addHook({ name: httpsNames }, http => { +addHook({ name: 'https' }, http => { // http.ServerResponse not present on https shimmer.wrap(http.Server.prototype, 'emit', wrapEmit) return http diff --git a/packages/datadog-instrumentations/src/http2/client.js b/packages/datadog-instrumentations/src/http2/client.js index b335df7c4cd..d38837fb2d9 100644 --- a/packages/datadog-instrumentations/src/http2/client.js +++ b/packages/datadog-instrumentations/src/http2/client.js @@ -10,8 +10,6 @@ const asyncStartChannel = channel('apm:http2:client:request:asyncStart') const asyncEndChannel = channel('apm:http2:client:request:asyncEnd') const errorChannel = channel('apm:http2:client:request:error') -const names = ['http2', 'node:http2'] - function createWrapEmit (ctx) { return function wrapEmit (emit) { return function (event, arg1) { @@ -68,7 +66,7 @@ function wrapConnect (connect) { } } -addHook({ name: names }, http2 => { +addHook({ name: 'http2' }, http2 => { shimmer.wrap(http2, 'connect', wrapConnect) if (http2.default) http2.default.connect = http2.connect diff --git a/packages/datadog-instrumentations/src/http2/server.js b/packages/datadog-instrumentations/src/http2/server.js index 5977d738554..5285f582d6c 100644 --- a/packages/datadog-instrumentations/src/http2/server.js +++ b/packages/datadog-instrumentations/src/http2/server.js @@ -13,9 +13,7 @@ const startServerCh = channel('apm:http2:server:request:start') const errorServerCh = channel('apm:http2:server:request:error') const emitCh = channel('apm:http2:server:response:emit') -const names = ['http2', 'node:http2'] - -addHook({ name: names }, http2 => { +addHook({ name: 'http2' }, http2 => { shimmer.wrap(http2, 'createSecureServer', wrapCreateServer) shimmer.wrap(http2, 'createServer', wrapCreateServer) }) diff --git a/packages/datadog-instrumentations/src/net.js b/packages/datadog-instrumentations/src/net.js index b0b6163efc7..8a1c0ffa7f2 100644 --- a/packages/datadog-instrumentations/src/net.js +++ b/packages/datadog-instrumentations/src/net.js @@ -16,9 +16,7 @@ const errorTCPCh = channel('apm:net:tcp:error') const readyCh = channel('apm:net:tcp:ready') const connectionCh = channel('apm:net:tcp:connection') -const names = ['net', 'node:net'] - -addHook({ name: names }, (net, version, name) => { +addHook({ name: 'net' }, (net, version, name) => { // explicitly require dns so that net gets an instrumented instance // so that we don't miss the dns calls if (name === 'net') { diff --git a/packages/datadog-instrumentations/src/url.js b/packages/datadog-instrumentations/src/url.js index 57248099b4e..407f683ef5c 100644 --- a/packages/datadog-instrumentations/src/url.js +++ b/packages/datadog-instrumentations/src/url.js @@ -2,13 +2,12 @@ const { addHook, channel } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') -const names = ['url', 'node:url'] const parseFinishedChannel = channel('datadog:url:parse:finish') const urlGetterChannel = channel('datadog:url:getter:finish') const instrumentedGetters = ['host', 'origin', 'hostname'] -addHook({ name: names }, function (url) { +addHook({ name: 'url' }, function (url) { shimmer.wrap(url, 'parse', (parse) => { return function wrappedParse (input) { const parsedValue = parse.apply(this, arguments) diff --git a/packages/datadog-instrumentations/src/vm.js b/packages/datadog-instrumentations/src/vm.js index 9df229556fa..2e4a3b7e625 100644 --- a/packages/datadog-instrumentations/src/vm.js +++ b/packages/datadog-instrumentations/src/vm.js @@ -2,12 +2,11 @@ const { channel, addHook } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') -const names = ['vm', 'node:vm'] const runScriptStartChannel = channel('datadog:vm:run-script:start') const sourceTextModuleStartChannel = channel('datadog:vm:source-text-module:start') -addHook({ name: names }, function (vm) { +addHook({ name: 'vm' }, function (vm) { vm.Script = class extends vm.Script { constructor (code) { super(...arguments) diff --git a/packages/dd-trace/src/ritm.js b/packages/dd-trace/src/ritm.js index 07c03c84468..492ee872c81 100644 --- a/packages/dd-trace/src/ritm.js +++ b/packages/dd-trace/src/ritm.js @@ -8,6 +8,39 @@ const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const origRequire = Module.prototype.require +function stripNodeProtocol (name) { + if (typeof name !== 'string') return name + return name.startsWith('node:') ? name.slice(5) : name +} + +const builtinModules = new Set(Module.builtinModules.map(stripNodeProtocol)) + +function isBuiltinModuleName (name) { + if (typeof name !== 'string') return false + return builtinModules.has(stripNodeProtocol(name)) +} + +function normalizeModuleName (name) { + if (typeof name !== 'string') return name + const stripped = stripNodeProtocol(name) + return builtinModules.has(stripped) ? stripped : name +} + +function normalizeModulesList (modules) { + const normalized = [] + const seen = new Set() + + for (const mod of modules) { + const normalizedName = normalizeModuleName(mod) + if (typeof normalizedName !== 'string') continue + if (seen.has(normalizedName)) continue + seen.add(normalizedName) + normalized.push(normalizedName) + } + + return normalized +} + // derived from require-in-the-middle@3 with tweaks module.exports = Hook @@ -30,15 +63,15 @@ function Hook (modules, options, onrequire) { options = {} } - modules = modules || [] options = options || {} + const normalizedModules = Array.isArray(modules) ? normalizeModulesList(modules) : [] - this.modules = modules + this.modules = normalizedModules this.options = options this.onrequire = onrequire if (Array.isArray(modules)) { - for (const mod of modules) { + for (const mod of normalizedModules) { const hooks = moduleHooks[mod] if (hooks) { @@ -65,26 +98,27 @@ function Hook (modules, options, onrequire) { return _origRequire.apply(this, arguments) } - const core = !filename.includes(path.sep) + const builtin = isBuiltinModuleName(filename) + const moduleId = builtin ? normalizeModuleName(filename) : filename let name, basedir, hooks // return known patched modules immediately - if (cache[filename]) { + if (cache[moduleId]) { // require.cache was potentially altered externally - if (require.cache[filename] && require.cache[filename].exports !== cache[filename].original) { + if (require.cache[filename] && require.cache[filename].exports !== cache[moduleId].original) { return require.cache[filename].exports } - return cache[filename].exports + return cache[moduleId].exports } // Check if this module has a patcher in-progress already. // Otherwise, mark this module as patching in-progress. - const patched = patching[filename] + const patched = patching[moduleId] if (patched) { // If it's already patched, just return it as-is. return origRequire.apply(this, arguments) } - patching[filename] = true + patching[moduleId] = true const payload = { filename, @@ -103,12 +137,12 @@ function Hook (modules, options, onrequire) { // The module has already been loaded, // so the patching mark can be cleaned up. - delete patching[filename] + delete patching[moduleId] - if (core) { - hooks = moduleHooks[filename] + if (builtin) { + hooks = moduleHooks[moduleId] if (!hooks) return exports // abort if module name isn't on whitelist - name = filename + name = moduleId } else { const inAWSLambda = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined const hasLambdaHandler = getEnvironmentVariable('DD_LAMBDA_HANDLER') !== undefined @@ -148,14 +182,14 @@ function Hook (modules, options, onrequire) { // ensure that the cache entry is assigned a value before calling // onrequire, in case calling onrequire requires the same module. - cache[filename] = { exports } - cache[filename].original = exports + cache[moduleId] = { exports } + cache[moduleId].original = exports for (const hook of hooks) { - cache[filename].exports = hook(cache[filename].exports, name, basedir) + cache[moduleId].exports = hook(cache[moduleId].exports, name, basedir) } - return cache[filename].exports + return cache[moduleId].exports } } diff --git a/packages/dd-trace/test/ritm.spec.js b/packages/dd-trace/test/ritm.spec.js index 1fb642dc46c..7d8984c3068 100644 --- a/packages/dd-trace/test/ritm.spec.js +++ b/packages/dd-trace/test/ritm.spec.js @@ -13,7 +13,7 @@ const Hook = require('../src/ritm') describe('Ritm', () => { let moduleLoadStartChannel, moduleLoadEndChannel, startListener, endListener - let utilHook, aHook, bHook, httpHook + let utilHook, aHook, bHook, httpHook, httpOnRequire before(() => { moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart') @@ -43,10 +43,11 @@ describe('Ritm', () => { utilHook = Hook('util') aHook = Hook('module-a') bHook = Hook('module-b') - httpHook = new Hook(['http'], function onRequire (exports, name, basedir) { + httpOnRequire = sinon.spy(function onRequire (exports, name, basedir) { exports.foo = 1 return exports }) + httpHook = new Hook(['http'], httpOnRequire) }) afterEach(() => { @@ -101,4 +102,14 @@ describe('Ritm', () => { 'a failing `require(...)` can still throw as expected' ) }) + + it('should hook node: prefixed builtins via canonical registrations', () => { + const nodeHttp = require('node:http') + assert.equal(nodeHttp.foo, 1) + assert.equal(httpOnRequire.callCount, 1, 'onrequire should run only once') + + const canonicalHttp = require('http') + assert.equal(httpOnRequire.callCount, 1, 'cache should be shared between node: and canonical names') + assert.strictEqual(nodeHttp, canonicalHttp) + }) }) From e13ec1d23b3c0e5cbf9d0433db1b99c06e6a0638 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Fri, 21 Nov 2025 12:02:07 -0500 Subject: [PATCH 2/3] fixed esbuild issues --- packages/datadog-esbuild/index.js | 42 +++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/packages/datadog-esbuild/index.js b/packages/datadog-esbuild/index.js index 97a318ac3ba..ea30ef05662 100644 --- a/packages/datadog-esbuild/index.js +++ b/packages/datadog-esbuild/index.js @@ -24,19 +24,47 @@ for (const hook of Object.values(hooks)) { } } +const RAW_BUILTINS = require('module').builtinModules + +function stripNodePrefix (specifier) { + return typeof specifier === 'string' && specifier.startsWith('node:') + ? specifier.slice(5) + : specifier +} + +function moduleOfInterestKey (name, file) { + return file ? `${name}/${file}` : name +} + +const builtinCanonicalNames = new Set(RAW_BUILTINS.map(stripNodePrefix)) + +function addModuleOfInterest (name, file) { + if (!name) return + + const canonicalName = stripNodePrefix(name) + const keys = new Set([moduleOfInterestKey(name, file)]) + + if (canonicalName !== name) { + keys.add(moduleOfInterestKey(canonicalName, file)) + } + + if (builtinCanonicalNames.has(canonicalName)) { + keys.add(moduleOfInterestKey(`node:${canonicalName}`, file)) + } + + for (const key of keys) { + modulesOfInterest.add(key) + } +} + const modulesOfInterest = new Set() for (const instrumentation of Object.values(instrumentations)) { for (const entry of instrumentation) { - if (!entry.file) { - modulesOfInterest.add(entry.name) // e.g. "redis" - } else { - modulesOfInterest.add(`${entry.name}/${entry.file}`) // e.g. "redis/my/file.js" - } + addModuleOfInterest(entry.name, entry.file) } } -const RAW_BUILTINS = require('module').builtinModules const CHANNEL = 'dd-trace:bundler:load' const path = require('path') const fs = require('fs') @@ -44,7 +72,7 @@ const { execSync } = require('child_process') const builtins = new Set() -for (const builtin of RAW_BUILTINS) { +for (const builtin of builtinCanonicalNames) { builtins.add(builtin) builtins.add(`node:${builtin}`) } From 1ef51d82be32c713574d2820f43bd056d09051be Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Fri, 21 Nov 2025 14:11:48 -0500 Subject: [PATCH 3/3] simplified esbuild logic --- packages/datadog-esbuild/index.js | 25 ++++--------------------- packages/dd-trace/test/ritm.spec.js | 2 +- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/packages/datadog-esbuild/index.js b/packages/datadog-esbuild/index.js index ea30ef05662..36418a7757d 100644 --- a/packages/datadog-esbuild/index.js +++ b/packages/datadog-esbuild/index.js @@ -24,36 +24,19 @@ for (const hook of Object.values(hooks)) { } } -const RAW_BUILTINS = require('module').builtinModules - -function stripNodePrefix (specifier) { - return typeof specifier === 'string' && specifier.startsWith('node:') - ? specifier.slice(5) - : specifier -} - function moduleOfInterestKey (name, file) { return file ? `${name}/${file}` : name } -const builtinCanonicalNames = new Set(RAW_BUILTINS.map(stripNodePrefix)) +const builtinCanonicalNames = new Set(require('module').builtinModules) function addModuleOfInterest (name, file) { if (!name) return - const canonicalName = stripNodePrefix(name) - const keys = new Set([moduleOfInterestKey(name, file)]) - - if (canonicalName !== name) { - keys.add(moduleOfInterestKey(canonicalName, file)) - } - - if (builtinCanonicalNames.has(canonicalName)) { - keys.add(moduleOfInterestKey(`node:${canonicalName}`, file)) - } + modulesOfInterest.add(moduleOfInterestKey(name, file)) - for (const key of keys) { - modulesOfInterest.add(key) + if (builtinCanonicalNames.has(name)) { + modulesOfInterest.add(moduleOfInterestKey(`node:${name}`, file)) } } diff --git a/packages/dd-trace/test/ritm.spec.js b/packages/dd-trace/test/ritm.spec.js index 7d8984c3068..a43b2b9c009 100644 --- a/packages/dd-trace/test/ritm.spec.js +++ b/packages/dd-trace/test/ritm.spec.js @@ -103,7 +103,7 @@ describe('Ritm', () => { ) }) - it('should hook node: prefixed builtins via canonical registrations', () => { + it('should hook node: prefixed builtins', () => { const nodeHttp = require('node:http') assert.equal(nodeHttp.foo, 1) assert.equal(httpOnRequire.callCount, 1, 'onrequire should run only once')