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
@@ -8,6 +8,17 @@ const {
8
8
  channel,
9
9
  addHook,
10
10
  } = require('./helpers/instrument')
11
+ const {
12
+ clearPoolWaitTime,
13
+ dispatchesAcquireSynchronously,
14
+ isPoolQueryAcquire,
15
+ runOutsidePoolQueryAcquire,
16
+ runPoolAcquireError,
17
+ runWithPoolWait,
18
+ takePoolWaitTime,
19
+ wrapPoolAcquireCarrier,
20
+ wrapPoolQueryMethod,
21
+ } = require('./helpers/pool-acquire')
11
22
 
12
23
  const startCh = channel('apm:pg:query:start')
13
24
  const finishCh = channel('apm:pg:query:finish')
@@ -21,20 +32,16 @@ const poolConnectFinishCh = channel('apm:pg:pool:connect:finish')
21
32
 
22
33
  const poolAcquireStartCh = channel('apm:pg:pool:acquire:start')
23
34
  const poolAcquireFinishCh = channel('apm:pg:pool:acquire:finish')
35
+ const poolAcquireChannels = {
36
+ connectionFinishCh: poolConnectFinishCh,
37
+ acquireStartCh: poolAcquireStartCh,
38
+ acquireFinishCh: poolAcquireFinishCh,
39
+ }
24
40
 
25
41
  // Drivers like pg-promise reuse the same prepared-statement query object across executions; cache
26
42
  // the un-injected `text` so the wrap doesn't capture a previous DBM injection as the new original.
27
43
  const originalTextCache = new WeakMap()
28
44
 
29
- // Pool.query dispatches its client query synchronously from the connect callback, including nested
30
- // `_release` queue pulses, so the wait can cross that callback without retaining the client.
31
- const poolWaitClients = []
32
- const poolWaitTimes = []
33
-
34
- // Pool.query reports its internal acquire on the query span; explicit connects get an acquire span.
35
- // Pool.query calls connect synchronously, so a module flag reliably distinguishes the two.
36
- let acquiringForPoolQuery = false
37
-
38
45
  addHook({ name: 'pg', versions: ['>=8.0.3'], file: 'lib/native/client.js' }, Client => {
39
46
  shimmer.wrap(Client.prototype, 'query', query => wrapQuery(query))
40
47
  return Client
@@ -46,79 +53,118 @@ addHook({ name: 'pg', versions: ['>=8.0.3'], file: 'lib/client.js' }, Client =>
46
53
  })
47
54
 
48
55
  addHook({ name: 'pg', versions: ['>=8.0.3'] }, pg => {
56
+ const poolWaitTransfer = { deferred: false }
57
+ const deferredPoolAcquire = !dispatchesAcquireSynchronously(
58
+ pg.Pool.prototype.query,
59
+ Object.assign(Object.create(pg.Pool.prototype), { Promise }),
60
+ 'connect',
61
+ ['SELECT 1', () => {}]
62
+ )
63
+
49
64
  // pg defers a busy pool's connect callback and runs it in the releasing query's async context;
50
65
  // capture the caller's context and restore it around the callback so spans attach to the caller.
51
- shimmer.wrap(pg.Pool.prototype, 'connect', connect => function (cb) {
52
- // Pool.query always supplies a callback, so a missing callback is an explicit acquire.
53
- if (typeof cb !== 'function') {
54
- if (!poolAcquireStartCh.hasSubscribers) {
66
+ shimmer.wrap(pg.Pool.prototype, 'connect', connect => {
67
+ /**
68
+ * @param {Function|undefined} callback
69
+ * @returns {unknown}
70
+ */
71
+ const wrappedConnect = function (callback) {
72
+ // Pool.query always supplies a callback, so a missing callback is an explicit acquire.
73
+ if (typeof callback !== 'function') {
74
+ if (!poolAcquireStartCh.hasSubscribers) {
75
+ return connect.apply(this, arguments)
76
+ }
77
+
78
+ const start = acquireStart(this)
79
+ const acquireCtx = { poolOptions: this.options }
80
+ poolAcquireStartCh.publish(acquireCtx)
81
+
82
+ return connectForAcquire(connect, this, arguments, acquireCtx, start).then(client => {
83
+ clearPoolWaitTime(client)
84
+ acquireCtx.params = client.connectionParameters
85
+ finishAcquire(acquireCtx, start)
86
+ return client
87
+ }, error => {
88
+ acquireCtx.error = error
89
+ finishAcquire(acquireCtx, start)
90
+ throw error
91
+ })
92
+ }
93
+
94
+ if (!poolConnectStartCh.hasSubscribers) {
55
95
  return connect.apply(this, arguments)
56
96
  }
57
97
 
98
+ const ctx = {}
99
+ const poolOptions = this.options
58
100
  const start = acquireStart(this)
59
- const acquireCtx = { poolOptions: this.options }
60
- poolAcquireStartCh.publish(acquireCtx)
61
-
62
- return connectForAcquire(connect, this, arguments, acquireCtx, start).then(client => {
63
- acquireCtx.params = client.connectionParameters
64
- finishAcquire(acquireCtx, start)
65
- return client
66
- }, error => {
67
- acquireCtx.error = error
68
- finishAcquire(acquireCtx, start)
69
- throw error
70
- })
71
- }
101
+ const poolQueryAcquire = isPoolQueryAcquire()
102
+ let acquireCtx
72
103
 
73
- if (!poolConnectStartCh.hasSubscribers) {
74
- return connect.apply(this, arguments)
75
- }
76
-
77
- const ctx = {}
78
- const start = acquireStart(this)
79
- let acquireCtx
80
-
81
- if (acquiringForPoolQuery) {
82
- arguments[0] = function (...args) {
83
- const client = args[1]
84
- if (client !== undefined) {
85
- poolWaitClients.push(client)
86
- poolWaitTimes.push(acquireWait(start))
87
- }
88
- try {
89
- return poolConnectFinishCh.runStores(ctx, cb, this, ...args)
90
- } finally {
104
+ if (poolQueryAcquire) {
105
+ arguments[0] = function (...args) {
106
+ const client = args[1]
91
107
  if (client !== undefined) {
92
- poolWaitClients.pop()
93
- poolWaitTimes.pop()
108
+ const waitTime = acquireWait(start)
109
+ return runWithPoolWait(
110
+ poolWaitTransfer,
111
+ client,
112
+ waitTime,
113
+ poolConnectFinishCh,
114
+ ctx,
115
+ callback,
116
+ this,
117
+ args
118
+ )
119
+ }
120
+ if (args[0] && poolAcquireStartCh.hasSubscribers) {
121
+ return runPoolAcquireError(
122
+ start,
123
+ args[0],
124
+ { poolOptions },
125
+ poolAcquireChannels,
126
+ ctx,
127
+ callback,
128
+ this,
129
+ args
130
+ )
94
131
  }
132
+ return poolConnectFinishCh.runStores(ctx, callback, this, ...args)
95
133
  }
96
- }
97
- } else {
98
- acquireCtx = { poolOptions: this.options }
99
- poolAcquireStartCh.publish(acquireCtx)
100
-
101
- arguments[0] = function (...args) {
102
- const client = args[1]
103
- acquireCtx.error = args[0]
104
- // A failed acquire has no client, so keep the parameters `connectForAcquire` snapshotted.
105
- if (client !== undefined) {
106
- acquireCtx.params = client.connectionParameters
134
+ } else {
135
+ acquireCtx = { poolOptions: this.options }
136
+ poolAcquireStartCh.publish(acquireCtx)
137
+
138
+ arguments[0] = function (...args) {
139
+ const client = args[1]
140
+ acquireCtx.error = args[0]
141
+ // A failed acquire has no client, so keep the parameters `connectForAcquire` snapshotted.
142
+ if (client !== undefined) {
143
+ clearPoolWaitTime(client)
144
+ acquireCtx.params = client.connectionParameters
145
+ }
146
+ finishAcquire(acquireCtx, start)
147
+ return poolConnectFinishCh.runStores(ctx, callback, this, ...args)
107
148
  }
108
- finishAcquire(acquireCtx, start)
109
- return poolConnectFinishCh.runStores(ctx, cb, this, ...args)
110
149
  }
111
- }
112
150
 
113
- poolConnectStartCh.publish(ctx)
151
+ poolConnectStartCh.publish(ctx)
114
152
 
115
- if (acquireCtx === undefined) {
116
- return connect.apply(this, arguments)
153
+ if (acquireCtx === undefined) return runOutsidePoolQueryAcquire(connect, this, arguments)
154
+
155
+ return connectForAcquire(connect, this, arguments, acquireCtx, start)
117
156
  }
118
157
 
119
- return connectForAcquire(connect, this, arguments, acquireCtx, start)
158
+ return wrapPoolAcquireCarrier(
159
+ wrappedConnect,
160
+ connect,
161
+ poolConnectStartCh,
162
+ deferredPoolAcquire
163
+ )
120
164
  })
121
- shimmer.wrap(pg.Pool.prototype, 'query', query => wrapPoolQuery(query))
165
+ shimmer.wrap(pg.Pool.prototype, 'query', query => wrapPoolQuery(
166
+ wrapPoolQueryMethod(query, poolConnectStartCh, deferredPoolAcquire)
167
+ ))
122
168
  return pg
123
169
  })
124
170
 
@@ -153,14 +199,9 @@ function wrapQuery (query) {
153
199
  stream,
154
200
  }
155
201
 
156
- if (poolWaitClients.length !== 0) {
157
- for (let i = poolWaitClients.length - 1; i >= 0; i--) {
158
- if (poolWaitClients[i] === this) {
159
- ctx.poolWaitTime = poolWaitTimes[i]
160
- poolWaitClients[i] = undefined
161
- break
162
- }
163
- }
202
+ const waitTime = takePoolWaitTime(this)
203
+ if (waitTime !== undefined) {
204
+ ctx.poolWaitTime = waitTime
164
205
  }
165
206
 
166
207
  const finish = (error, res) => {
@@ -342,25 +383,10 @@ function finishAcquire (ctx, start) {
342
383
  poolAcquireFinishCh.publish(ctx)
343
384
  }
344
385
 
345
- /**
346
- * @param {Function} query
347
- * @param {InstrumentedPool} pool
348
- * @param {unknown[]} args
349
- */
350
- function acquireForPoolQuery (query, pool, args) {
351
- acquiringForPoolQuery = true
352
- try {
353
- return query.apply(pool, args)
354
- } finally {
355
- acquiringForPoolQuery = false
356
- }
357
- }
358
386
  function wrapPoolQuery (query) {
359
387
  return function (...args) {
360
388
  if (!startPoolQueryCh.hasSubscribers) {
361
- return poolConnectStartCh.hasSubscribers
362
- ? acquireForPoolQuery(query, this, args)
363
- : query.apply(this, args)
389
+ return query.apply(this, args)
364
390
  }
365
391
 
366
392
  const pgQuery = args[0] !== null && typeof args[0] === 'object' ? args[0] : { text: args[0] }
@@ -390,9 +416,7 @@ function wrapPoolQuery (query) {
390
416
  })
391
417
  }
392
418
 
393
- const retval = poolConnectStartCh.hasSubscribers
394
- ? acquireForPoolQuery(query, this, args)
395
- : query.apply(this, args)
419
+ const retval = query.apply(this, args)
396
420
 
397
421
  if (retval?.then) {
398
422
  retval.then(() => {
@@ -47,6 +47,7 @@ const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTI
47
47
  const VITEST_NO_WORKER_INIT_REQUEST_ENV = 'DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT'
48
48
  const VITEST_NO_WORKER_INIT_MINIMUM_VERSION = '3.2.6'
49
49
  const VITEST_DEFAULT_POOL = 'forks'
50
+ const VITEST_BROWSER_EFD_SUITE_ADMISSION_COMMAND = '__dd_vitest_efd_suite_admission'
50
51
  const VITEST_NO_WORKER_INIT_SETUP_FILE = path.join(
51
52
  __dirname,
52
53
  '..',
@@ -69,6 +70,7 @@ const VITEST_BROWSER_URL_RE = /https?:\/\/[^\s)"'>\]]+/g
69
70
  let hasWarnedDisabledIsolate = false
70
71
  let hasWarnedUnsupportedVersion = false
71
72
  let nodeOptionsBeforeNoWorkerInit
73
+ let reserveEarlyFlakeDetectionSuite
72
74
 
73
75
  function noop () {}
74
76
 
@@ -322,6 +324,7 @@ function isEarlyFlakeDetectionActive (state) {
322
324
 
323
325
  function configure (ctx, frameworkVersion, testSpecifications, setupData, options) {
324
326
  const { shouldReportTestModule, state } = options
327
+ reserveEarlyFlakeDetectionSuite = options.reserveEarlyFlakeDetectionSuite
325
328
  addSetupFileToVitestConfigs(ctx, VITEST_NO_WORKER_INIT_SETUP_FILE, testSpecifications)
326
329
  addVitestBrowserSetupFileAccess(testSpecifications)
327
330
 
@@ -341,6 +344,8 @@ function configure (ctx, frameworkVersion, testSpecifications, setupData, option
341
344
  attemptToFixTests: getSelectedTestManagementTests(testManagementTestsBySuite, 'isAttemptToFix'),
342
345
  disabledTests: getSelectedTestManagementTests(testManagementTestsBySuite, 'isDisabled'),
343
346
  earlyFlakeDetectionRetryPolicy: state.earlyFlakeDetectionRetryPolicy,
347
+ efdSuiteAdmissionBrowserCommand: VITEST_BROWSER_EFD_SUITE_ADMISSION_COMMAND,
348
+ isEfdSuiteAdmissionEnabled: state.isEfdSuiteAdmissionEnabled,
344
349
  isEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionActive(state),
345
350
  isRumCorrelationEnabled: !canRaceRumCorrelation(ctx, testSpecifications),
346
351
  knownTests: knownTestsBySuite || {},
@@ -443,6 +448,18 @@ function configureVitestBrowserSetupFile (viteConfig) {
443
448
  }
444
449
  }
445
450
 
451
+ /**
452
+ * Reserves EFD retries for a suite executing in Vitest Browser Mode.
453
+ *
454
+ * @param {unknown} _context
455
+ * @param {string} testSuite
456
+ * @param {unknown} hasNewTest
457
+ * @returns {boolean}
458
+ */
459
+ function handleBrowserEfdSuiteAdmission (_context, testSuite, hasNewTest) {
460
+ return reserveEarlyFlakeDetectionSuite?.(testSuite, hasNewTest === true) === true
461
+ }
462
+
446
463
  /**
447
464
  * Allows Vite to serve the Datadog setup file after its default workspace detection has run.
448
465
  *
@@ -451,7 +468,9 @@ function configureVitestBrowserSetupFile (viteConfig) {
451
468
  */
452
469
  function allowVitestBrowserSetupFile (viteConfig) {
453
470
  const allow = viteConfig.server?.fs?.allow
454
- if (allow && !allow.includes(VITEST_NO_WORKER_INIT_SETUP_FILE)) {
471
+ if (!allow) return
472
+
473
+ if (!allow.includes(VITEST_NO_WORKER_INIT_SETUP_FILE)) {
455
474
  allow.push(VITEST_NO_WORKER_INIT_SETUP_FILE)
456
475
  }
457
476
  }
@@ -471,10 +490,16 @@ function addVitestBrowserSetupFileAccess (testSpecifications) {
471
490
  if (!isBrowserTestSpecification(testSpecification)) continue
472
491
 
473
492
  const browserServerProject = project?._parent || project
474
- if (!browserServerProject || configuredProjects.has(browserServerProject)) continue
493
+ if (!browserServerProject) continue
494
+
495
+ addVitestBrowserCommand(safeConfig(project))
496
+ if (configuredProjects.has(browserServerProject)) continue
475
497
 
476
498
  configuredProjects.add(browserServerProject)
499
+ addVitestBrowserCommand(safeConfig(browserServerProject))
477
500
  browserServerProject.options ||= {}
501
+ browserServerProject.options.browser ||= {}
502
+ addVitestBrowserCommand(browserServerProject.options)
478
503
  browserServerProject.options.plugins ||= []
479
504
  if (!browserServerProject.options.plugins.includes(VITEST_BROWSER_SETUP_FILE_PLUGIN)) {
480
505
  browserServerProject.options.plugins.push(VITEST_BROWSER_SETUP_FILE_PLUGIN)
@@ -482,6 +507,20 @@ function addVitestBrowserSetupFileAccess (testSpecifications) {
482
507
  }
483
508
  }
484
509
 
510
+ /**
511
+ * Adds the private EFD admission command to a Vitest Browser Mode configuration.
512
+ *
513
+ * @param {object|undefined} config
514
+ * @returns {void}
515
+ */
516
+ function addVitestBrowserCommand (config) {
517
+ const browserConfig = config?.browser
518
+ if (!browserConfig) return
519
+
520
+ browserConfig.commands ||= {}
521
+ browserConfig.commands[VITEST_BROWSER_EFD_SUITE_ADMISSION_COMMAND] = handleBrowserEfdSuiteAdmission
522
+ }
523
+
485
524
  function getNoWorkerInitVitestConfigs (ctx, testSpecifications) {
486
525
  const configs = new Set()
487
526
  if (Array.isArray(testSpecifications)) {
@@ -847,7 +886,7 @@ function createMainProcessReporter (reporterState) {
847
886
  !isAttemptToFix &&
848
887
  state.isKnownTestsEnabled &&
849
888
  knownTests &&
850
- !state.isEarlyFlakeDetectionFaulty &&
889
+ (state.isEfdSuiteAdmissionEnabled || !state.isEarlyFlakeDetectionFaulty) &&
851
890
  !knownTests.includes(testName)
852
891
  )
853
892
  const isModified = testProperties?.isModified === true
@@ -861,7 +900,7 @@ function createMainProcessReporter (reporterState) {
861
900
  return {
862
901
  isAttemptToFix,
863
902
  isDisabled: testManagementProperties.isDisabled,
864
- isEarlyFlakeDetection: (isNew || isModified) && isEarlyFlakeDetectionActive(state),
903
+ isEarlyFlakeDetection: task.meta?.__ddTestOptEfdRetries !== undefined,
865
904
  isFlakyTestRetries,
866
905
  isQuarantined: testManagementProperties.isQuarantined,
867
906
  isModified,