Skip to content

Commit 5290868

Browse files
committed
Default instrumentation of node built in libraries
1 parent aef3b3f commit 5290868

5 files changed

Lines changed: 66 additions & 25 deletions

File tree

packages/datadog-esbuild/index.js

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,38 @@ for (const hook of Object.values(hooks)) {
2424
}
2525
}
2626

27+
function moduleOfInterestKey (name, file) {
28+
return file ? `${name}/${file}` : name
29+
}
30+
31+
const builtinModules = new Set(require('module').builtinModules)
32+
33+
function addModuleOfInterest (name, file) {
34+
if (!name) return
35+
36+
modulesOfInterest.add(moduleOfInterestKey(name, file))
37+
38+
if (builtinModules.has(name)) {
39+
modulesOfInterest.add(moduleOfInterestKey(`node:${name}`, file))
40+
}
41+
}
42+
2743
const modulesOfInterest = new Set()
2844

29-
for (const [name, instrumentation] of Object.entries(instrumentations)) {
45+
for (const instrumentation of Object.values(instrumentations)) {
3046
for (const entry of instrumentation) {
31-
if (!entry.file) {
32-
modulesOfInterest.add(name) // e.g. "redis"
33-
} else {
34-
modulesOfInterest.add(`${name}/${entry.file}`) // e.g. "redis/my/file.js"
35-
}
47+
addModuleOfInterest(entry.name, entry.file)
3648
}
3749
}
3850

39-
const RAW_BUILTINS = require('module').builtinModules
4051
const CHANNEL = 'dd-trace:bundler:load'
4152
const path = require('path')
4253
const fs = require('fs')
4354
const { execSync } = require('child_process')
4455

4556
const builtins = new Set()
4657

47-
for (const builtin of RAW_BUILTINS) {
58+
for (const builtin of builtinModules) {
4859
builtins.add(builtin)
4960
builtins.add(`node:${builtin}`)
5061
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ dc.subscribe(CHANNEL, (message) => {
8282
return
8383
}
8484

85-
for (const { file, versions, hook } of instrumentation) {
85+
for (const { name, file, versions, hook } of instrumentation) {
8686
if (payload.path !== filename(name, file) || !matchVersion(payload.version, versions)) {
8787
continue
8888
}

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,16 @@ exports.tracingChannel = function (name) {
5151
* @param {import('./instrumentations').Hook} hook
5252
*/
5353
exports.addHook = function addHook ({ name, versions, file, filePattern, patchDefault }, hook) {
54-
if (!instrumentations[name]) {
55-
instrumentations[name] = []
54+
if (typeof name === 'string') {
55+
name = [name]
5656
}
5757

58-
instrumentations[name].push({ versions, file, filePattern, hook, patchDefault })
58+
for (const val of name) {
59+
if (!instrumentations[val]) {
60+
instrumentations[val] = []
61+
}
62+
instrumentations[val].push({ name: val, versions, file, filePattern, hook, patchDefault })
63+
}
5964
}
6065

6166
exports.AsyncResource = AsyncResource

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const disabledInstrumentations = new Set(
3030
)
3131

3232
const loadChannel = channel('dd-trace:instrumentation:load')
33+
const HOOK_SYMBOL = Symbol('hookExportsSet')
3334

3435
// Globals
3536
if (!disabledInstrumentations.has('fetch')) {
@@ -124,6 +125,7 @@ for (const name of names) {
124125
}
125126
}
126127

128+
hook[HOOK_SYMBOL] ??= new WeakSet()
127129
const fullFilename = filename(name, file)
128130

129131
let matchesFile = moduleName === fullFilename
@@ -136,12 +138,16 @@ for (const name of names) {
136138
}
137139

138140
if (matchesFile && matchVersion(moduleVersion, versions)) {
141+
if (hook[HOOK_SYMBOL].has(moduleExports)) {
142+
return moduleExports
143+
}
139144
// Do not log in case of an error to prevent duplicate telemetry for the same integration version.
140145
instrumentedIntegrationsSuccess.set(`${name}@${moduleVersion}`, true)
141146
try {
142147
loadChannel.publish({ name })
143148

144149
moduleExports = hook(moduleExports, moduleVersion) ?? moduleExports
150+
hook[HOOK_SYMBOL].add(moduleExports)
145151
} catch (error) {
146152
log.info('Error during ddtrace instrumentation of application, aborting.', error)
147153
telemetry('error', [

packages/dd-trace/src/ritm.js

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,24 @@ let patchedRequire = null
1919
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
2020
const moduleLoadEndChannel = dc.channel('dd-trace:moduleLoadEnd')
2121

22+
function stripNodePrefix (name) {
23+
if (typeof name !== 'string') return name
24+
return name.startsWith('node:') ? name.slice(5) : name
25+
}
26+
27+
const builtinModules = new Set(Module.builtinModules.map(stripNodePrefix))
28+
29+
function isBuiltinModuleName (name) {
30+
if (typeof name !== 'string') return false
31+
return builtinModules.has(stripNodePrefix(name))
32+
}
33+
34+
function normalizeModuleName (name) {
35+
if (typeof name !== 'string') return name
36+
const stripped = stripNodePrefix(name)
37+
return builtinModules.has(stripped) ? stripped : name
38+
}
39+
2240
/**
2341
* @overload
2442
* @param {string[]} modules list of modules to hook into
@@ -73,27 +91,28 @@ function Hook (modules, options, onrequire) {
7391
return _origRequire.apply(this, arguments)
7492
}
7593

76-
const core = !filename.includes(path.sep)
94+
const builtin = isBuiltinModuleName(filename)
95+
const moduleId = builtin ? normalizeModuleName(filename) : filename
7796
let name, basedir, hooks
7897
// return known patched modules immediately
79-
if (cache[filename]) {
98+
if (cache[moduleId]) {
8099
// require.cache was potentially altered externally
81100
const cacheEntry = require.cache[filename]
82101
if (cacheEntry && cacheEntry.exports !== cache[filename].original) {
83102
return cacheEntry.exports
84103
}
85104

86-
return cache[filename].exports
105+
return cache[moduleId].exports
87106
}
88107

89108
// Check if this module has a patcher in-progress already.
90109
// Otherwise, mark this module as patching in-progress.
91-
const patched = patching[filename]
110+
const patched = patching[moduleId]
92111
if (patched) {
93112
// If it's already patched, just return it as-is.
94113
return origRequire.apply(this, arguments)
95114
}
96-
patching[filename] = true
115+
patching[moduleId] = true
97116

98117
const payload = {
99118
filename,
@@ -112,12 +131,12 @@ function Hook (modules, options, onrequire) {
112131

113132
// The module has already been loaded,
114133
// so the patching mark can be cleaned up.
115-
delete patching[filename]
134+
delete patching[moduleId]
116135

117-
if (core) {
118-
hooks = moduleHooks[filename]
136+
if (builtin) {
137+
hooks = moduleHooks[moduleId]
119138
if (!hooks) return exports // abort if module name isn't on whitelist
120-
name = filename
139+
name = moduleId
121140
} else {
122141
const inAWSLambda = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined
123142
const hasLambdaHandler = getEnvironmentVariable('DD_LAMBDA_HANDLER') !== undefined
@@ -159,14 +178,14 @@ function Hook (modules, options, onrequire) {
159178

160179
// ensure that the cache entry is assigned a value before calling
161180
// onrequire, in case calling onrequire requires the same module.
162-
cache[filename] = { exports }
163-
cache[filename].original = exports
181+
cache[moduleId] = { exports }
182+
cache[moduleId].original = exports
164183

165184
for (const hook of hooks) {
166-
cache[filename].exports = hook(cache[filename].exports, name, basedir)
185+
cache[moduleId].exports = hook(cache[moduleId].exports, name, basedir)
167186
}
168187

169-
return cache[filename].exports
188+
return cache[moduleId].exports
170189
}
171190
}
172191

0 commit comments

Comments
 (0)