dd-trace 5.73.0 → 5.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +1 -0
- package/index.d.ts +28 -0
- package/package.json +6 -3
- package/packages/datadog-core/src/utils/src/set.js +5 -1
- package/packages/datadog-esbuild/index.js +104 -36
- package/packages/datadog-esbuild/src/utils.js +198 -0
- package/packages/datadog-instrumentations/src/express-session.js +1 -0
- package/packages/datadog-instrumentations/src/express.js +82 -0
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +238 -0
- package/packages/datadog-instrumentations/src/jest.js +2 -1
- package/packages/datadog-instrumentations/src/playwright.js +110 -56
- package/packages/datadog-instrumentations/src/router.js +63 -6
- package/packages/datadog-instrumentations/src/ws.js +3 -3
- package/packages/datadog-plugin-express/src/code_origin.js +2 -0
- package/packages/datadog-plugin-ws/src/close.js +1 -1
- package/packages/dd-trace/src/config-helper.js +3 -1
- package/packages/dd-trace/src/config.js +437 -446
- package/packages/dd-trace/src/config_defaults.js +5 -12
- package/packages/dd-trace/src/plugins/util/ci.js +3 -2
- package/packages/dd-trace/src/plugins/util/stacktrace.js +16 -1
- package/packages/dd-trace/src/supported-configurations.json +1 -0
- package/packages/dd-trace/src/telemetry/endpoints.js +27 -1
- package/packages/dd-trace/src/telemetry/index.js +16 -13
- package/scripts/preinstall.js +3 -1
- package/version.js +2 -1
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -50,6 +50,7 @@ dev,@stylistic/eslint-plugin,MIT,Copyright OpenJS Foundation and other contribut
|
|
|
50
50
|
dev,axios,MIT,Copyright 2014-present Matt Zabriskie
|
|
51
51
|
dev,benchmark,MIT,Copyright 2010-2016 Mathias Bynens Robert Kieffer John-David Dalton
|
|
52
52
|
dev,body-parser,MIT,Copyright 2014 Jonathan Ong 2014-2015 Douglas Christopher Wilson
|
|
53
|
+
dev,bun,MIT,Copyright contributors
|
|
53
54
|
dev,chai,MIT,Copyright 2017 Chai.js Assertion Library
|
|
54
55
|
dev,eslint,MIT,Copyright JS Foundation and other contributors https://js.foundation
|
|
55
56
|
dev,eslint-plugin-cypress,MIT,Copyright (c) 2019 Cypress.io
|
package/index.d.ts
CHANGED
|
@@ -134,6 +134,11 @@ interface Tracer extends opentracing.Tracer {
|
|
|
134
134
|
|
|
135
135
|
dogstatsd: tracer.DogStatsD;
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Data Streams manual checkpointer API.
|
|
139
|
+
*/
|
|
140
|
+
dataStreamsCheckpointer: tracer.DataStreamsCheckpointer;
|
|
141
|
+
|
|
137
142
|
/**
|
|
138
143
|
* LLM Observability SDK
|
|
139
144
|
*/
|
|
@@ -1026,6 +1031,29 @@ declare namespace tracer {
|
|
|
1026
1031
|
flush(): void
|
|
1027
1032
|
}
|
|
1028
1033
|
|
|
1034
|
+
/**
|
|
1035
|
+
* Manual Data Streams Monitoring checkpointer API.
|
|
1036
|
+
*/
|
|
1037
|
+
export interface DataStreamsCheckpointer {
|
|
1038
|
+
/**
|
|
1039
|
+
* Sets a produce checkpoint and injects the DSM context into the provided carrier.
|
|
1040
|
+
* @param type The streaming technology (e.g., kafka, kinesis, sns).
|
|
1041
|
+
* @param target The target of data (topic, exchange, stream name).
|
|
1042
|
+
* @param carrier The carrier object to inject DSM context into.
|
|
1043
|
+
*/
|
|
1044
|
+
setProduceCheckpoint (type: string, target: string, carrier: any): void;
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Sets a consume checkpoint and extracts DSM context from the provided carrier.
|
|
1048
|
+
* @param type The streaming technology (e.g., kafka, kinesis, sns).
|
|
1049
|
+
* @param source The source of data (topic, exchange, stream name).
|
|
1050
|
+
* @param carrier The carrier object to extract DSM context from.
|
|
1051
|
+
* @param manualCheckpoint Whether this checkpoint was manually set. Defaults to true.
|
|
1052
|
+
* @returns The DSM context associated with the current pathway.
|
|
1053
|
+
*/
|
|
1054
|
+
setConsumeCheckpoint (type: string, source: string, carrier: any, manualCheckpoint?: boolean): any;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1029
1057
|
export interface EventTrackingV2 {
|
|
1030
1058
|
/**
|
|
1031
1059
|
* Links a successful login event to the current trace. Will link the passed user to the current trace with Appsec.setUser() internally.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.74.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"",
|
|
33
33
|
"test:trace:guardrails": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/guardrails/**/*.spec.js\"",
|
|
34
34
|
"test:trace:guardrails:ci": "nyc --no-clean --include \"packages/dd-trace/src/guardrails/**/*.js\" -- npm run test:trace:guardrails",
|
|
35
|
+
"test:esbuild": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-esbuild/test/**/*.spec.js\"",
|
|
36
|
+
"test:esbuild:ci": "nyc --no-clean --include \"packages/datadog-esbuild/test/**/*.js\" -- npm run test:esbuild",
|
|
35
37
|
"test:instrumentations": "mocha -r 'packages/dd-trace/test/setup/mocha.js' \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\"",
|
|
36
38
|
"test:instrumentations:ci": "yarn services && nyc --no-clean --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS)).js\" --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS))/**/*.js\" -- npm run test:instrumentations",
|
|
37
39
|
"test:instrumentations:misc": "mocha -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/*/**/*.spec.js'",
|
|
@@ -158,7 +160,7 @@
|
|
|
158
160
|
},
|
|
159
161
|
"peerDependencies": {
|
|
160
162
|
"@openfeature/core": "^1.9.0",
|
|
161
|
-
"@openfeature/server-sdk": "~1.
|
|
163
|
+
"@openfeature/server-sdk": "~1.20.0"
|
|
162
164
|
},
|
|
163
165
|
"peerDependenciesMeta": {
|
|
164
166
|
"@openfeature/core": {
|
|
@@ -174,7 +176,7 @@
|
|
|
174
176
|
"@eslint/js": "^9.29.0",
|
|
175
177
|
"@msgpack/msgpack": "^3.1.2",
|
|
176
178
|
"@openfeature/core": "^1.8.1",
|
|
177
|
-
"@openfeature/server-sdk": "~1.
|
|
179
|
+
"@openfeature/server-sdk": "~1.20.0",
|
|
178
180
|
"@stylistic/eslint-plugin": "^5.0.0",
|
|
179
181
|
"@types/chai": "^4.3.16",
|
|
180
182
|
"@types/mocha": "^10.0.10",
|
|
@@ -184,6 +186,7 @@
|
|
|
184
186
|
"axios": "^1.12.2",
|
|
185
187
|
"benchmark": "^2.1.4",
|
|
186
188
|
"body-parser": "^2.2.0",
|
|
189
|
+
"bun": "1.3.1",
|
|
187
190
|
"chai": "^4.5.0",
|
|
188
191
|
"eslint": "^9.29.0",
|
|
189
192
|
"eslint-plugin-cypress": "^5.1.0",
|
|
@@ -5,7 +5,11 @@ module.exports = function set (object, path, value) {
|
|
|
5
5
|
while (true) {
|
|
6
6
|
const nextIndex = path.indexOf('.', index + 1)
|
|
7
7
|
if (nextIndex === -1) {
|
|
8
|
-
|
|
8
|
+
if (index === -1) {
|
|
9
|
+
object[path] = value
|
|
10
|
+
} else {
|
|
11
|
+
object[path.slice(index + 1)] = value
|
|
12
|
+
}
|
|
9
13
|
return
|
|
10
14
|
}
|
|
11
15
|
object = object[path.slice(index + 1, nextIndex)] ??= {}
|
|
@@ -8,6 +8,12 @@ const extractPackageAndModulePath = require(
|
|
|
8
8
|
'../datadog-instrumentations/src/helpers/extract-package-and-module-path.js'
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
+
const { pathToFileURL, fileURLToPath } = require('url')
|
|
12
|
+
const { processModule, isESMFile } = require('./src/utils.js')
|
|
13
|
+
|
|
14
|
+
const ESM_INTERCEPTED_SUFFIX = '._dd_esbuild_intercepted'
|
|
15
|
+
const INTERNAL_ESM_INTERCEPTED_PREFIX = '/_dd_esm_internal_/'
|
|
16
|
+
|
|
11
17
|
let rewriter
|
|
12
18
|
|
|
13
19
|
for (const hook of Object.values(hooks)) {
|
|
@@ -30,7 +36,6 @@ for (const instrumentation of Object.values(instrumentations)) {
|
|
|
30
36
|
}
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
const INSTRUMENTED = Object.keys(instrumentations)
|
|
34
39
|
const RAW_BUILTINS = require('module').builtinModules
|
|
35
40
|
const CHANNEL = 'dd-trace:bundler:load'
|
|
36
41
|
const path = require('path')
|
|
@@ -47,14 +52,6 @@ for (const builtin of RAW_BUILTINS) {
|
|
|
47
52
|
const DEBUG = !!process.env.DD_TRACE_DEBUG
|
|
48
53
|
const DD_IAST_ENABLED = process.env.DD_IAST_ENABLED?.toLowerCase() === 'true' || process.env.DD_IAST_ENABLED === '1'
|
|
49
54
|
|
|
50
|
-
// We don't want to handle any built-in packages
|
|
51
|
-
// Those packages will still be handled via RITM
|
|
52
|
-
// Attempting to instrument them would fail as they have no package.json file
|
|
53
|
-
for (const pkg of INSTRUMENTED) {
|
|
54
|
-
if (builtins.has(pkg) || pkg.startsWith('node:')) continue
|
|
55
|
-
modulesOfInterest.add(pkg)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
55
|
module.exports.name = 'datadog-esbuild'
|
|
59
56
|
|
|
60
57
|
function isESMBuild (build) {
|
|
@@ -123,7 +120,8 @@ ${build.initialOptions.banner.js}`
|
|
|
123
120
|
build.initialOptions.external.push('@openfeature/core')
|
|
124
121
|
}
|
|
125
122
|
|
|
126
|
-
|
|
123
|
+
const esmBuild = isESMBuild(build)
|
|
124
|
+
if (esmBuild) {
|
|
127
125
|
if (!build.initialOptions.banner.js.includes('import { createRequire as $dd_createRequire } from \'module\'')) {
|
|
128
126
|
build.initialOptions.banner.js = `import { createRequire as $dd_createRequire } from 'module';
|
|
129
127
|
import { fileURLToPath as $dd_fileURLToPath } from 'url';
|
|
@@ -157,6 +155,9 @@ ${build.initialOptions.banner.js}`
|
|
|
157
155
|
console.warn('Warning: No git metadata available - skipping injection')
|
|
158
156
|
}
|
|
159
157
|
|
|
158
|
+
// first time is intercepted, proxy should be created, next time the original should be loaded
|
|
159
|
+
const interceptedESMModules = new Set()
|
|
160
|
+
|
|
160
161
|
build.onResolve({ filter: /.*/ }, args => {
|
|
161
162
|
if (externalModules.has(args.path)) {
|
|
162
163
|
// Internal Node.js packages will still be instrumented via require()
|
|
@@ -175,7 +176,7 @@ ${build.initialOptions.banner.js}`
|
|
|
175
176
|
|
|
176
177
|
let fullPathToModule
|
|
177
178
|
try {
|
|
178
|
-
fullPathToModule = dotFriendlyResolve(args.path, args.resolveDir)
|
|
179
|
+
fullPathToModule = dotFriendlyResolve(args.path, args.resolveDir, args.kind === 'import-statement')
|
|
179
180
|
} catch (err) {
|
|
180
181
|
if (DEBUG) {
|
|
181
182
|
console.warn(`Warning: Unable to find "${args.path}".` +
|
|
@@ -205,8 +206,25 @@ ${build.initialOptions.banner.js}`
|
|
|
205
206
|
if (args.namespace === 'file' && (
|
|
206
207
|
modulesOfInterest.has(args.path) || modulesOfInterest.has(`${extracted.pkg}/${extracted.path}`))
|
|
207
208
|
) {
|
|
209
|
+
// Internal module like http/fs is imported and the build output is ESM
|
|
210
|
+
if (internal && args.kind === 'import-statement' && esmBuild && !interceptedESMModules.has(fullPathToModule)) {
|
|
211
|
+
fullPathToModule = `${INTERNAL_ESM_INTERCEPTED_PREFIX}${fullPathToModule}${ESM_INTERCEPTED_SUFFIX}`
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
path: fullPathToModule,
|
|
215
|
+
pluginData: {
|
|
216
|
+
pkg: extracted?.pkg,
|
|
217
|
+
path: extracted?.path,
|
|
218
|
+
full: fullPathToModule,
|
|
219
|
+
raw: args.path,
|
|
220
|
+
pkgOfInterest: true,
|
|
221
|
+
kind: args.kind,
|
|
222
|
+
internal,
|
|
223
|
+
isESM: true
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
208
227
|
// The file namespace is used when requiring files from disk in userland
|
|
209
|
-
|
|
210
228
|
let pathToPackageJson
|
|
211
229
|
try {
|
|
212
230
|
// we can't use require.resolve('pkg/package.json') as ESM modules don't make the file available
|
|
@@ -228,6 +246,11 @@ ${build.initialOptions.banner.js}`
|
|
|
228
246
|
|
|
229
247
|
const packageJson = JSON.parse(fs.readFileSync(pathToPackageJson).toString())
|
|
230
248
|
|
|
249
|
+
const isESM = isESMFile(fullPathToModule, pathToPackageJson, packageJson)
|
|
250
|
+
if (isESM && !interceptedESMModules.has(fullPathToModule)) {
|
|
251
|
+
fullPathToModule += ESM_INTERCEPTED_SUFFIX
|
|
252
|
+
}
|
|
253
|
+
|
|
231
254
|
if (DEBUG) console.log(`RESOLVE: ${args.path}@${packageJson.version}`)
|
|
232
255
|
|
|
233
256
|
// https://esbuild.github.io/plugins/#on-resolve-arguments
|
|
@@ -240,13 +263,15 @@ ${build.initialOptions.banner.js}`
|
|
|
240
263
|
full: fullPathToModule,
|
|
241
264
|
raw: args.path,
|
|
242
265
|
pkgOfInterest: true,
|
|
243
|
-
|
|
266
|
+
kind: args.kind,
|
|
267
|
+
internal,
|
|
268
|
+
isESM
|
|
244
269
|
}
|
|
245
270
|
}
|
|
246
271
|
}
|
|
247
272
|
})
|
|
248
273
|
|
|
249
|
-
build.onLoad({ filter: /.*/ }, args => {
|
|
274
|
+
build.onLoad({ filter: /.*/ }, async args => {
|
|
250
275
|
if (args.pluginData?.pkgOfInterest) {
|
|
251
276
|
const data = args.pluginData
|
|
252
277
|
|
|
@@ -257,26 +282,63 @@ ${build.initialOptions.banner.js}`
|
|
|
257
282
|
: data.pkg
|
|
258
283
|
|
|
259
284
|
// Read the content of the module file of interest
|
|
260
|
-
|
|
285
|
+
let contents
|
|
286
|
+
|
|
287
|
+
if (data.isESM) {
|
|
288
|
+
if (args.path.endsWith(ESM_INTERCEPTED_SUFFIX)) {
|
|
289
|
+
args.path = args.path.slice(0, -1 * ESM_INTERCEPTED_SUFFIX.length)
|
|
290
|
+
|
|
291
|
+
if (data.internal) {
|
|
292
|
+
args.path = args.path.slice(INTERNAL_ESM_INTERCEPTED_PREFIX.length)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
interceptedESMModules.add(args.path)
|
|
296
|
+
|
|
297
|
+
const setters = await processModule({
|
|
298
|
+
path: args.path,
|
|
299
|
+
internal: data.internal,
|
|
300
|
+
context: { format: 'module' }
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
const iitmPath = require.resolve('import-in-the-middle/lib/register.js')
|
|
304
|
+
const toRegister = data.internal ? args.path : pathToFileURL(args.path)
|
|
305
|
+
// Mimic a Module object (https://tc39.es/ecma262/#sec-module-namespace-objects).
|
|
306
|
+
contents = `
|
|
307
|
+
import { register } from ${JSON.stringify(iitmPath)};
|
|
308
|
+
import * as namespace from ${JSON.stringify(args.path)};
|
|
309
|
+
const _ = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
|
|
310
|
+
const set = {};
|
|
311
|
+
const get = {};
|
|
312
|
+
|
|
313
|
+
${Array.from(setters.values()).join(';\n')};
|
|
314
|
+
|
|
315
|
+
register(${JSON.stringify(toRegister)}, _, set, get, ${JSON.stringify(data.raw)});
|
|
316
|
+
`
|
|
317
|
+
} else {
|
|
318
|
+
contents = fs.readFileSync(args.path, 'utf8')
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
const fileCode = fs.readFileSync(args.path, 'utf8')
|
|
322
|
+
contents = `
|
|
323
|
+
(function() {
|
|
324
|
+
${fileCode}
|
|
325
|
+
})(...arguments);
|
|
326
|
+
{
|
|
327
|
+
const dc = require('dc-polyfill');
|
|
328
|
+
const ch = dc.channel('${CHANNEL}');
|
|
329
|
+
const mod = module.exports
|
|
330
|
+
const payload = {
|
|
331
|
+
module: mod,
|
|
332
|
+
version: '${data.version}',
|
|
333
|
+
package: '${data.pkg}',
|
|
334
|
+
path: '${pkgPath}'
|
|
335
|
+
};
|
|
336
|
+
ch.publish(payload);
|
|
337
|
+
module.exports = payload.module;
|
|
338
|
+
}
|
|
339
|
+
`
|
|
340
|
+
}
|
|
261
341
|
|
|
262
|
-
const contents = `
|
|
263
|
-
(function() {
|
|
264
|
-
${fileCode}
|
|
265
|
-
})(...arguments);
|
|
266
|
-
{
|
|
267
|
-
const dc = require('dc-polyfill');
|
|
268
|
-
const ch = dc.channel('${CHANNEL}');
|
|
269
|
-
const mod = module.exports
|
|
270
|
-
const payload = {
|
|
271
|
-
module: mod,
|
|
272
|
-
version: '${data.version}',
|
|
273
|
-
package: '${data.pkg}',
|
|
274
|
-
path: '${pkgPath}'
|
|
275
|
-
};
|
|
276
|
-
ch.publish(payload);
|
|
277
|
-
module.exports = payload.module;
|
|
278
|
-
}
|
|
279
|
-
`
|
|
280
342
|
// https://esbuild.github.io/plugins/#on-load-results
|
|
281
343
|
return {
|
|
282
344
|
contents,
|
|
@@ -284,7 +346,6 @@ ${build.initialOptions.banner.js}`
|
|
|
284
346
|
resolveDir: path.dirname(args.path)
|
|
285
347
|
}
|
|
286
348
|
}
|
|
287
|
-
|
|
288
349
|
if (DD_IAST_ENABLED && args.pluginData?.applicationFile) {
|
|
289
350
|
const ext = path.extname(args.path).toLowerCase()
|
|
290
351
|
const isJs = /^\.(js|mjs|cjs)$/.test(ext)
|
|
@@ -303,12 +364,19 @@ ${build.initialOptions.banner.js}`
|
|
|
303
364
|
}
|
|
304
365
|
|
|
305
366
|
// @see https://github.com/nodejs/node/issues/47000
|
|
306
|
-
function dotFriendlyResolve (path, directory) {
|
|
367
|
+
function dotFriendlyResolve (path, directory, usesImportStatement) {
|
|
307
368
|
if (path === '.') {
|
|
308
369
|
path = './'
|
|
309
370
|
} else if (path === '..') {
|
|
310
371
|
path = '../'
|
|
311
372
|
}
|
|
373
|
+
let conditions
|
|
374
|
+
if (usesImportStatement) {
|
|
375
|
+
conditions = new Set(['import', 'node'])
|
|
376
|
+
}
|
|
312
377
|
|
|
313
|
-
|
|
378
|
+
if (path.startsWith('file://')) {
|
|
379
|
+
path = fileURLToPath(path)
|
|
380
|
+
}
|
|
381
|
+
return require.resolve(path, { paths: [directory], conditions })
|
|
314
382
|
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// The content of this file is copied from the `import-in-the-middle` package with minor modifications (https://www.npmjs.com/package/import-in-the-middle)
|
|
4
|
+
const { pathToFileURL, fileURLToPath } = require('node:url')
|
|
5
|
+
const fs = require('node:fs')
|
|
6
|
+
const path = require('node:path')
|
|
7
|
+
const { NODE_MAJOR, NODE_MINOR } = require('../../../version.js')
|
|
8
|
+
|
|
9
|
+
const getExportsImporting = (url) => import(url).then(Object.keys)
|
|
10
|
+
const getExports = NODE_MAJOR >= 20 || (NODE_MAJOR === 18 && NODE_MINOR >= 19)
|
|
11
|
+
? require('import-in-the-middle/lib/get-exports.js')
|
|
12
|
+
: getExportsImporting
|
|
13
|
+
|
|
14
|
+
function isStarExportLine (line) {
|
|
15
|
+
return /^\* from /.test(line)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isBareSpecifier (specifier) {
|
|
19
|
+
// Relative and absolute paths are not bare specifiers.
|
|
20
|
+
if (
|
|
21
|
+
specifier.startsWith('.') ||
|
|
22
|
+
specifier.startsWith('/')) {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Valid URLs are not bare specifiers. (file:, http:, node:, etc.)
|
|
27
|
+
|
|
28
|
+
if (URL.hasOwnProperty('canParse')) {
|
|
29
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
30
|
+
return !URL.canParse(specifier)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// eslint-disable-next-line no-new
|
|
35
|
+
new URL(specifier)
|
|
36
|
+
return false
|
|
37
|
+
} catch {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolve (specifier, context) {
|
|
43
|
+
// This comes from an import, that is why import makes preference
|
|
44
|
+
const conditions = ['import']
|
|
45
|
+
|
|
46
|
+
if (specifier.startsWith('file://')) {
|
|
47
|
+
specifier = fileURLToPath(specifier)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const resolved = require.resolve(specifier, { conditions, paths: [fileURLToPath(context.parentURL)] })
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
url: pathToFileURL(resolved),
|
|
54
|
+
format: isESMFile(resolved) ? 'module' : 'commonjs'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getSource (url, { format }) {
|
|
59
|
+
return {
|
|
60
|
+
source: fs.readFileSync(fileURLToPath(url), 'utf8'),
|
|
61
|
+
format
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generates the pieces of code for the proxy module before the path
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} moduleData { path, internal, context, excludeDefault }
|
|
69
|
+
* @returns {Promise<Map>}
|
|
70
|
+
*/
|
|
71
|
+
async function processModule ({ path, internal, context, excludeDefault }) {
|
|
72
|
+
let exportNames, srcUrl
|
|
73
|
+
if (internal) {
|
|
74
|
+
// we can not read and parse of internal modules
|
|
75
|
+
exportNames = await getExportsImporting(path)
|
|
76
|
+
} else {
|
|
77
|
+
srcUrl = pathToFileURL(path)
|
|
78
|
+
exportNames = await getExports(srcUrl, context, getSource)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const starExports = new Set()
|
|
82
|
+
const setters = new Map()
|
|
83
|
+
|
|
84
|
+
const addSetter = (name, setter, isStarExport = false) => {
|
|
85
|
+
if (setters.has(name)) {
|
|
86
|
+
if (isStarExport) {
|
|
87
|
+
// If there's already a matching star export, delete it
|
|
88
|
+
if (starExports.has(name)) {
|
|
89
|
+
setters.delete(name)
|
|
90
|
+
}
|
|
91
|
+
// and return so this is excluded
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// if we already have this export but it is from a * export, overwrite it
|
|
96
|
+
if (starExports.has(name)) {
|
|
97
|
+
starExports.delete(name)
|
|
98
|
+
setters.set(name, setter)
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
// Store export * exports so we know they can be overridden by explicit
|
|
102
|
+
// named exports
|
|
103
|
+
if (isStarExport) {
|
|
104
|
+
starExports.add(name)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setters.set(name, setter)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const n of exportNames) {
|
|
112
|
+
if (n === 'default' && excludeDefault) continue
|
|
113
|
+
|
|
114
|
+
if (isStarExportLine(n) === true) {
|
|
115
|
+
// export * from 'wherever'
|
|
116
|
+
const [, modFile] = n.split('* from ')
|
|
117
|
+
|
|
118
|
+
// Relative paths need to be resolved relative to the parent module
|
|
119
|
+
const newSpecifier = isBareSpecifier(modFile) ? modFile : new URL(modFile, srcUrl).href
|
|
120
|
+
// We need to call `parentResolve` to resolve bare specifiers to a full
|
|
121
|
+
// URL. We also need to call `parentResolve` for all sub-modules to get
|
|
122
|
+
// the `format`. We can't rely on the parents `format` to know if this
|
|
123
|
+
// sub-module is ESM or CJS!
|
|
124
|
+
|
|
125
|
+
const result = resolve(newSpecifier, { parentURL: srcUrl })
|
|
126
|
+
|
|
127
|
+
// eslint-disable-next-line no-await-in-loop
|
|
128
|
+
const subSetters = await processModule({
|
|
129
|
+
path: fileURLToPath(result.url),
|
|
130
|
+
context: { ...context, format: result.format },
|
|
131
|
+
excludeDefault: true
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
for (const [name, setter] of subSetters.entries()) {
|
|
135
|
+
addSetter(name, setter, true)
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
const variableName = `$${n.replaceAll(/[^a-zA-Z0-9_$]/g, '_')}`
|
|
139
|
+
const objectKey = JSON.stringify(n)
|
|
140
|
+
const reExportedName = n === 'default' ? n : objectKey
|
|
141
|
+
|
|
142
|
+
addSetter(n, `
|
|
143
|
+
let ${variableName}
|
|
144
|
+
try {
|
|
145
|
+
${variableName} = _[${objectKey}] = namespace[${objectKey}]
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (!(err instanceof ReferenceError)) throw err
|
|
148
|
+
}
|
|
149
|
+
export { ${variableName} as ${reExportedName} }
|
|
150
|
+
set[${objectKey}] = (v) => {
|
|
151
|
+
${variableName} = v
|
|
152
|
+
return true
|
|
153
|
+
}
|
|
154
|
+
get[${objectKey}] = () => ${variableName}
|
|
155
|
+
`)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return setters
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Determines if a file is a ESM module or CommonJS
|
|
164
|
+
*
|
|
165
|
+
* @param {string} fullPathToModule File to analize
|
|
166
|
+
* @param {string} [modulePackageJsonPath] Path of the package.json
|
|
167
|
+
* @param {Object} [packageJson] The content of the module package.json
|
|
168
|
+
* @returns {boolean}
|
|
169
|
+
*/
|
|
170
|
+
function isESMFile (fullPathToModule, modulePackageJsonPath, packageJson = {}) {
|
|
171
|
+
if (fullPathToModule.endsWith('.mjs')) return true
|
|
172
|
+
if (fullPathToModule.endsWith('.cjs')) return false
|
|
173
|
+
|
|
174
|
+
const pathParts = fullPathToModule.split(path.sep)
|
|
175
|
+
do {
|
|
176
|
+
pathParts.pop()
|
|
177
|
+
|
|
178
|
+
const packageJsonPath = [...pathParts, 'package.json'].join(path.sep)
|
|
179
|
+
if (packageJsonPath === modulePackageJsonPath) {
|
|
180
|
+
return packageJson.type === 'module'
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const packageJsonContent = fs.readFileSync(packageJsonPath).toString()
|
|
185
|
+
const packageJson = JSON.parse(packageJsonContent)
|
|
186
|
+
return packageJson.type === 'module'
|
|
187
|
+
} catch {
|
|
188
|
+
// file does not exit, continue
|
|
189
|
+
}
|
|
190
|
+
} while (pathParts.length > 0)
|
|
191
|
+
|
|
192
|
+
return packageJson.type === 'module'
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
processModule,
|
|
197
|
+
isESMFile
|
|
198
|
+
}
|
|
@@ -3,8 +3,18 @@
|
|
|
3
3
|
const { createWrapRouterMethod } = require('./router')
|
|
4
4
|
const shimmer = require('../../datadog-shimmer')
|
|
5
5
|
const { addHook, channel, tracingChannel } = require('./helpers/instrument')
|
|
6
|
+
const {
|
|
7
|
+
setRouterMountPath,
|
|
8
|
+
markAppMounted,
|
|
9
|
+
normalizeRoutePaths,
|
|
10
|
+
wrapRouteMethodsAndPublish,
|
|
11
|
+
extractMountPaths,
|
|
12
|
+
hasRouterCycle,
|
|
13
|
+
collectRoutesFromRouter
|
|
14
|
+
} = require('./helpers/router-helper')
|
|
6
15
|
|
|
7
16
|
const handleChannel = channel('apm:express:request:handle')
|
|
17
|
+
const routeAddedChannel = channel('apm:express:route:added')
|
|
8
18
|
|
|
9
19
|
function wrapHandle (handle) {
|
|
10
20
|
return function handleWithTrace (req, res) {
|
|
@@ -56,8 +66,80 @@ function wrapResponseRender (render) {
|
|
|
56
66
|
}
|
|
57
67
|
}
|
|
58
68
|
|
|
69
|
+
function wrapAppAll (all) {
|
|
70
|
+
return function wrappedAll (path, ...otherArgs) {
|
|
71
|
+
if (!routeAddedChannel.hasSubscribers) return all.call(this, path, ...otherArgs)
|
|
72
|
+
|
|
73
|
+
const paths = normalizeRoutePaths(path)
|
|
74
|
+
|
|
75
|
+
for (const p of paths) {
|
|
76
|
+
routeAddedChannel.publish({ method: '*', path: p })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return all.call(this, path, ...otherArgs)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Wrap app.route() to instrument Route object
|
|
84
|
+
function wrapAppRoute (route) {
|
|
85
|
+
return function wrappedRoute (path, ...otherArgs) {
|
|
86
|
+
const routeObj = route.call(this, path, ...otherArgs)
|
|
87
|
+
|
|
88
|
+
if (!routeAddedChannel.hasSubscribers) return routeObj
|
|
89
|
+
|
|
90
|
+
const paths = normalizeRoutePaths(path)
|
|
91
|
+
|
|
92
|
+
if (!paths.length) return routeObj
|
|
93
|
+
|
|
94
|
+
wrapRouteMethodsAndPublish(routeObj, paths, ({ method, path }) => {
|
|
95
|
+
routeAddedChannel.publish({ method, path })
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
return routeObj
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function wrapAppUse (use) {
|
|
103
|
+
return function wrappedUse (...args) {
|
|
104
|
+
if (!args.length) return use.call(this)
|
|
105
|
+
|
|
106
|
+
// Get mount argument and use it to register each router against the exact paths Express will use.
|
|
107
|
+
const { mountPaths, startIdx } = extractMountPaths(args[0])
|
|
108
|
+
const pathsToRegister = mountPaths.length ? mountPaths : ['/']
|
|
109
|
+
|
|
110
|
+
for (let i = startIdx; i < args.length; i++) {
|
|
111
|
+
const router = args[i]
|
|
112
|
+
|
|
113
|
+
if (!router || typeof router !== 'function') continue
|
|
114
|
+
|
|
115
|
+
markAppMounted(router)
|
|
116
|
+
|
|
117
|
+
// Avoid enumerating routes for routers that contain cycles.
|
|
118
|
+
// Express will refuse those at runtime, but collecting them here could loop forever.
|
|
119
|
+
let skipCollection = false
|
|
120
|
+
if (routeAddedChannel.hasSubscribers) {
|
|
121
|
+
skipCollection = hasRouterCycle(router)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const mountPath of pathsToRegister) {
|
|
125
|
+
const normalizedMountPath = mountPath || '/'
|
|
126
|
+
setRouterMountPath(router, normalizedMountPath)
|
|
127
|
+
|
|
128
|
+
if (!skipCollection && routeAddedChannel.hasSubscribers) {
|
|
129
|
+
collectRoutesFromRouter(router, normalizedMountPath)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return use.apply(this, args)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
59
138
|
addHook({ name: 'express', versions: ['>=4'], file: ['lib/express.js'] }, express => {
|
|
60
139
|
shimmer.wrap(express.application, 'handle', wrapHandle)
|
|
140
|
+
shimmer.wrap(express.application, 'all', wrapAppAll)
|
|
141
|
+
shimmer.wrap(express.application, 'route', wrapAppRoute)
|
|
142
|
+
shimmer.wrap(express.application, 'use', wrapAppUse)
|
|
61
143
|
|
|
62
144
|
shimmer.wrap(express.response, 'json', wrapResponseJson)
|
|
63
145
|
shimmer.wrap(express.response, 'jsonp', wrapResponseJson)
|