dd-trace 5.73.0 → 5.75.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.
Files changed (37) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +39 -7
  3. package/loader-hook.mjs +52 -1
  4. package/package.json +8 -16
  5. package/packages/datadog-core/src/utils/src/set.js +5 -1
  6. package/packages/datadog-esbuild/index.js +105 -36
  7. package/packages/datadog-esbuild/src/utils.js +198 -0
  8. package/packages/datadog-instrumentations/src/cookie-parser.js +0 -2
  9. package/packages/datadog-instrumentations/src/cucumber.js +2 -2
  10. package/packages/datadog-instrumentations/src/express.js +82 -0
  11. package/packages/datadog-instrumentations/src/helpers/router-helper.js +238 -0
  12. package/packages/datadog-instrumentations/src/jest.js +2 -1
  13. package/packages/datadog-instrumentations/src/mariadb.js +9 -7
  14. package/packages/datadog-instrumentations/src/playwright.js +226 -93
  15. package/packages/datadog-instrumentations/src/router.js +63 -6
  16. package/packages/datadog-instrumentations/src/vitest.js +44 -12
  17. package/packages/datadog-instrumentations/src/ws.js +3 -3
  18. package/packages/datadog-plugin-aws-sdk/src/base.js +0 -1
  19. package/packages/datadog-plugin-express/src/code_origin.js +2 -0
  20. package/packages/datadog-plugin-playwright/src/index.js +74 -31
  21. package/packages/datadog-plugin-ws/src/close.js +1 -1
  22. package/packages/datadog-shimmer/src/shimmer.js +2 -0
  23. package/packages/dd-trace/src/aiguard/sdk.js +25 -3
  24. package/packages/dd-trace/src/aiguard/tags.js +4 -1
  25. package/packages/dd-trace/src/config-helper.js +4 -1
  26. package/packages/dd-trace/src/config.js +599 -592
  27. package/packages/dd-trace/src/config_defaults.js +14 -12
  28. package/packages/dd-trace/src/plugins/util/ci.js +3 -2
  29. package/packages/dd-trace/src/plugins/util/stacktrace.js +16 -1
  30. package/packages/dd-trace/src/proxy.js +1 -1
  31. package/packages/dd-trace/src/supported-configurations.json +1 -0
  32. package/packages/dd-trace/src/telemetry/endpoints.js +27 -1
  33. package/packages/dd-trace/src/telemetry/index.js +16 -13
  34. package/packages/dd-trace/src/telemetry/logs/log-collector.js +5 -3
  35. package/register.js +1 -11
  36. package/scripts/preinstall.js +3 -1
  37. package/version.js +2 -1
@@ -259,7 +259,7 @@ function wrapRun (pl, isLatestVersion, version) {
259
259
  testStartCh.runStores(ctx, () => {})
260
260
  const promises = {}
261
261
  try {
262
- this.eventBroadcaster.on('envelope', shimmer.wrapFunction(null, () => async (testCase) => {
262
+ this.eventBroadcaster.on('envelope', async (testCase) => {
263
263
  // Only supported from >=8.0.0
264
264
  if (testCase?.testCaseFinished) {
265
265
  const { testCaseFinished: { willBeRetried } } = testCase
@@ -289,7 +289,7 @@ function wrapRun (pl, isLatestVersion, version) {
289
289
  testStartCh.runStores(newCtx, () => {})
290
290
  }
291
291
  }
292
- }))
292
+ })
293
293
  let promise
294
294
 
295
295
  testFnCh.runStores(ctx, () => {
@@ -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)
@@ -0,0 +1,238 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('./instrument')
4
+ const shimmer = require('../../../datadog-shimmer')
5
+
6
+ const routerMountPaths = new WeakMap() // to track mount paths for router instances
7
+ const layerMatchers = new WeakMap() // to store layer matchers
8
+ const appMountedRouters = new WeakSet() // to track routers mounted via app.use()
9
+
10
+ const METHODS = [...require('http').METHODS.map(v => v.toLowerCase()), 'all']
11
+
12
+ const routeAddedChannel = channel('apm:express:route:added')
13
+
14
+ /**
15
+ * Joins two URL path segments into a single path
16
+ *
17
+ * @param {string} base - The base path
18
+ * @param {string} path - The path to append
19
+ * @returns {string|null} The joined path or null if the combination would create an invalid route in Express
20
+ */
21
+ function joinPath (base, path) {
22
+ if (!base || base === '/') return path || '/'
23
+ if (!path || path === '/') return base
24
+
25
+ // Express does not normalize paths without leading slashes.
26
+ // If either path doesn't start with '/', we should skip this combination.
27
+ // Allow only empty string for path
28
+ if (path !== '' && !path.startsWith('/')) return null
29
+ if (!base.startsWith('/')) return null
30
+
31
+ // Handle duplicate slashes when base ends with / and path starts with /
32
+ if (base.endsWith('/') && path.startsWith('/')) {
33
+ return base + path.slice(1)
34
+ }
35
+
36
+ return base + path
37
+ }
38
+
39
+ // Normalize route definitions coming from Express into a string representation
40
+ function normalizeRoutePath (path) {
41
+ if (path == null) return null
42
+ if (typeof path === 'string') return path
43
+ if (path instanceof RegExp) return path.toString()
44
+
45
+ return String(path)
46
+ }
47
+
48
+ // Recursively publish every route reachable from the router.
49
+ function collectRoutesFromRouter (router, prefix) {
50
+ if (!router?.stack?.length) return
51
+
52
+ for (const layer of router.stack) {
53
+ if (layer.route) {
54
+ // This layer has a direct route
55
+ const route = layer.route
56
+
57
+ const fullPaths = getRouteFullPaths(route, prefix)
58
+
59
+ for (const fullPath of fullPaths) {
60
+ for (const [method, enabled] of Object.entries(route.methods || {})) {
61
+ if (!enabled) continue
62
+ routeAddedChannel.publish({
63
+ method: normalizeMethodName(method),
64
+ path: fullPath
65
+ })
66
+ }
67
+ }
68
+ } else if (layer.handle?.stack?.length) {
69
+ // This layer contains a nested router
70
+ // Extract mount path from layer
71
+ const mountPath = typeof layer.path === 'string'
72
+ ? layer.path
73
+ : getLayerMatchers(layer)?.[0]?.path || ''
74
+
75
+ const nestedPrefix = joinPath(prefix, mountPath)
76
+ if (nestedPrefix === null) continue
77
+
78
+ // Set the mount path for the nested router
79
+ setRouterMountPath(layer.handle, nestedPrefix)
80
+ markAppMounted(layer.handle)
81
+ // Recursively collect from nested routers
82
+ collectRoutesFromRouter(layer.handle, nestedPrefix)
83
+ }
84
+ }
85
+ }
86
+
87
+ // Flatten any route definition into an array of normalized path strings.
88
+ function normalizeRoutePaths (path) {
89
+ if (path == null) return []
90
+
91
+ if (Array.isArray(path) === false) {
92
+ const normalized = normalizeRoutePath(path)
93
+ return [normalized]
94
+ }
95
+
96
+ const paths = path.flat(Infinity)
97
+ const result = []
98
+ for (const _path of paths) {
99
+ const normalized = normalizeRoutePath(_path)
100
+ if (normalized !== null) {
101
+ result.push(normalized)
102
+ }
103
+ }
104
+
105
+ return result
106
+ }
107
+
108
+ function setRouterMountPath (router, mountPath) {
109
+ if (!router || typeof mountPath !== 'string') return
110
+ const existing = routerMountPaths.get(router)
111
+ if (existing) {
112
+ existing.add(mountPath)
113
+ } else {
114
+ routerMountPaths.set(router, new Set([mountPath]))
115
+ }
116
+ }
117
+
118
+ function getRouterMountPaths (router) {
119
+ const paths = routerMountPaths.get(router)
120
+ if (!paths) return []
121
+ return [...paths]
122
+ }
123
+
124
+ function setLayerMatchers (layer, matchers) {
125
+ layerMatchers.set(layer, matchers)
126
+ }
127
+
128
+ function getLayerMatchers (layer) {
129
+ return layerMatchers.get(layer)
130
+ }
131
+
132
+ function normalizeMethodName (method) {
133
+ return method === '_all' || method === 'all' ? '*' : method
134
+ }
135
+
136
+ function getRouteFullPaths (route, prefix) {
137
+ if (!route) return []
138
+
139
+ const routePaths = normalizeRoutePaths(route.path)
140
+ const pathsToPublish = routePaths.length ? routePaths : ['']
141
+
142
+ return pathsToPublish
143
+ .map(routePath => joinPath(prefix, routePath))
144
+ .filter(path => path !== null) // Filter out invalid path combinations
145
+ }
146
+
147
+ function markAppMounted (router) {
148
+ if (router) appMountedRouters.add(router)
149
+ }
150
+
151
+ function isAppMounted (router) {
152
+ return appMountedRouters.has(router)
153
+ }
154
+
155
+ /**
156
+ * Normalise the optional mount argument provided to app.use()/router.use().
157
+ * Express accepts strings, regex, arrays (possibly nested), or
158
+ * no mount path at all; this helper returns the flattened set of paths along
159
+ * with the index where actual middleware arguments start.
160
+ */
161
+ function extractMountPaths (path) {
162
+ const hasMount = typeof path === 'string' || path instanceof RegExp || Array.isArray(path)
163
+
164
+ if (!hasMount) {
165
+ return { mountPaths: ['/'], startIdx: 0 }
166
+ }
167
+
168
+ const paths = normalizeRoutePaths(path)
169
+ return {
170
+ mountPaths: paths.length ? paths : ['/'],
171
+ startIdx: 1
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Detect cycle router graphs.
177
+ */
178
+ function hasRouterCycle (router, stack = new Set()) {
179
+ if (!router?.stack?.length) return false
180
+ if (stack.has(router)) return true
181
+
182
+ stack.add(router)
183
+
184
+ for (const layer of router.stack) {
185
+ if (!layer?.route && layer?.handle?.stack?.length) {
186
+ const hasCycle = hasRouterCycle(layer.handle, stack)
187
+ if (hasCycle) {
188
+ return true
189
+ }
190
+ }
191
+ }
192
+
193
+ stack.delete(router)
194
+ return false
195
+ }
196
+
197
+ function wrapRouteMethodsAndPublish (route, paths, publish) {
198
+ if (!route || !paths.length) return
199
+
200
+ const filteredPaths = paths.filter(Boolean)
201
+ if (!filteredPaths.length) return
202
+
203
+ const uniquePaths = new Set(filteredPaths)
204
+
205
+ METHODS.forEach(method => {
206
+ if (typeof route[method] !== 'function') return
207
+
208
+ shimmer.wrap(route, method, (originalMethod) => function wrappedRouteMethod (...args) {
209
+ const normalizedMethod = normalizeMethodName(method)
210
+
211
+ for (const path of uniquePaths) {
212
+ publish({
213
+ method: normalizedMethod,
214
+ path
215
+ })
216
+ }
217
+
218
+ return originalMethod.apply(this, args)
219
+ })
220
+ })
221
+ }
222
+
223
+ module.exports = {
224
+ setRouterMountPath,
225
+ getRouterMountPaths,
226
+ joinPath,
227
+ setLayerMatchers,
228
+ getLayerMatchers,
229
+ markAppMounted,
230
+ isAppMounted,
231
+ normalizeRoutePath,
232
+ normalizeRoutePaths,
233
+ getRouteFullPaths,
234
+ wrapRouteMethodsAndPublish,
235
+ extractMountPaths,
236
+ hasRouterCycle,
237
+ collectRoutesFromRouter
238
+ }
@@ -1335,7 +1335,7 @@ addHook({
1335
1335
  }
1336
1336
  const returnedValue = requireModuleOrMock.apply(this, arguments)
1337
1337
  if (process.exitCode === 1) {
1338
- if (this.loggedReferenceErrors.size > 0) {
1338
+ if (this.loggedReferenceErrors?.size > 0) {
1339
1339
  const errorMessage = [...this.loggedReferenceErrors][0]
1340
1340
  testSuiteErrorCh.publish({
1341
1341
  errorMessage,
@@ -1427,6 +1427,7 @@ function enqueueWrapper (enqueue) {
1427
1427
  if (worker && !wrappedWorkers.has(worker)) {
1428
1428
  shimmer.wrap(worker._child, 'send', sendWrapper)
1429
1429
  shimmer.wrap(worker, '_onMessage', onMessageWrapper)
1430
+ worker._child.removeAllListeners('message')
1430
1431
  worker._child.on('message', worker._onMessage.bind(worker))
1431
1432
  wrappedWorkers.add(worker)
1432
1433
  }
@@ -82,12 +82,7 @@ function createWrapQueryCallback (options) {
82
82
 
83
83
  const cb = arguments[arguments.length - 1]
84
84
  const ctx = { sql, conf: options }
85
-
86
- if (typeof cb !== 'function') {
87
- arguments.length += 1
88
- }
89
-
90
- arguments[arguments.length - 1] = shimmer.wrapFunction(cb, cb => function (err) {
85
+ const wrapper = (cb) => function (err) {
91
86
  if (err) {
92
87
  ctx.error = err
93
88
  errorCh.publish(ctx)
@@ -96,7 +91,14 @@ function createWrapQueryCallback (options) {
96
91
  return typeof cb === 'function'
97
92
  ? finishCh.runStores(ctx, cb, this, ...arguments)
98
93
  : finishCh.publish(ctx)
99
- })
94
+ }
95
+
96
+ if (typeof cb === 'function') {
97
+ arguments[arguments.length - 1] = shimmer.wrapFunction(cb, wrapper)
98
+ } else {
99
+ arguments.length += 1
100
+ arguments[arguments.length - 1] = wrapper()
101
+ }
100
102
 
101
103
  return startCh.runStores(ctx, query, this, ...arguments)
102
104
  }