Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a38a294

Browse files
benglpabloerhard
andauthored
remove special-casing of default in hook.js (#6401)
Fixed ESM instrumentation, the previous model of assuming default === exports was very incorrect for ESM since they're never the exact same object. We also can't assume they should have the same properties, or even that they're both objects/functions, at least not in the general case. Therefore, we need to be specific about what's being instrumented in every single instrumentation. Added appropriate testing and modifications to ensure any known broken instrumentation is properly instrumented with all import scenarios. --------- Co-authored-by: perhardhernandez <[email protected]>
1 parent 964886d commit a38a294

File tree

40 files changed

+493
-297
lines changed

40 files changed

+493
-297
lines changed

integration-tests/helpers/index.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,50 @@ async function createSandbox (dependencies = [], isGitRepo = false,
273273
}
274274
}
275275

276+
/**
277+
* Creates a bunch of files based on an original file in sandbox. Useful for varying test files
278+
* without having to create a bunch of them yourself.
279+
*
280+
* The variants object should have keys that are named variants, and values that are the text
281+
* in the file that's different in each variant. There must always be a "default" variant,
282+
* whose value is the original text within the file that will be replaced.
283+
*
284+
* @param {object} sandbox - A `sandbox` as returned from `createSandbox`
285+
* @param {string} filename - The file that will be copied and modified for each variant.
286+
* @param {object} variants - The variants. If empty then a default import style will be added
287+
* depending on the parameters passed through.
288+
* @param {object} bindingName - The binding name that will be use to bind to the packageName.
289+
* @param {object} namedVariant - The name of the named variant to use.
290+
* @param {object} packageName - The name of the package.
291+
* @returns {object} A map from variant names to resulting filenames
292+
*/
293+
function varySandbox (sandbox, filename, variants, bindingName, namedVariant, packageName) {
294+
const origFileData = fs.readFileSync(path.join(sandbox.folder, filename), 'utf8')
295+
const [prefix, suffix] = filename.split('.')
296+
const variantFilenames = {}
297+
packageName = packageName || bindingName
298+
const defaultVariants = {
299+
default: `import ${bindingName} from '${packageName}'`,
300+
star: namedVariant
301+
? `import * as ${bindingName} from '${packageName}'`
302+
: `import * as mod${bindingName} from '${packageName}'; const ${bindingName} = mod${bindingName}.default`,
303+
destructure: namedVariant
304+
? `import { ${namedVariant} } from '${packageName}'; const ${bindingName} = { ${namedVariant} }`
305+
: `import { default as ${bindingName}} from '${packageName}'`
306+
}
307+
variants = variants || defaultVariants
308+
for (const variant in variants) {
309+
const variantFilename = `${prefix}-${variant}.${suffix}`
310+
variantFilenames[variant] = variantFilename
311+
let newFileData = origFileData
312+
if (variant !== 'default') {
313+
newFileData = origFileData.replace(variants.default, `${variants[variant]}`)
314+
}
315+
fs.writeFileSync(path.join(sandbox.folder, variantFilename), newFileData)
316+
}
317+
return variantFilenames
318+
}
319+
276320
function telemetryForwarder (shouldExpectTelemetryPoints = true) {
277321
process.env.DD_TELEMETRY_FORWARDER_PATH =
278322
path.join(__dirname, '..', 'telemetry-forwarder.sh')
@@ -488,5 +532,6 @@ module.exports = {
488532
useEnv,
489533
useSandbox,
490534
sandboxCwd,
491-
setShouldKill
535+
setShouldKill,
536+
varySandbox
492537
}

packages/datadog-instrumentations/src/azure-functions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const dc = require('dc-polyfill')
88

99
const azureFunctionsChannel = dc.tracingChannel('datadog:azure:functions:invoke')
1010

11-
addHook({ name: '@azure/functions', versions: ['>=4'] }, azureFunction => {
11+
addHook({ name: '@azure/functions', versions: ['>=4'], patchDefault: false }, (azureFunction) => {
1212
const { app } = azureFunction
1313

1414
// Http triggers

packages/datadog-instrumentations/src/azure-service-bus.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const producerStartCh = channel('apm:azure-service-bus:send:start')
1111
const producerErrorCh = channel('apm:azure-service-bus:send:error')
1212
const producerFinishCh = channel('apm:azure-service-bus:send:finish')
1313

14-
addHook({ name: '@azure/service-bus', versions: ['>=7.9.2'] }, (obj) => {
14+
addHook({ name: '@azure/service-bus', versions: ['>=7.9.2'], patchDefault: false }, (obj) => {
1515
const ServiceBusClient = obj.ServiceBusClient
1616
shimmer.wrap(ServiceBusClient.prototype, 'createSender', createSender => function (queueOrTopicName) {
1717
const sender = createSender.apply(this, arguments)

packages/datadog-instrumentations/src/cassandra-driver.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ addHook({ name: 'cassandra-driver', versions: ['>=3.0.0'] }, cassandra => {
4545
return cassandra
4646
})
4747

48-
addHook({ name: 'cassandra-driver', versions: ['>=4.4'] }, cassandra => {
48+
addHook({ name: 'cassandra-driver', versions: ['>=4.4'], patchDefault: false }, (cassandra) => {
4949
shimmer.wrap(cassandra.Client.prototype, '_execute', _execute => function (query, params, execOptions, callback) {
5050
if (!startCh.hasSubscribers) {
5151
return _execute.apply(this, arguments)
@@ -68,7 +68,7 @@ const isValid = (args) => {
6868
return args.length === 4 || typeof args[3] === 'function'
6969
}
7070

71-
addHook({ name: 'cassandra-driver', versions: ['3 - 4.3'] }, cassandra => {
71+
addHook({ name: 'cassandra-driver', versions: ['3 - 4.3'], patchDefault: false }, (cassandra) => {
7272
shimmer.wrap(cassandra.Client.prototype, '_innerExecute', _innerExecute =>
7373
function (query, params, execOptions, callback) {
7474
if (!startCh.hasSubscribers) {

packages/datadog-instrumentations/src/connect.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,15 @@ function wrapNext (req, next) {
102102
})
103103
}
104104

105-
addHook({ name: 'connect', versions: ['>=3'] }, connect => {
105+
addHook({ name: 'connect', versions: ['>=3.4.0'] }, (connect) => {
106106
return shimmer.wrapFunction(connect, connect => wrapConnect(connect))
107107
})
108108

109-
addHook({ name: 'connect', versions: ['2.2.2'] }, connect => {
109+
addHook({ name: 'connect', versions: ['>=3 <3.4.0'], file: 'lib/connect.js' }, (connect) => {
110+
return shimmer.wrapFunction(connect, connect => wrapConnect(connect))
111+
})
112+
113+
addHook({ name: 'connect', versions: ['2.2.2'], file: 'lib/connect.js' }, connect => {
110114
shimmer.wrap(connect.proto, 'use', wrapUse)
111115
shimmer.wrap(connect.proto, 'handle', wrapHandle)
112116

packages/datadog-instrumentations/src/express.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function wrapResponseRender (render) {
5656
}
5757
}
5858

59-
addHook({ name: 'express', versions: ['>=4'] }, express => {
59+
addHook({ name: 'express', versions: ['>=4'], file: ['lib/express.js'] }, express => {
6060
shimmer.wrap(express.application, 'handle', wrapHandle)
6161

6262
shimmer.wrap(express.response, 'json', wrapResponseJson)
@@ -69,7 +69,7 @@ addHook({ name: 'express', versions: ['>=4'] }, express => {
6969
// Express 5 does not rely on router in the same way as v4 and should not be instrumented anymore.
7070
// It would otherwise produce spans for router and express, and so duplicating them.
7171
// We now fall back to router instrumentation
72-
addHook({ name: 'express', versions: ['4'] }, express => {
72+
addHook({ name: 'express', versions: ['4'], file: 'lib/express.js' }, express => {
7373
shimmer.wrap(express.Router, 'use', wrapRouterMethod)
7474
shimmer.wrap(express.Router, 'route', wrapRouterMethod)
7575

@@ -131,12 +131,12 @@ function wrapProcessParamsMethod (requestPositionInArguments) {
131131
}
132132
}
133133

134-
addHook({ name: 'express', versions: ['>=4.0.0 <4.3.0'] }, express => {
134+
addHook({ name: 'express', versions: ['>=4.0.0 <4.3.0'], file: ['lib/express.js'] }, express => {
135135
shimmer.wrap(express.Router, 'process_params', wrapProcessParamsMethod(1))
136136
return express
137137
})
138138

139-
addHook({ name: 'express', versions: ['>=4.3.0 <5.0.0'] }, express => {
139+
addHook({ name: 'express', versions: ['>=4.3.0 <5.0.0'], file: ['lib/express.js'] }, express => {
140140
shimmer.wrap(express.Router, 'process_params', wrapProcessParamsMethod(2))
141141
return express
142142
})

packages/datadog-instrumentations/src/fastify.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ function canPublishResponsePayload (payload) {
265265
!ArrayBuffer.isView(payload) // TypedArray
266266
}
267267

268-
addHook({ name: 'fastify', versions: ['>=3'] }, fastify => {
268+
addHook({ name: 'fastify', versions: ['>=3'] }, (fastify) => {
269269
const wrapped = shimmer.wrapFunction(fastify, fastify => wrapFastify(fastify, true))
270270

271271
wrapped.fastify = wrapped
@@ -274,11 +274,11 @@ addHook({ name: 'fastify', versions: ['>=3'] }, fastify => {
274274
return wrapped
275275
})
276276

277-
addHook({ name: 'fastify', versions: ['2'] }, fastify => {
277+
addHook({ name: 'fastify', versions: ['2'] }, (fastify) => {
278278
return shimmer.wrapFunction(fastify, fastify => wrapFastify(fastify, true))
279279
})
280280

281-
addHook({ name: 'fastify', versions: ['1'] }, fastify => {
281+
addHook({ name: 'fastify', versions: ['1'] }, (fastify) => {
282282
return shimmer.wrapFunction(fastify, fastify => wrapFastify(fastify, false))
283283
})
284284

packages/datadog-instrumentations/src/helpers/hook.js

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict'
2-
3-
const path = require('path')
42
const iitm = require('../../../dd-trace/src/iitm')
3+
const path = require('path')
54
const ritm = require('../../../dd-trace/src/ritm')
65

76
/**
@@ -20,29 +19,43 @@ function Hook (modules, hookOptions, onrequire) {
2019
}
2120

2221
this._patched = Object.create(null)
22+
const patched = new WeakMap()
2323

24-
const safeHook = (moduleExports, moduleName, moduleBaseDir, moduleVersion) => {
24+
const safeHook = (moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm) => {
2525
const parts = [moduleBaseDir, moduleName].filter(Boolean)
2626
const filename = path.join(...parts)
2727

28-
if (this._patched[filename]) return moduleExports
28+
if (this._patched[filename] && patched.has(moduleExports)) {
29+
return patched.get(moduleExports)
30+
}
2931

30-
this._patched[filename] = true
32+
let defaultWrapResult
33+
34+
if (
35+
isIitm &&
36+
moduleExports.default &&
37+
(typeof moduleExports.default === 'object' ||
38+
typeof moduleExports.default === 'function')
39+
) {
40+
defaultWrapResult = onrequire(moduleExports.default, moduleName, moduleBaseDir, moduleVersion, isIitm)
41+
}
42+
43+
const newExports = onrequire(moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm)
44+
45+
if (defaultWrapResult) newExports.default = defaultWrapResult
3146

32-
return onrequire(moduleExports, moduleName, moduleBaseDir, moduleVersion)
47+
this._patched[filename] = true
48+
if (newExports &&
49+
(typeof newExports === 'object' ||
50+
typeof newExports === 'function')) {
51+
patched.set(moduleExports, newExports)
52+
}
53+
return newExports
3354
}
3455

3556
this._ritmHook = ritm(modules, {}, safeHook)
3657
this._iitmHook = iitm(modules, hookOptions, (moduleExports, moduleName, moduleBaseDir) => {
37-
// TODO: Move this logic to import-in-the-middle and only do it for CommonJS
38-
// modules and not ESM. In the meantime, all the modules we instrument are
39-
// CommonJS modules for which the default export is always moved to
40-
// `default` anyway.
41-
if (moduleExports && moduleExports.default) {
42-
moduleExports.default = safeHook(moduleExports.default, moduleName, moduleBaseDir)
43-
return moduleExports
44-
}
45-
return safeHook(moduleExports, moduleName, moduleBaseDir)
58+
return safeHook(moduleExports, moduleName, moduleBaseDir, null, true)
4659
})
4760
}
4861

packages/datadog-instrumentations/src/helpers/instrument.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ exports.tracingChannel = function (name) {
2929
* @param {string} args.filePattern pattern to match files within package to instrument
3030
* @param Function hook
3131
*/
32-
exports.addHook = function addHook ({ name, versions, file, filePattern }, hook) {
32+
exports.addHook = function addHook ({ name, versions, file, filePattern, patchDefault }, hook) {
3333
if (typeof name === 'string') {
3434
name = [name]
3535
}
@@ -38,7 +38,7 @@ exports.addHook = function addHook ({ name, versions, file, filePattern }, hook)
3838
if (!instrumentations[val]) {
3939
instrumentations[val] = []
4040
}
41-
instrumentations[val].push({ name: val, versions, file, filePattern, hook })
41+
instrumentations[val].push({ name: val, versions, file, filePattern, hook, patchDefault })
4242
}
4343
}
4444

packages/datadog-instrumentations/src/helpers/register.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ for (const packageName of names) {
6666
// get the instrumentation file name to save all hooked versions
6767
const instrumentationFileName = parseHookInstrumentationFileName(packageName)
6868

69-
Hook([packageName], hookOptions, (moduleExports, moduleName, moduleBaseDir, moduleVersion) => {
69+
Hook([packageName], hookOptions, (moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm) => {
7070
moduleName = moduleName.replace(pathSepExpr, '/')
7171

7272
// This executes the integration file thus adding its entries to `instrumentations`
@@ -77,7 +77,13 @@ for (const packageName of names) {
7777
}
7878

7979
const namesAndSuccesses = {}
80-
for (const { name, file, versions, hook, filePattern } of instrumentations[packageName]) {
80+
for (const { name, file, versions, hook, filePattern, patchDefault } of instrumentations[packageName]) {
81+
if (patchDefault === false && !moduleExports.default && isIitm) {
82+
return moduleExports
83+
} else if (patchDefault === true && moduleExports.default && isIitm) {
84+
moduleExports = moduleExports.default
85+
}
86+
8187
let fullFilePattern = filePattern
8288
const fullFilename = filename(name, file)
8389
if (fullFilePattern) {
@@ -137,7 +143,8 @@ for (const packageName of names) {
137143
// picked up due to the unification. Check what modules actually use the name.
138144
// TODO(BridgeAR): Only replace moduleExports if the hook returns a new value.
139145
// This allows to reduce the instrumentation code (no return needed).
140-
moduleExports = hook(moduleExports, version, name) ?? moduleExports
146+
147+
moduleExports = hook(moduleExports, version, name, isIitm) ?? moduleExports
141148
// Set the moduleExports in the hooks WeakSet
142149
hook[HOOK_SYMBOL].add(moduleExports)
143150
} catch (e) {

0 commit comments

Comments
 (0)