dd-trace 4.11.1 → 4.16.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 (78) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +4 -9
  3. package/ext/tags.d.ts +1 -0
  4. package/ext/tags.js +1 -0
  5. package/index.d.ts +44 -0
  6. package/package.json +9 -6
  7. package/packages/datadog-esbuild/index.js +57 -32
  8. package/packages/datadog-instrumentations/src/body-parser.js +2 -2
  9. package/packages/datadog-instrumentations/src/cookie-parser.js +37 -0
  10. package/packages/datadog-instrumentations/src/cucumber.js +30 -11
  11. package/packages/datadog-instrumentations/src/express.js +1 -1
  12. package/packages/datadog-instrumentations/src/graphql.js +10 -4
  13. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  14. package/packages/datadog-instrumentations/src/http/server.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +22 -11
  16. package/packages/datadog-instrumentations/src/kafkajs.js +3 -4
  17. package/packages/datadog-instrumentations/src/mocha.js +33 -8
  18. package/packages/datadog-instrumentations/src/mysql.js +39 -1
  19. package/packages/datadog-instrumentations/src/next.js +47 -19
  20. package/packages/datadog-instrumentations/src/openai.js +1 -1
  21. package/packages/datadog-instrumentations/src/pg.js +60 -15
  22. package/packages/datadog-instrumentations/src/playwright.js +15 -3
  23. package/packages/datadog-plugin-cucumber/src/index.js +14 -2
  24. package/packages/datadog-plugin-cypress/src/plugin.js +49 -13
  25. package/packages/datadog-plugin-graphql/src/index.js +3 -3
  26. package/packages/datadog-plugin-graphql/src/resolve.js +27 -2
  27. package/packages/datadog-plugin-jest/src/index.js +10 -2
  28. package/packages/datadog-plugin-jest/src/util.js +10 -4
  29. package/packages/datadog-plugin-mocha/src/index.js +14 -2
  30. package/packages/datadog-plugin-mongodb-core/src/index.js +6 -2
  31. package/packages/datadog-plugin-mysql/src/index.js +2 -2
  32. package/packages/datadog-plugin-next/src/index.js +22 -5
  33. package/packages/datadog-plugin-pg/src/index.js +2 -2
  34. package/packages/dd-trace/src/appsec/addresses.js +1 -0
  35. package/packages/dd-trace/src/appsec/channels.js +2 -0
  36. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +7 -0
  37. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +29 -18
  38. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +19 -1
  39. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +48 -5
  42. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +14 -5
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +131 -10
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
  45. package/packages/dd-trace/src/appsec/index.js +42 -7
  46. package/packages/dd-trace/src/appsec/recommended.json +655 -31
  47. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  48. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
  49. package/packages/dd-trace/src/appsec/reporter.js +26 -0
  50. package/packages/dd-trace/src/appsec/telemetry.js +132 -0
  51. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  52. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +13 -5
  53. package/packages/dd-trace/src/appsec/waf/waf_manager.js +12 -14
  54. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +1 -14
  55. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -13
  56. package/packages/dd-trace/src/datastreams/processor.js +6 -2
  57. package/packages/dd-trace/src/dogstatsd.js +108 -8
  58. package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
  59. package/packages/dd-trace/src/exporters/common/request.js +13 -4
  60. package/packages/dd-trace/src/format.js +6 -1
  61. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
  62. package/packages/dd-trace/src/opentracing/span.js +13 -13
  63. package/packages/dd-trace/src/opentracing/tracer.js +3 -5
  64. package/packages/dd-trace/src/plugin_manager.js +1 -2
  65. package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
  66. package/packages/dd-trace/src/plugins/database.js +14 -4
  67. package/packages/dd-trace/src/plugins/index.js +1 -0
  68. package/packages/dd-trace/src/plugins/outbound.js +4 -3
  69. package/packages/dd-trace/src/plugins/tracing.js +1 -1
  70. package/packages/dd-trace/src/plugins/util/test.js +20 -3
  71. package/packages/dd-trace/src/profiling/config.js +3 -1
  72. package/packages/dd-trace/src/profiling/profilers/wall.js +31 -7
  73. package/packages/dd-trace/src/proxy.js +13 -2
  74. package/packages/dd-trace/src/ritm.js +10 -2
  75. package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +1 -32
  76. package/packages/dd-trace/src/telemetry/dependencies.js +15 -0
  77. package/packages/dd-trace/src/telemetry/index.js +21 -2
  78. package/packages/dd-trace/src/util.js +1 -1
@@ -39,10 +39,13 @@ const testErrCh = channel('ci:jest:test:err')
39
39
  const skippableSuitesCh = channel('ci:jest:test-suite:skippable')
40
40
  const jestItrConfigurationCh = channel('ci:jest:itr-configuration')
41
41
 
42
+ const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
43
+
42
44
  let skippableSuites = []
43
45
  let isCodeCoverageEnabled = false
44
46
  let isSuitesSkippingEnabled = false
45
47
  let isSuitesSkipped = false
48
+ let numSkippedSuites = 0
46
49
 
47
50
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
48
51
 
@@ -102,7 +105,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
102
105
  await super.handleTestEvent(event, state)
103
106
  }
104
107
 
105
- const setNameToParams = (name, params) => { this.nameToParams[name] = params }
108
+ const setNameToParams = (name, params) => { this.nameToParams[name] = [...params] }
106
109
 
107
110
  if (event.name === 'setup') {
108
111
  if (this.global.test) {
@@ -191,7 +194,7 @@ addHook({
191
194
  addHook({
192
195
  name: '@jest/test-sequencer',
193
196
  versions: ['>=24.8.0']
194
- }, sequencerPackage => {
197
+ }, (sequencerPackage, frameworkVersion) => {
195
198
  shimmer.wrap(sequencerPackage.default.prototype, 'shard', shard => function () {
196
199
  const shardedTests = shard.apply(this, arguments)
197
200
 
@@ -202,13 +205,15 @@ addHook({
202
205
  const [test] = shardedTests
203
206
  const rootDir = test && test.context && test.context.config && test.context.config.rootDir
204
207
 
205
- const filteredTests = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
208
+ const { skippedSuites, suitesToRun } = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
206
209
 
207
- isSuitesSkipped = filteredTests.length !== shardedTests.length
210
+ isSuitesSkipped = suitesToRun.length !== shardedTests.length
211
+ numSkippedSuites = skippedSuites.length
208
212
 
209
- skippableSuites = []
213
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
210
214
 
211
- return filteredTests
215
+ skippableSuites = []
216
+ return suitesToRun
212
217
  })
213
218
  return sequencerPackage
214
219
  })
@@ -279,10 +284,13 @@ function cliWrapper (cli, jestVersion) {
279
284
  isSuitesSkipped,
280
285
  isSuitesSkippingEnabled,
281
286
  isCodeCoverageEnabled,
282
- testCodeCoverageLinesTotal
287
+ testCodeCoverageLinesTotal,
288
+ numSkippedSuites
283
289
  })
284
290
  })
285
291
 
292
+ numSkippedSuites = 0
293
+
286
294
  return result
287
295
  })
288
296
 
@@ -468,7 +476,7 @@ addHook({
468
476
  name: '@jest/core',
469
477
  versions: ['>=24.8.0'],
470
478
  file: 'build/SearchSource.js'
471
- }, searchSourcePackage => {
479
+ }, (searchSourcePackage, frameworkVersion) => {
472
480
  const SearchSource = searchSourcePackage.default ? searchSourcePackage.default : searchSourcePackage
473
481
 
474
482
  shimmer.wrap(SearchSource.prototype, 'getTestPaths', getTestPaths => async function () {
@@ -492,13 +500,16 @@ addHook({
492
500
  const testPaths = await getTestPaths.apply(this, arguments)
493
501
  const { tests } = testPaths
494
502
 
495
- const filteredTests = getJestSuitesToRun(skippableSuites, tests, rootDir)
503
+ const { skippedSuites, suitesToRun } = getJestSuitesToRun(skippableSuites, tests, rootDir)
504
+
505
+ isSuitesSkipped = suitesToRun.length !== tests.length
506
+ numSkippedSuites = skippedSuites.length
496
507
 
497
- isSuitesSkipped = filteredTests.length !== tests.length
508
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
498
509
 
499
510
  skippableSuites = []
500
511
 
501
- return { ...testPaths, tests: filteredTests }
512
+ return { ...testPaths, tests: suitesToRun }
502
513
  })
503
514
 
504
515
  return searchSourcePackage
@@ -15,15 +15,14 @@ const consumerStartCh = channel('apm:kafkajs:consume:start')
15
15
  const consumerFinishCh = channel('apm:kafkajs:consume:finish')
16
16
  const consumerErrorCh = channel('apm:kafkajs:consume:error')
17
17
 
18
- addHook({ name: 'kafkajs', versions: ['>=1.4'] }, (obj) => {
19
- class Kafka extends obj.Kafka {
18
+ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKafka) => {
19
+ class Kafka extends BaseKafka {
20
20
  constructor (options) {
21
21
  super(options)
22
22
  this._brokers = (options.brokers && typeof options.brokers !== 'function')
23
23
  ? options.brokers.join(',') : undefined
24
24
  }
25
25
  }
26
- obj.Kafka = Kafka
27
26
 
28
27
  shimmer.wrap(Kafka.prototype, 'producer', createProducer => function () {
29
28
  const producer = createProducer.apply(this, arguments)
@@ -117,5 +116,5 @@ addHook({ name: 'kafkajs', versions: ['>=1.4'] }, (obj) => {
117
116
  }
118
117
  return consumer
119
118
  })
120
- return obj
119
+ return Kafka
121
120
  })
@@ -30,6 +30,8 @@ const testSuiteFinishCh = channel('ci:mocha:test-suite:finish')
30
30
  const testSuiteErrorCh = channel('ci:mocha:test-suite:error')
31
31
  const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
32
32
 
33
+ const itrSkippedSuitesCh = channel('ci:mocha:itr:skipped-suites')
34
+
33
35
  // TODO: remove when root hooks and fixtures are implemented
34
36
  const patched = new WeakSet()
35
37
 
@@ -47,6 +49,7 @@ const originalCoverageMap = createCoverageMap()
47
49
  let suitesToSkip = []
48
50
  let frameworkVersion
49
51
  let isSuitesSkipped = false
52
+ let skippedSuites = []
50
53
 
51
54
  function getSuitesByTestFile (root) {
52
55
  const suitesByTestFile = {}
@@ -97,10 +100,17 @@ function getTestAsyncResource (test) {
97
100
  return testToAr.get(originalFn)
98
101
  }
99
102
 
100
- function getSuitesToRun (originalSuites) {
101
- return originalSuites.filter(suite =>
102
- !suitesToSkip.includes(getTestSuitePath(suite.file, process.cwd()))
103
- )
103
+ function getFilteredSuites (originalSuites) {
104
+ return originalSuites.reduce((acc, suite) => {
105
+ const testPath = getTestSuitePath(suite.file, process.cwd())
106
+ const shouldSkip = suitesToSkip.includes(testPath)
107
+ if (shouldSkip) {
108
+ acc.skippedSuites.add(testPath)
109
+ } else {
110
+ acc.suitesToRun.push(suite)
111
+ }
112
+ return acc
113
+ }, { suitesToRun: [], skippedSuites: new Set() })
104
114
  }
105
115
 
106
116
  function mochaHook (Runner) {
@@ -137,13 +147,21 @@ function mochaHook (Runner) {
137
147
  global.__coverage__ = fromCoverageMapToCoverage(originalCoverageMap)
138
148
  }
139
149
 
140
- testSessionFinishCh.publish({ status, isSuitesSkipped, testCodeCoverageLinesTotal })
150
+ testSessionFinishCh.publish({
151
+ status,
152
+ isSuitesSkipped,
153
+ testCodeCoverageLinesTotal,
154
+ numSkippedSuites: skippedSuites.length
155
+ })
141
156
  }))
142
157
 
143
158
  this.once('start', testRunAsyncResource.bind(function () {
144
159
  const processArgv = process.argv.slice(2).join(' ')
145
160
  const command = `mocha ${processArgv}`
146
161
  testSessionStartCh.publish({ command, frameworkVersion })
162
+ if (skippedSuites.length) {
163
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
164
+ }
147
165
  }))
148
166
 
149
167
  this.on('suite', function (suite) {
@@ -359,9 +377,13 @@ addHook({
359
377
  suitesToSkip = skippableSuites
360
378
  }
361
379
  // We remove the suites that we skip through ITR
362
- const newSuites = getSuitesToRun(runner.suite.suites)
363
- isSuitesSkipped = newSuites.length !== runner.suite.suites.length
364
- runner.suite.suites = newSuites
380
+ const filteredSuites = getFilteredSuites(runner.suite.suites)
381
+ const { suitesToRun } = filteredSuites
382
+
383
+ isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
384
+ runner.suite.suites = suitesToRun
385
+
386
+ skippedSuites = Array.from(filteredSuites.skippedSuites)
365
387
 
366
388
  global.run()
367
389
  }
@@ -419,6 +441,9 @@ addHook({
419
441
  file: 'lib/cli/run-helpers.js'
420
442
  }, (run) => {
421
443
  shimmer.wrap(run, 'runMocha', runMocha => async function () {
444
+ if (!testStartCh.hasSubscribers) {
445
+ return runMocha.apply(this, arguments)
446
+ }
422
447
  const mocha = arguments[0]
423
448
  /**
424
449
  * This attaches `run` to the global context, which we'll call after
@@ -17,7 +17,7 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
17
17
  return query.apply(this, arguments)
18
18
  }
19
19
 
20
- const sql = arguments[0].sql ? arguments[0].sql : arguments[0]
20
+ const sql = arguments[0].sql || arguments[0]
21
21
  const conf = this.config
22
22
  const payload = { sql, conf }
23
23
 
@@ -66,9 +66,47 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
66
66
  })
67
67
 
68
68
  addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
69
+ const startPoolQueryCh = channel('datadog:mysql:pool:query:start')
70
+ const finishPoolQueryCh = channel('datadog:mysql:pool:query:finish')
71
+
69
72
  shimmer.wrap(Pool.prototype, 'getConnection', getConnection => function (cb) {
70
73
  arguments[0] = AsyncResource.bind(cb)
71
74
  return getConnection.apply(this, arguments)
72
75
  })
76
+
77
+ shimmer.wrap(Pool.prototype, 'query', query => function () {
78
+ if (!startPoolQueryCh.hasSubscribers) {
79
+ return query.apply(this, arguments)
80
+ }
81
+
82
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
83
+
84
+ const sql = arguments[0].sql || arguments[0]
85
+
86
+ return asyncResource.runInAsyncScope(() => {
87
+ startPoolQueryCh.publish({ sql })
88
+
89
+ const finish = asyncResource.bind(function () {
90
+ finishPoolQueryCh.publish()
91
+ })
92
+
93
+ const cb = arguments[arguments.length - 1]
94
+ if (typeof cb === 'function') {
95
+ arguments[arguments.length - 1] = shimmer.wrap(cb, function () {
96
+ finish()
97
+ return cb.apply(this, arguments)
98
+ })
99
+ }
100
+
101
+ const retval = query.apply(this, arguments)
102
+
103
+ if (retval && retval.then) {
104
+ retval.then(finish).catch(finish)
105
+ }
106
+
107
+ return retval
108
+ })
109
+ })
110
+
73
111
  return Pool
74
112
  })
@@ -2,7 +2,7 @@
2
2
 
3
3
  // TODO: either instrument all or none of the render functions
4
4
 
5
- const { channel, addHook, AsyncResource } = require('./helpers/instrument')
5
+ const { channel, addHook } = require('./helpers/instrument')
6
6
  const shimmer = require('../../datadog-shimmer')
7
7
  const { DD_MAJOR } = require('../../../version')
8
8
 
@@ -11,7 +11,7 @@ const finishChannel = channel('apm:next:request:finish')
11
11
  const errorChannel = channel('apm:next:request:error')
12
12
  const pageLoadChannel = channel('apm:next:page:load')
13
13
 
14
- const requestResources = new WeakMap()
14
+ const requests = new WeakSet()
15
15
 
16
16
  function wrapHandleRequest (handleRequest) {
17
17
  return function (req, res, pathname, query) {
@@ -28,9 +28,9 @@ function wrapHandleApiRequest (handleApiRequest) {
28
28
  if (!handled) return handled
29
29
 
30
30
  return this.hasPage(pathname).then(pageFound => {
31
- const page = pageFound ? pathname : getPageFromPath(pathname, this.dynamicRoutes)
31
+ const pageData = pageFound ? { page: pathname } : getPageFromPath(pathname, this.dynamicRoutes)
32
32
 
33
- pageLoadChannel.publish({ page })
33
+ pageLoadChannel.publish(pageData)
34
34
 
35
35
  return handled
36
36
  })
@@ -83,15 +83,19 @@ function wrapFindPageComponents (findPageComponents) {
83
83
  const result = findPageComponents.apply(this, arguments)
84
84
 
85
85
  if (result) {
86
- pageLoadChannel.publish({ page: getPagePath(pathname) })
86
+ pageLoadChannel.publish(getPagePath(pathname))
87
87
  }
88
88
 
89
89
  return result
90
90
  }
91
91
  }
92
92
 
93
- function getPagePath (page) {
94
- return typeof page === 'object' ? page.pathname : page
93
+ function getPagePath (maybePage) {
94
+ if (typeof maybePage !== 'object') return { page: maybePage }
95
+
96
+ const isAppPath = maybePage.isAppPath
97
+ const page = maybePage.pathname || maybePage.page
98
+ return { page, isAppPath }
95
99
  }
96
100
 
97
101
  function getPageFromPath (page, dynamicRoutes = []) {
@@ -105,38 +109,50 @@ function getPageFromPath (page, dynamicRoutes = []) {
105
109
  }
106
110
 
107
111
  function instrument (req, res, handler) {
108
- if (requestResources.has(req)) return handler()
112
+ req = req.originalRequest || req
113
+ res = res.originalResponse || res
109
114
 
110
- const requestResource = new AsyncResource('bound-anonymous-fn')
115
+ if (requests.has(req)) return handler()
111
116
 
112
- requestResources.set(req, requestResource)
117
+ requests.add(req)
113
118
 
114
- return requestResource.runInAsyncScope(() => {
115
- startChannel.publish({ req, res })
119
+ const ctx = { req, res }
116
120
 
121
+ return startChannel.runStores(ctx, () => {
117
122
  try {
118
- const promise = handler()
123
+ const promise = handler(ctx)
119
124
 
120
125
  // promise should only reject when propagateError is true:
121
126
  // https://github.com/vercel/next.js/blob/cee656238a/packages/next/server/api-utils/node.ts#L547
122
127
  return promise.then(
123
- result => finish(req, res, result),
124
- err => finish(req, res, null, err)
128
+ result => finish(ctx, result),
129
+ err => finish(ctx, null, err)
125
130
  )
126
131
  } catch (e) {
127
132
  // this will probably never happen as the handler caller is an async function:
128
133
  // https://github.com/vercel/next.js/blob/cee656238a/packages/next/server/api-utils/node.ts#L420
129
- return finish(req, res, null, e)
134
+ return finish(ctx, null, e)
130
135
  }
131
136
  })
132
137
  }
133
138
 
134
- function finish (req, res, result, err) {
139
+ function wrapServeStatic (serveStatic) {
140
+ return function (req, res, path) {
141
+ return instrument(req, res, () => {
142
+ if (pageLoadChannel.hasSubscribers && path) pageLoadChannel.publish({ page: path })
143
+
144
+ return serveStatic.apply(this, arguments)
145
+ })
146
+ }
147
+ }
148
+
149
+ function finish (ctx, result, err) {
135
150
  if (err) {
136
- errorChannel.publish(err)
151
+ ctx.error = err
152
+ errorChannel.publish(ctx)
137
153
  }
138
154
 
139
- finishChannel.publish({ req, res })
155
+ finishChannel.publish(ctx)
140
156
 
141
157
  if (err) {
142
158
  throw err
@@ -145,6 +161,18 @@ function finish (req, res, result, err) {
145
161
  return result
146
162
  }
147
163
 
164
+ addHook({
165
+ name: 'next',
166
+ versions: ['>=11.1'],
167
+ file: 'dist/server/serve-static.js'
168
+ }, serveStatic => shimmer.wrap(serveStatic, 'serveStatic', wrapServeStatic))
169
+
170
+ addHook({
171
+ name: 'next',
172
+ versions: DD_MAJOR >= 4 ? ['>=10.2 <11.1'] : ['>=9.5 <11.1'],
173
+ file: 'dist/next-server/server/serve-static.js'
174
+ }, serveStatic => shimmer.wrap(serveStatic, 'serveStatic', wrapServeStatic))
175
+
148
176
  addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js' }, nextServer => {
149
177
  const Server = nextServer.default
150
178
 
@@ -10,7 +10,7 @@ const startCh = channel('apm:openai:request:start')
10
10
  const finishCh = channel('apm:openai:request:finish')
11
11
  const errorCh = channel('apm:openai:request:error')
12
12
 
13
- addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0'] }, exports => {
13
+ addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0 <4'] }, exports => {
14
14
  const methodNames = Object.getOwnPropertyNames(exports.OpenAIApi.prototype)
15
15
  methodNames.shift() // remove leading 'constructor' method
16
16
 
@@ -11,8 +11,12 @@ const startCh = channel('apm:pg:query:start')
11
11
  const finishCh = channel('apm:pg:query:finish')
12
12
  const errorCh = channel('apm:pg:query:error')
13
13
 
14
+ const startPoolQueryCh = channel('datadog:pg:pool:query:start')
15
+ const finishPoolQueryCh = channel('datadog:pg:pool:query:finish')
16
+
14
17
  addHook({ name: 'pg', versions: ['>=8.0.3'] }, pg => {
15
18
  shimmer.wrap(pg.Client.prototype, 'query', query => wrapQuery(query))
19
+ shimmer.wrap(pg.Pool.prototype, 'query', query => wrapPoolQuery(query))
16
20
  return pg
17
21
  })
18
22
 
@@ -35,28 +39,27 @@ function wrapQuery (query) {
35
39
  ? arguments[0]
36
40
  : { text: arguments[0] }
37
41
 
38
- // The query objects passed in can be pretty complex. They can be instances of EventEmitter.
39
- // For this reason we can't make a shallow clone of the object.
40
- // Some libraries, such as sql-template-tags, can provide a getter .text property.
41
- // For this reason we can't replace the .text property.
42
- // Instead, we create a new object, and set the original query as the prototype.
43
- // This allows any existing methods to still work and lets us easily provide a new query.
44
- let newQuery = {
45
- __ddInjectableQuery: '',
46
- get text () {
47
- return this.__ddInjectableQuery || Object.getPrototypeOf(this).text
48
- }
42
+ const textProp = Object.getOwnPropertyDescriptor(pgQuery, 'text')
43
+
44
+ // Only alter `text` property if safe to do so.
45
+ if (!textProp || textProp.configurable) {
46
+ const originalText = pgQuery.text
47
+
48
+ Object.defineProperty(pgQuery, 'text', {
49
+ get () {
50
+ return this?.__ddInjectableQuery || originalText
51
+ }
52
+ })
49
53
  }
50
- Object.setPrototypeOf(newQuery, pgQuery)
51
54
 
52
55
  return asyncResource.runInAsyncScope(() => {
53
56
  startCh.publish({
54
57
  params: this.connectionParameters,
55
- query: newQuery,
58
+ query: pgQuery,
56
59
  processId
57
60
  })
58
61
 
59
- arguments[0] = newQuery
62
+ arguments[0] = pgQuery
60
63
 
61
64
  const finish = asyncResource.bind(function (error) {
62
65
  if (error) {
@@ -69,7 +72,7 @@ function wrapQuery (query) {
69
72
  const queryQueue = this.queryQueue || this._queryQueue
70
73
  const activeQuery = this.activeQuery || this._activeQuery
71
74
 
72
- newQuery = queryQueue[queryQueue.length - 1] || activeQuery
75
+ const newQuery = queryQueue[queryQueue.length - 1] || activeQuery
73
76
 
74
77
  if (!newQuery) {
75
78
  return retval
@@ -97,3 +100,45 @@ function wrapQuery (query) {
97
100
  })
98
101
  }
99
102
  }
103
+
104
+ function wrapPoolQuery (query) {
105
+ return function () {
106
+ if (!startPoolQueryCh.hasSubscribers) {
107
+ return query.apply(this, arguments)
108
+ }
109
+
110
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
111
+
112
+ const pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
113
+
114
+ return asyncResource.runInAsyncScope(() => {
115
+ startPoolQueryCh.publish({
116
+ query: pgQuery
117
+ })
118
+
119
+ const finish = asyncResource.bind(function () {
120
+ finishPoolQueryCh.publish()
121
+ })
122
+
123
+ const cb = arguments[arguments.length - 1]
124
+ if (typeof cb === 'function') {
125
+ arguments[arguments.length - 1] = shimmer.wrap(cb, function () {
126
+ finish()
127
+ return cb.apply(this, arguments)
128
+ })
129
+ }
130
+
131
+ const retval = query.apply(this, arguments)
132
+
133
+ if (retval && retval.then) {
134
+ retval.then(() => {
135
+ finish()
136
+ }).catch(() => {
137
+ finish()
138
+ })
139
+ }
140
+
141
+ return retval
142
+ })
143
+ }
144
+ }
@@ -254,7 +254,7 @@ addHook({
254
254
  addHook({
255
255
  name: '@playwright/test',
256
256
  file: 'lib/dispatcher.js',
257
- versions: ['>=1.18.0 <1.30.0']
257
+ versions: ['>=1.18.0 <1.30.0']
258
258
  }, dispatcherHook)
259
259
 
260
260
  addHook({
@@ -266,11 +266,23 @@ addHook({
266
266
  addHook({
267
267
  name: '@playwright/test',
268
268
  file: 'lib/runner/dispatcher.js',
269
- versions: ['>=1.31.0']
269
+ versions: ['>=1.31.0 <1.38.0']
270
270
  }, (dispatcher) => dispatcherHookNew(dispatcher, dispatcherRunWrapperNew))
271
271
 
272
272
  addHook({
273
273
  name: '@playwright/test',
274
274
  file: 'lib/runner/runner.js',
275
- versions: ['>=1.31.0']
275
+ versions: ['>=1.31.0 <1.38.0']
276
276
  }, runnerHook)
277
+
278
+ // From >=1.38.0
279
+ addHook({
280
+ name: 'playwright',
281
+ file: 'lib/runner/runner.js',
282
+ versions: ['>=1.38.0']
283
+ }, runnerHook)
284
+ addHook({
285
+ name: 'playwright',
286
+ file: 'lib/runner/dispatcher.js',
287
+ versions: ['>=1.38.0']
288
+ }, (dispatcher) => dispatcherHookNew(dispatcher, dispatcherRunWrapperNew))
@@ -25,12 +25,24 @@ class CucumberPlugin extends CiPlugin {
25
25
 
26
26
  this.sourceRoot = process.cwd()
27
27
 
28
- this.addSub('ci:cucumber:session:finish', ({ status, isSuitesSkipped, testCodeCoverageLinesTotal }) => {
28
+ this.addSub('ci:cucumber:session:finish', ({
29
+ status,
30
+ isSuitesSkipped,
31
+ numSkippedSuites,
32
+ testCodeCoverageLinesTotal
33
+ }) => {
29
34
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
30
35
  addIntelligentTestRunnerSpanTags(
31
36
  this.testSessionSpan,
32
37
  this.testModuleSpan,
33
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
38
+ {
39
+ isSuitesSkipped,
40
+ isSuitesSkippingEnabled,
41
+ isCodeCoverageEnabled,
42
+ testCodeCoverageLinesTotal,
43
+ skippingCount: numSkippedSuites,
44
+ skippingType: 'suite'
45
+ }
34
46
  )
35
47
 
36
48
  this.testSessionSpan.setTag(TEST_STATUS, status)