dd-trace
Version:
Datadog APM tracing client for JavaScript
130 lines (96 loc) • 3.57 kB
JavaScript
const semver = require('semver')
const hook = require('./ritm')
const esmHook = require('./iitm')
const parse = require('module-details-from-path')
const path = require('path')
const uniq = require('lodash.uniq')
const log = require('./log')
const requirePackageJson = require('./require-package-json')
const pathSepExpr = new RegExp(`\\${path.sep}`, 'g')
class Loader {
constructor (instrumenter) {
this._instrumenter = instrumenter
}
reload (plugins) {
this._plugins = plugins
const instrumentations = Array.from(this._plugins.keys())
.reduce((prev, current) => prev.concat(current), [])
const instrumentedModules = uniq(instrumentations
.map(instrumentation => instrumentation.name))
this._names = new Set(instrumentations
.map(instrumentation => filename(instrumentation)))
hook(instrumentedModules, this._hookModule.bind(this))
esmHook(instrumentedModules, this._hookModule.bind(this))
}
load (instrumentation, config) {
this._getModules(instrumentation).forEach(nodule => {
this._instrumenter.patch(instrumentation, nodule, config)
})
}
_getModules (instrumentation) {
const modules = []
const ids = Object.keys(require.cache)
let pkg
for (let i = 0, l = ids.length; i < l; i++) {
const id = ids[i].replace(pathSepExpr, '/')
if (!id.includes(`/node_modules/${instrumentation.name}/`)) continue
if (instrumentation.file) {
if (!id.endsWith(`/node_modules/${filename(instrumentation)}`)) continue
const basedir = getBasedir(ids[i])
pkg = requirePackageJson(basedir, module)
} else {
const basedir = getBasedir(ids[i])
pkg = requirePackageJson(basedir, module)
const mainFile = path.posix.normalize(pkg.main || 'index.js')
if (!id.endsWith(`/node_modules/${instrumentation.name}/${mainFile}`)) continue
}
if (!matchVersion(pkg.version, instrumentation.versions)) continue
modules.push(require.cache[ids[i]].exports)
}
return modules
}
_hookModule (moduleExports, moduleName, moduleBaseDir) {
moduleName = moduleName.replace(pathSepExpr, '/')
if (!this._names.has(moduleName)) {
return moduleExports
}
if (moduleBaseDir) {
moduleBaseDir = moduleBaseDir.replace(pathSepExpr, '/')
}
const moduleVersion = getVersion(moduleBaseDir)
for (const [plugin, meta] of this._plugins) {
if (meta.config.enabled === false) {
continue
}
try {
for (const instrumentation of [].concat(plugin)) {
if (moduleName !== filename(instrumentation) || !matchVersion(moduleVersion, instrumentation.versions)) {
continue
}
moduleExports = this._instrumenter.patch(instrumentation, moduleExports, meta.config) || moduleExports
}
} catch (e) {
log.error(e)
this._instrumenter.unload(plugin)
log.debug(`Error while trying to patch ${meta.name}. The plugin has been disabled.`)
}
}
return moduleExports
}
}
function getBasedir (id) {
return parse(id).basedir.replace(pathSepExpr, '/')
}
function matchVersion (version, ranges) {
return !version || (ranges && ranges.some(range => semver.satisfies(semver.coerce(version), range)))
}
function getVersion (moduleBaseDir) {
if (moduleBaseDir) {
return requirePackageJson(moduleBaseDir, module).version
}
}
function filename (plugin) {
return [plugin.name, plugin.file].filter(val => val).join('/')
}
module.exports = Loader