dd-trace 6.9.0 → 6.10.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 (59) hide show
  1. package/ci/vitest-no-worker-init-setup.mjs +112 -10
  2. package/index.d.ts +42 -0
  3. package/package.json +4 -3
  4. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  5. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +629 -0
  6. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
  7. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  10. package/packages/datadog-instrumentations/src/jest.js +1 -1
  11. package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -8
  13. package/packages/datadog-instrumentations/src/mysql.js +45 -15
  14. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  15. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  16. package/packages/datadog-instrumentations/src/pg.js +116 -92
  17. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
  18. package/packages/datadog-instrumentations/src/vitest-main.js +169 -17
  19. package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
  20. package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
  21. package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
  22. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  23. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -5
  24. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  25. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -1
  26. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  27. package/packages/datadog-plugin-jest/src/index.js +5 -4
  28. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  29. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  30. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
  31. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  32. package/packages/datadog-plugin-mocha/src/index.js +550 -39
  33. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  34. package/packages/datadog-plugin-next/src/index.js +1 -19
  35. package/packages/datadog-plugin-openai-agents/src/integration.js +278 -69
  36. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  37. package/packages/datadog-plugin-pg/src/index.js +1 -0
  38. package/packages/datadog-plugin-vitest/src/index.js +8 -20
  39. package/packages/datadog-plugin-ws/src/util.js +2 -1
  40. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  41. package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
  42. package/packages/dd-trace/src/carrier.js +356 -0
  43. package/packages/dd-trace/src/config/generated-config-types.d.ts +3 -0
  44. package/packages/dd-trace/src/config/supported-configurations.json +10 -0
  45. package/packages/dd-trace/src/datastreams/context.js +6 -1
  46. package/packages/dd-trace/src/datastreams/manager.js +5 -1
  47. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  48. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  49. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  50. package/packages/dd-trace/src/llmobs/index.js +34 -2
  51. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  52. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  53. package/packages/dd-trace/src/llmobs/util.js +78 -0
  54. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  55. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  56. package/packages/dd-trace/src/plugin_manager.js +2 -0
  57. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +1 -62
  58. package/packages/dd-trace/src/plugins/util/test.js +84 -5
  59. package/packages/dd-trace/src/plugins/util/web.js +53 -2
@@ -172,4 +172,85 @@ module.exports = [
172
172
  },
173
173
  channelName: 'testFrameworkFnWrapper',
174
174
  },
175
+ {
176
+ module: {
177
+ name: '@wdio/utils',
178
+ versionRange: '>=9.0.0',
179
+ filePath: 'build/index.js',
180
+ },
181
+ functionQuery: {
182
+ functionName: 'executeAsync',
183
+ kind: 'Async',
184
+ },
185
+ channelName: 'executeAsync',
186
+ },
187
+ {
188
+ module: {
189
+ name: '@wdio/utils',
190
+ versionRange: '>=9.0.0',
191
+ filePath: 'build/index.js',
192
+ },
193
+ astQuery: 'FunctionDeclaration[id.name="executeAsync"] CatchClause ' +
194
+ 'IfStatement[test.operator=">"][test.left.object.name="retries"]' +
195
+ '[test.left.property.name="limit"][test.right.object.name="retries"]' +
196
+ '[test.right.property.name="attempts"]',
197
+ channelName: 'executeAsync',
198
+ transform: 'awaitContextCallback',
199
+ transformOptions: {
200
+ callbackArgumentNames: ['err'],
201
+ callbackName: 'retryCallback',
202
+ },
203
+ },
204
+ {
205
+ module: {
206
+ name: 'jasmine-core',
207
+ versionRange: '>=5.0.0 <6.0.0',
208
+ filePath: 'lib/jasmine-core/jasmine.js',
209
+ },
210
+ astQuery: 'AssignmentExpression[left.object.object.name="Spec"]' +
211
+ '[left.object.property.name="prototype"][left.property.name="execute"] > FunctionExpression',
212
+ functionQuery: {
213
+ kind: 'Sync',
214
+ },
215
+ channelName: 'Spec_execute',
216
+ },
217
+ {
218
+ module: {
219
+ name: 'jasmine-core',
220
+ versionRange: '>=5.0.0 <6.0.0',
221
+ filePath: 'lib/jasmine-core/jasmine.js',
222
+ },
223
+ astQuery: 'AssignmentExpression[left.object.object.name="Spec"]' +
224
+ '[left.object.property.name="prototype"][left.property.name="status"] > FunctionExpression',
225
+ functionQuery: {
226
+ kind: 'Sync',
227
+ },
228
+ channelName: 'Spec_attemptDone',
229
+ },
230
+ {
231
+ module: {
232
+ name: 'jasmine-core',
233
+ versionRange: '>=5.0.0 <6.0.0',
234
+ filePath: 'lib/jasmine-core/jasmine.js',
235
+ },
236
+ functionQuery: {
237
+ className: 'Spec',
238
+ methodName: 'executionFinished',
239
+ kind: 'Sync',
240
+ },
241
+ channelName: 'Spec_attemptDone',
242
+ },
243
+ {
244
+ module: {
245
+ name: 'jasmine-core',
246
+ versionRange: '>=5.0.0 <6.0.0',
247
+ filePath: 'lib/jasmine-core/jasmine.js',
248
+ },
249
+ functionQuery: {
250
+ className: 'TreeRunner',
251
+ methodName: '_executeSpec',
252
+ kind: 'Sync',
253
+ },
254
+ channelName: 'Spec_execute',
255
+ },
175
256
  ]
@@ -54,6 +54,7 @@
54
54
  "graphql/utilities/index.mjs": "graphql",
55
55
  "graphql/validation/validate.js": "graphql",
56
56
  "graphql/validation/validate.mjs": "graphql",
57
+ "jasmine-core/lib/jasmine-core/jasmine.js": "jasmine-core",
57
58
  "mercurius/index.js": "mercurius",
58
59
  "playwright-core/lib/coreBundle.js": "playwright-core",
59
60
  "playwright/lib/index.js": "playwright",
@@ -14,7 +14,89 @@ const clone = require('../../../../../vendor/dist/rfdc')({ proto: false, circles
14
14
 
15
15
  const { parse, query } = require('./compiler')
16
16
 
17
- module.exports = { waitForAsyncEnd }
17
+ const functionTypes = new Set(['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'])
18
+ const identifierPattern = /^[$A-Z_a-z][$\w]*$/
19
+
20
+ module.exports = { awaitContextCallback, waitForAsyncEnd }
21
+
22
+ /**
23
+ * Awaits an optional context callback before continuing through a matched conditional branch.
24
+ *
25
+ * The branch condition is checked again after the callback settles so the
26
+ * original body does not run against state that changed while awaiting.
27
+ *
28
+ * @param {{
29
+ * transformOptions?: {
30
+ * callbackArgumentNames?: string[],
31
+ * callbackName?: string
32
+ * }
33
+ * }} state
34
+ * @param {import('estree').IfStatement} node
35
+ * @param {import('estree').Node} _parent
36
+ * @param {import('estree').Node[]} ancestry
37
+ * @returns {void}
38
+ */
39
+ function awaitContextCallback (state, node, _parent, ancestry) {
40
+ assert(node.type === 'IfStatement' && node.consequent?.type === 'BlockStatement',
41
+ 'awaitContextCallback: expected an if statement with a block body')
42
+
43
+ const { callbackArgumentNames = [], callbackName } = state.transformOptions ?? {}
44
+
45
+ assert(identifierPattern.test(callbackName), 'awaitContextCallback: callbackName must be an identifier')
46
+ assert(callbackArgumentNames.every(name => identifierPattern.test(name)),
47
+ 'awaitContextCallback: callbackArgumentNames must be identifiers')
48
+
49
+ let enclosingFunction
50
+ let hasTraceWrapper = false
51
+ for (const ancestor of ancestry) {
52
+ if (!functionTypes.has(ancestor.type)) continue
53
+
54
+ enclosingFunction ??= ancestor
55
+ if (ancestor.body?.type !== 'BlockStatement') continue
56
+
57
+ let hasContextBinding = false
58
+ let hasTracedBinding = false
59
+ for (const statement of ancestor.body.body) {
60
+ if (statement.type !== 'VariableDeclaration') continue
61
+
62
+ for (const declaration of statement.declarations) {
63
+ hasContextBinding ||= declaration.id?.name === '__apm$ctx'
64
+ hasTracedBinding ||= declaration.id?.name === '__apm$traced'
65
+ }
66
+ }
67
+ hasTraceWrapper ||= hasContextBinding && hasTracedBinding
68
+ }
69
+
70
+ assert(enclosingFunction?.async, 'awaitContextCallback: expected an enclosing async function')
71
+ assert(hasTraceWrapper, 'awaitContextCallback: expected an enclosing trace wrapper')
72
+
73
+ const callbackVariable = `__apm$${callbackName}`
74
+ if (query(node, `[id.name="${callbackVariable}"]`).length > 0) {
75
+ return
76
+ }
77
+
78
+ const originalStatements = node.consequent.body
79
+ const callbackStatements = parse(`
80
+ async function wrapper () {
81
+ const ${callbackVariable} = __apm$ctx.${callbackName};
82
+ if (typeof ${callbackVariable} === 'function') {
83
+ try {
84
+ await ${callbackVariable}(${callbackArgumentNames.join(', ')});
85
+ } catch {}
86
+ if (true) {}
87
+ } else {
88
+ }
89
+ }
90
+ `).body[0].body.body
91
+
92
+ const callbackBranch = callbackStatements[1]
93
+ const recheckedBranch = callbackBranch.consequent.body[1]
94
+ recheckedBranch.test = clone(node.test)
95
+ recheckedBranch.consequent.body.push(...clone(originalStatements))
96
+ recheckedBranch.alternate = clone(node.alternate)
97
+ callbackBranch.alternate.body.push(...originalStatements)
98
+ node.consequent.body = callbackStatements
99
+ }
18
100
 
19
101
  /**
20
102
  * Injects settlement-specific asyncEnd waits into a generated `tracePromise`
@@ -49,7 +49,7 @@ const {
49
49
  getRawJestTestName,
50
50
  getJestSuitesToRun,
51
51
  removeSeedSuffixFromTestName,
52
- } = require('../../datadog-plugin-jest/src/util')
52
+ } = require('../../dd-trace/src/plugins/util/jest')
53
53
  const {
54
54
  addCoverageBackfillUntestedFiles,
55
55
  getCoverageBackfillFiles,
@@ -10,7 +10,6 @@ const {
10
10
  } = require('../helpers/channel')
11
11
  const { addHook, channel } = require('../helpers/instrument')
12
12
  const shimmer = require('../../../datadog-shimmer')
13
- const { isMarkedAsUnskippable } = require('../../../datadog-plugin-jest/src/util')
14
13
  const { EMPTY_EFD_RETRY_POLICY } = require('../../../dd-trace/src/ci-visibility/efd-retry-policy')
15
14
  const { writeCoverageBackfillToCache } = require('../../../dd-trace/src/ci-visibility/test-optimization-cache')
16
15
  const log = require('../../../dd-trace/src/log')
@@ -33,6 +32,7 @@ const {
33
32
  TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE,
34
33
  getTestOptimizationRequestResults,
35
34
  isModifiedTest,
35
+ isMarkedAsUnskippable,
36
36
  } = require('../../../dd-trace/src/plugins/util/test')
37
37
 
38
38
  const {
@@ -11,6 +11,7 @@ const {
11
11
  const {
12
12
  getTestSuitePath,
13
13
  DYNAMIC_NAME_RE,
14
+ getFailedTestReplayPromise,
14
15
  recordTestManagementExecution,
15
16
  recordAttemptToFixExecution,
16
17
  logAttemptToFixTestExecution,
@@ -953,14 +954,7 @@ function getOnTestRetryHandler (config) {
953
954
  promises,
954
955
  ...ctx.currentStore,
955
956
  })
956
- if (promises.setProbePromise && promises.finishTestPromise) {
957
- test._ddFailedTestReplayPromise = Promise.all([
958
- promises.setProbePromise,
959
- promises.finishTestPromise,
960
- ]).then(() => {})
961
- } else if (promises.setProbePromise || promises.finishTestPromise) {
962
- test._ddFailedTestReplayPromise = promises.setProbePromise || promises.finishTestPromise
963
- }
957
+ test._ddFailedTestReplayPromise = getFailedTestReplayPromise(promises)
964
958
  }
965
959
  const key = getTestToContextKey(test)
966
960
  testToContext.delete(key)
@@ -2,6 +2,13 @@
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
4
  const { channel, addHook } = require('./helpers/instrument')
5
+ const {
6
+ dispatchesAcquireSynchronously,
7
+ takePoolWaitTime,
8
+ wrapPoolClusterQueryMethod,
9
+ wrapPoolGetConnection,
10
+ wrapPoolQueryMethod,
11
+ } = require('./helpers/pool-acquire')
5
12
 
6
13
  addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connection => {
7
14
  const startCh = channel('apm:mysql:query:start')
@@ -17,6 +24,11 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
17
24
  const conf = this.config
18
25
  const ctx = { sql, conf }
19
26
 
27
+ const waitTime = takePoolWaitTime(this)
28
+ if (waitTime !== undefined) {
29
+ ctx.poolWaitTime = waitTime
30
+ }
31
+
20
32
  return startCh.runStores(ctx, () => {
21
33
  if (args[0].sql) {
22
34
  args[0].sql = ctx.sql
@@ -61,20 +73,24 @@ addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
61
73
  const connectionFinishCh = channel('apm:mysql:connection:finish')
62
74
  const startPoolQueryCh = channel('datadog:mysql:pool:query:start')
63
75
  const finishPoolQueryCh = channel('datadog:mysql:pool:query:finish')
64
-
65
- shimmer.wrap(Pool.prototype, 'getConnection', getConnection => function (cb) {
66
- arguments[0] = function (...args) {
67
- return connectionFinishCh.runStores(ctx, cb, this, ...args)
68
- }
69
-
70
- const ctx = {}
71
-
72
- connectionStartCh.publish(ctx)
73
-
74
- return getConnection.apply(this, arguments)
75
- })
76
-
77
- shimmer.wrap(Pool.prototype, 'query', query => function (...args) {
76
+ const acquireStartCh = channel('apm:mysql:pool:acquire:start')
77
+ const acquireFinishCh = channel('apm:mysql:pool:acquire:finish')
78
+ const deferredPoolAcquire = !dispatchesAcquireSynchronously(
79
+ Pool.prototype.query,
80
+ Object.assign(Object.create(Pool.prototype), { config: { connectionConfig: {} } }),
81
+ 'getConnection',
82
+ ['SELECT 1', () => {}]
83
+ )
84
+
85
+ shimmer.wrap(Pool.prototype, 'getConnection', getConnection => wrapPoolGetConnection(getConnection, {
86
+ connectionStartCh,
87
+ connectionFinishCh,
88
+ acquireStartCh,
89
+ acquireFinishCh,
90
+ reentersQueuedCallbacks: true,
91
+ }, deferredPoolAcquire))
92
+
93
+ shimmer.wrap(Pool.prototype, 'query', query => wrapPoolQueryMethod(function (...args) {
78
94
  if (!startPoolQueryCh.hasSubscribers) {
79
95
  return query.apply(this, args)
80
96
  }
@@ -99,7 +115,21 @@ addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
99
115
 
100
116
  return retval
101
117
  })
102
- })
118
+ }, connectionStartCh, deferredPoolAcquire))
103
119
 
104
120
  return Pool
105
121
  })
122
+
123
+ // A `canRetry` failover re-invokes `query` with the same Query object. Key the acquire state by that
124
+ // object so a retry remains part of the query even if a future mysql version defers it.
125
+ addHook({ name: 'mysql', file: 'lib/PoolNamespace.js', versions: ['>=2'] }, PoolNamespace => {
126
+ const channels = {
127
+ connectionStartCh: channel('apm:mysql:connection:start'),
128
+ connectionFinishCh: channel('apm:mysql:connection:finish'),
129
+ acquireStartCh: channel('apm:mysql:pool:acquire:start'),
130
+ acquireFinishCh: channel('apm:mysql:pool:acquire:finish'),
131
+ }
132
+ shimmer.wrap(PoolNamespace.prototype, 'query', query => wrapPoolClusterQueryMethod(query, channels))
133
+
134
+ return PoolNamespace
135
+ })
@@ -5,10 +5,55 @@ const { errorMonitor } = require('node:events')
5
5
  const shimmer = require('../../datadog-shimmer')
6
6
  const satisfies = require('../../../vendor/dist/semifies')
7
7
  const { channel, addHook } = require('./helpers/instrument')
8
+ const {
9
+ dispatchesAcquireSynchronously,
10
+ takePoolWaitTime,
11
+ wrapPoolClusterGetConnection,
12
+ wrapPoolClusterMethod,
13
+ wrapPoolGetConnection,
14
+ wrapPoolQueryMethod,
15
+ } = require('./helpers/pool-acquire')
8
16
 
9
17
  /** @type {WeakMap<object, Function>} */
10
18
  const wrappedOnResult = new WeakMap()
11
19
 
20
+ /** @type {WeakSet<object>} */
21
+ const flagWrappedNamespaces = new WeakSet()
22
+
23
+ const connectionStartCh = channel('apm:mysql2:connection:start')
24
+ const connectionFinishCh = channel('apm:mysql2:connection:finish')
25
+ const acquireStartCh = channel('apm:mysql2:pool:acquire:start')
26
+ const acquireFinishCh = channel('apm:mysql2:pool:acquire:finish')
27
+ const poolAcquireChannels = {
28
+ connectionStartCh,
29
+ connectionFinishCh,
30
+ acquireStartCh,
31
+ acquireFinishCh,
32
+ }
33
+
34
+ /**
35
+ * Bracket a pool-cluster namespace's internal acquires and key retry state by its stable callback.
36
+ * @param {{ query?: Function, execute?: Function, getConnection?: Function }} poolNamespace
37
+ */
38
+ function flagWrapNamespace (poolNamespace) {
39
+ if (poolNamespace == null || flagWrappedNamespaces.has(poolNamespace)) return
40
+ flagWrappedNamespaces.add(poolNamespace)
41
+
42
+ if (typeof poolNamespace.query === 'function') {
43
+ shimmer.wrap(poolNamespace, 'query', query => wrapPoolClusterMethod(query, connectionStartCh))
44
+ }
45
+ if (typeof poolNamespace.execute === 'function') {
46
+ shimmer.wrap(poolNamespace, 'execute', execute => wrapPoolClusterMethod(execute, connectionStartCh))
47
+ }
48
+ if (typeof poolNamespace.getConnection === 'function') {
49
+ shimmer.wrap(
50
+ poolNamespace,
51
+ 'getConnection',
52
+ getConnection => wrapPoolClusterGetConnection(getConnection, poolAcquireChannels)
53
+ )
54
+ }
55
+ }
56
+
12
57
  /**
13
58
  * @param {unknown} sql
14
59
  * @returns {string|undefined}
@@ -43,6 +88,11 @@ function wrapConnection (Connection, version) {
43
88
  const ctx = {}
44
89
 
45
90
  if (isQuery) {
91
+ const waitTime = takePoolWaitTime(this)
92
+ if (waitTime !== undefined) {
93
+ ctx.poolWaitTime = waitTime
94
+ }
95
+
46
96
  command.execute = wrapExecute(command, command.execute, ctx, this.config)
47
97
 
48
98
  return commandAddCh.runStores(ctx, addCommand, this, ...arguments)
@@ -212,22 +262,41 @@ function wrapConnection (Connection, version) {
212
262
  * by the queued query attach to the caller rather than the previous query that freed the connection.
213
263
  *
214
264
  * @param {Function} Pool
265
+ * @param {boolean} deferredPoolAcquire
215
266
  * @returns {Function}
216
267
  */
217
- function wrapGetConnection (Pool) {
218
- const connectionStartCh = channel('apm:mysql2:connection:start')
219
- const connectionFinishCh = channel('apm:mysql2:connection:finish')
268
+ function wrapGetConnection (Pool, deferredPoolAcquire) {
269
+ shimmer.wrap(
270
+ Pool.prototype,
271
+ 'getConnection',
272
+ getConnection => wrapPoolGetConnection(getConnection, poolAcquireChannels, deferredPoolAcquire)
273
+ )
220
274
 
221
- shimmer.wrap(Pool.prototype, 'getConnection', getConnection => function (cb) {
222
- const ctx = {}
223
- arguments[0] = function (...args) {
224
- return connectionFinishCh.runStores(ctx, cb, this, ...args)
225
- }
226
-
227
- connectionStartCh.publish(ctx)
275
+ return Pool
276
+ }
228
277
 
229
- return getConnection.apply(this, arguments)
230
- })
278
+ /**
279
+ * mysql2 >=3.11.5 keeps the outer-query abort at the connection level, so the pool only needs the
280
+ * acquire wrap plus the pool-query flag around `query` / `execute`.
281
+ *
282
+ * @param {Function} Pool
283
+ * @returns {Function}
284
+ */
285
+ function wrapBasePool (Pool) {
286
+ const deferredQueryAcquire = defersPoolAcquire(Pool, 'query')
287
+ const deferredExecuteAcquire = defersPoolAcquire(Pool, 'execute')
288
+ wrapGetConnection(Pool, deferredQueryAcquire || deferredExecuteAcquire)
289
+
290
+ shimmer.wrap(
291
+ Pool.prototype,
292
+ 'query',
293
+ query => wrapPoolQueryMethod(query, connectionStartCh, deferredQueryAcquire)
294
+ )
295
+ shimmer.wrap(
296
+ Pool.prototype,
297
+ 'execute',
298
+ execute => wrapPoolQueryMethod(execute, connectionStartCh, deferredExecuteAcquire)
299
+ )
231
300
 
232
301
  return Pool
233
302
  }
@@ -240,10 +309,12 @@ function wrapGetConnection (Pool) {
240
309
  function wrapPool (Pool, version) {
241
310
  const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
242
311
  const shouldEmitEndAfterQueryAbort = satisfies(version, '>=1.3.3')
312
+ const deferredQueryAcquire = defersPoolAcquire(Pool, 'query')
313
+ const deferredExecuteAcquire = defersPoolAcquire(Pool, 'execute')
243
314
 
244
- wrapGetConnection(Pool)
315
+ wrapGetConnection(Pool, deferredQueryAcquire || deferredExecuteAcquire)
245
316
 
246
- shimmer.wrap(Pool.prototype, 'query', query => function (sql, values, cb) {
317
+ shimmer.wrap(Pool.prototype, 'query', query => wrapPoolQueryMethod(function (sql, values, cb) {
247
318
  if (!startOuterQueryCh.hasSubscribers) return query.apply(this, arguments)
248
319
 
249
320
  const resolvedSql = resolveSqlString(sql)
@@ -279,9 +350,9 @@ function wrapPool (Pool, version) {
279
350
  }
280
351
 
281
352
  return query.apply(this, arguments)
282
- })
353
+ }, connectionStartCh, deferredQueryAcquire))
283
354
 
284
- shimmer.wrap(Pool.prototype, 'execute', execute => function (sql, values, cb) {
355
+ shimmer.wrap(Pool.prototype, 'execute', execute => wrapPoolQueryMethod(function (sql, values, cb) {
285
356
  if (!startOuterQueryCh.hasSubscribers) return execute.apply(this, arguments)
286
357
 
287
358
  const resolvedSql = resolveSqlString(sql)
@@ -304,11 +375,25 @@ function wrapPool (Pool, version) {
304
375
  }
305
376
 
306
377
  return execute.apply(this, arguments)
307
- })
378
+ }, connectionStartCh, deferredExecuteAcquire))
308
379
 
309
380
  return Pool
310
381
  }
311
382
 
383
+ /**
384
+ * @param {Function} Pool
385
+ * @param {'query'|'execute'} method
386
+ * @returns {boolean}
387
+ */
388
+ function defersPoolAcquire (Pool, method) {
389
+ return !dispatchesAcquireSynchronously(
390
+ Pool.prototype[method],
391
+ Object.assign(Object.create(Pool.prototype), { config: { connectionConfig: {} } }),
392
+ 'getConnection',
393
+ ['SELECT 1', () => {}]
394
+ )
395
+ }
396
+
312
397
  /**
313
398
  * @param {Function} PoolCluster
314
399
  * @returns {Function}
@@ -319,6 +404,7 @@ function wrapPoolCluster (PoolCluster) {
319
404
 
320
405
  shimmer.wrap(PoolCluster.prototype, 'of', of => function (...args) {
321
406
  const poolNamespace = of.apply(this, args)
407
+ flagWrapNamespace(poolNamespace)
322
408
 
323
409
  if (startOuterQueryCh.hasSubscribers && !wrappedPoolNamespaces.has(poolNamespace)) {
324
410
  shimmer.wrap(poolNamespace, 'query', query => function (sql, values, cb) {
@@ -404,7 +490,7 @@ addHook(
404
490
  // mysql2 >=3.11.5 moved the pool onto BasePool in lib/base/pool.js.
405
491
  addHook(
406
492
  { name: 'mysql2', file: 'lib/base/pool.js', versions: ['>=3.11.5'] },
407
- /** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapGetConnection)
493
+ /** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapBasePool)
408
494
  )
409
495
 
410
496
  // PoolNamespace.prototype.query does not exist in mysql2<2.3.0
@@ -412,3 +498,18 @@ addHook(
412
498
  { name: 'mysql2', file: 'lib/pool_cluster.js', versions: ['2.3.0 - 3.11.4'] },
413
499
  /** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapPoolCluster)
414
500
  )
501
+
502
+ // mysql2 >=3.11.5 keeps the outer-query abort at the connection level, so a cluster namespace only
503
+ // needs the pool-query flag so its internal query/execute acquire does not open an acquire span.
504
+ addHook(
505
+ { name: 'mysql2', file: 'lib/pool_cluster.js', versions: ['>=3.11.5'] },
506
+ /** @type {(moduleExports: unknown, version: string) => unknown} */ (PoolCluster => {
507
+ shimmer.wrap(PoolCluster.prototype, 'of', of => function () {
508
+ const poolNamespace = of.apply(this, arguments)
509
+ flagWrapNamespace(poolNamespace)
510
+ return poolNamespace
511
+ })
512
+
513
+ return PoolCluster
514
+ })
515
+ )
@@ -5,6 +5,12 @@ const { addHook } = require('./helpers/instrument')
5
5
  /** @type {((pattern: string | RegExp) => RegExp | undefined) | undefined} */
6
6
  let compileToRegexp
7
7
 
8
+ /** @type {((pattern: string) => { tokens: object[] } | undefined) | undefined} */
9
+ let parseTokens
10
+
11
+ /** @type {((route: string) => ((url: string) => object | undefined) | undefined) | undefined} */
12
+ let makeMatcher
13
+
8
14
  addHook({ name: 'path-to-regexp', versions: ['*'] }, moduleExports => {
9
15
  // 0.1.x and 6.x: `module.exports = (path, ...) => RegExp`.
10
16
  // 7.x: `module.exports = { pathToRegexp(path, ...) => RegExp }`.
@@ -29,6 +35,47 @@ addHook({ name: 'path-to-regexp', versions: ['*'] }, moduleExports => {
29
35
  return moduleExports
30
36
  })
31
37
 
38
+ // 8.x only: earlier majors also expose `parse`/`match`, but with incompatible shapes — 7.x returns
39
+ // `{ tokens }` too, yet its tokens are bare strings rather than typed nodes. Gating on the version
40
+ // keeps those from installing adapters the consumers would misread.
41
+ addHook({ name: 'path-to-regexp', versions: ['>=8'] }, moduleExports => {
42
+ const { parse, match } = moduleExports
43
+
44
+ if (typeof parse === 'function' && typeof match === 'function') {
45
+ parseTokens = pattern => {
46
+ let result
47
+ try {
48
+ result = parse(pattern)
49
+ } catch {
50
+ return
51
+ }
52
+ if (Array.isArray(result?.tokens)) return result
53
+ }
54
+
55
+ makeMatcher = route => {
56
+ let matcher
57
+ try {
58
+ // Callers read which params matched, never their values: skipping decode is faster and
59
+ // stops a malformed escape ('%ZZ') from throwing URIError, which would read as "no match".
60
+ matcher = match(route, { decode: false })
61
+ } catch {
62
+ return
63
+ }
64
+ return url => {
65
+ let result
66
+ try {
67
+ result = matcher(url)
68
+ } catch {
69
+ return
70
+ }
71
+ return result ? result.params : undefined
72
+ }
73
+ }
74
+ }
75
+
76
+ return moduleExports
77
+ })
78
+
32
79
  /**
33
80
  * Returns whatever path-to-regexp compile adapter the host most recently
34
81
  * loaded. Capture this once at addHook fire time so each express/router
@@ -41,4 +88,23 @@ function getCompileToRegexp () {
41
88
  return compileToRegexp
42
89
  }
43
90
 
44
- module.exports = { getCompileToRegexp }
91
+ /**
92
+ * Returns the host's path-to-regexp `parse` adapter (8.x token tree), or `undefined` when the
93
+ * host has not loaded an 8.x path-to-regexp (e.g. Express 4 ships 0.1.x, which has no `parse`).
94
+ * @returns {((pattern: string) => { tokens: object[] } | undefined) | undefined}
95
+ */
96
+ function getParse () {
97
+ return parseTokens
98
+ }
99
+
100
+ /**
101
+ * Returns the host's path-to-regexp 8.x `match` adapter: a factory `route => (url => params)` that
102
+ * compiles a route once and returns a matcher yielding the captured params (or undefined on no
103
+ * match / unusable route). `undefined` when the host has not loaded an 8.x path-to-regexp.
104
+ * @returns {((route: string) => ((url: string) => object | undefined) | undefined) | undefined}
105
+ */
106
+ function getMatch () {
107
+ return makeMatcher
108
+ }
109
+
110
+ module.exports = { getCompileToRegexp, getParse, getMatch }