UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

164 lines (133 loc) 5.65 kB
/* eslint n/no-unsupported-features/node-builtins: ['error', { ignores: ['module.registerHooks'] }] */ import * as Module from 'node:module' import { pathToFileURL } from 'node:url' import { createHook, supportsSyncHooks } from 'import-in-the-middle/create-hook.mjs' import { initialize as origInitialize, load as origLoad, resolve } from 'import-in-the-middle/hook.mjs' import regexpEscapeModule from './vendor/dist/escape-string-regexp/index.js' import hooks from './packages/datadog-instrumentations/src/helpers/hooks.js' import configHelper from './packages/dd-trace/src/config/helper.js' import * as rewriterLoader from './packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs' import { isRelativeRequire } from './packages/datadog-instrumentations/src/helpers/shared-utils.js' // This file must support Node.js 12.0.0 syntax const regexpEscape = regexpEscapeModule.default const require = Module.createRequire(import.meta.url) let syncImportInTheMiddleHook // For some reason `getEnvironmentVariable` is not otherwise available to ESM. const env = configHelper.getEnvironmentVariable function initialize (data = {}) { prepareImportInTheMiddleOptions(data) return origInitialize(data) } function prepareImportInTheMiddleOptions (data = {}) { if (data.include == null) data.include = [] if (data.exclude == null) data.exclude = [] addInstrumentations(data) addSecurityControls(data) addExclusions(data) return data } function load (url, context, nextLoad) { return rewriterLoader.load(url, context, (url, context) => origLoad(url, context, nextLoad)) } function loadSync (url, context, nextLoad) { if (isCommonJSLoad(context)) { return getSyncImportInTheMiddleHook().loadSync(url, context, nextLoad) } return rewriterLoader.loadSync(url, context, (url, context) => { return getSyncImportInTheMiddleHook().loadSync(url, context, nextLoad) }) } function isCommonJSLoad (context) { if (context.format) return context.format === 'commonjs' // Sync hooks report CommonJS require() dependency loads with a `require` // condition but no format. If a format is present, trust it instead: ESM // loaded through require() reports `format: 'module'` and still needs rewrite. const conditions = context.conditions if (!conditions) return false for (let i = 0; i < conditions.length; i++) { if (conditions[i] === 'require') return true } return false } function getSyncImportInTheMiddleHook () { if (syncImportInTheMiddleHook) { return syncImportInTheMiddleHook } const importInTheMiddleRegisterHooksUrl = pathToFileURL( require.resolve('import-in-the-middle/register-hooks.mjs') ).href syncImportInTheMiddleHook = createHook({ url: importInTheMiddleRegisterHooksUrl }) return syncImportInTheMiddleHook } function registerSyncLoaderHooks (data = {}) { // The synchronous loader strips the source of a require() pulled into the iitm // ESM graph so Node loads it natively, but module.registerHooks rejected that // nullish CommonJS source until nodejs/node#59929 (released in 22.22.3, 24.11.1, // 25.1.0 and 26.0.0). On versions that ship registerHooks but predate the fix, // fall back to the asynchronous loader instead of crashing mid-graph. if (!supportsSyncHooks()) { return false } const syncHook = getSyncImportInTheMiddleHook() if ( typeof Module.registerHooks !== 'function' || typeof syncHook.applyOptions !== 'function' || typeof syncHook.loadSync !== 'function' || typeof syncHook.resolveSync !== 'function' ) { return false } // Node built-ins are instrumented under the synchronous loader as well: iitm // reads a built-in's exports through process.getBuiltinModule(), which // bypasses the registered hooks and therefore cannot re-enter them. The // synchronous and asynchronous loaders share the same option preparation so // that `import http from 'node:http'` is wrapped on both paths. syncHook.applyOptions(prepareImportInTheMiddleOptions(data)) Module.registerHooks({ resolve: syncHook.resolveSync, load: loadSync, }) return true } function addInstrumentations (data) { const instrumentations = Object.keys(hooks) for (const moduleName of instrumentations) { // Skip instrumentation hooks with relative module names // since there is no current business need of instrumenting imports outside of the node_modules folder if (!isRelativeRequire(moduleName)) { data.include.push(new RegExp(`node_modules/${moduleName}/(?!node_modules).+`), moduleName) } } } function addSecurityControls (data) { const raw = env('DD_IAST_SECURITY_CONTROLS_CONFIGURATION') if (!raw) return // Parse `;`-separated entries and take the 3rd `:`-separated segment. // Expected form (per entry): `<...>:<...>:<subpath>:<...>` const entries = raw.split(';') for (const entry of entries) { if (entry) { const first = entry.indexOf(':') if (first === -1) continue const second = entry.indexOf(':', first + 1) if (second === -1) continue const third = entry.indexOf(':', second + 1) const subpath = entry.slice(second + 1, third === -1 ? undefined : third).trim() if (subpath) { data.include.push(new RegExp(regexpEscape(subpath))) } } } } function addExclusions (data) { data.exclude.push(...iitmExclusions) } export const iitmExclusions = [ /middle/, /langsmith/, /openai\/_shims/, /openai\/resources\/chat\/completions\/messages/, /openai\/agents-core\/dist\/shims/, /@anthropic-ai\/sdk\/_shims/, ] export { initialize, load, registerSyncLoaderHooks, resolve }