dd-trace 4.16.0 → 4.17.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 (50) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/package.json +4 -3
  3. package/packages/datadog-instrumentations/src/body-parser.js +2 -1
  4. package/packages/datadog-instrumentations/src/cucumber.js +24 -4
  5. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
  6. package/packages/datadog-instrumentations/src/express.js +2 -1
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  8. package/packages/datadog-instrumentations/src/jest.js +20 -11
  9. package/packages/datadog-instrumentations/src/knex.js +62 -1
  10. package/packages/datadog-instrumentations/src/mocha.js +19 -4
  11. package/packages/datadog-instrumentations/src/mongodb.js +63 -0
  12. package/packages/datadog-instrumentations/src/mongoose.js +140 -1
  13. package/packages/datadog-instrumentations/src/next.js +40 -0
  14. package/packages/datadog-instrumentations/src/playwright.js +11 -2
  15. package/packages/datadog-plugin-cucumber/src/index.js +17 -5
  16. package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
  17. package/packages/datadog-plugin-jest/src/index.js +19 -4
  18. package/packages/datadog-plugin-jest/src/util.js +45 -2
  19. package/packages/datadog-plugin-memcached/src/index.js +10 -5
  20. package/packages/datadog-plugin-mocha/src/index.js +19 -6
  21. package/packages/dd-trace/src/appsec/channels.js +3 -1
  22. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  23. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +166 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
  25. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
  26. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -2
  27. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -0
  28. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +25 -12
  29. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
  30. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
  31. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  32. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
  33. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
  34. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +13 -1
  36. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
  37. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  38. package/packages/dd-trace/src/appsec/index.js +31 -13
  39. package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
  40. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +14 -1
  41. package/packages/dd-trace/src/config.js +8 -0
  42. package/packages/dd-trace/src/format.js +3 -0
  43. package/packages/dd-trace/src/plugin_manager.js +3 -1
  44. package/packages/dd-trace/src/plugins/util/ci.js +17 -0
  45. package/packages/dd-trace/src/plugins/util/git.js +26 -4
  46. package/packages/dd-trace/src/plugins/util/test.js +16 -1
  47. package/packages/dd-trace/src/profiling/config.js +36 -5
  48. package/packages/dd-trace/src/profiling/profilers/wall.js +7 -1
  49. package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
  50. package/packages/dd-trace/src/telemetry/metrics.js +0 -5
@@ -14,6 +14,7 @@ require,import-in-the-middle,Apache license 2.0,Copyright 2021 Datadog Inc.
14
14
  require,int64-buffer,MIT,Copyright 2015-2016 Yusuke Kawasaki
15
15
  require,ipaddr.js,MIT,Copyright 2011-2017 whitequark
16
16
  require,istanbul-lib-coverage,BSD-3-Clause,Copyright 2012-2015 Yahoo! Inc.
17
+ require,jest-docblock,MIT,Copyright Meta Platforms, Inc. and affiliates.
17
18
  require,koalas,MIT,Copyright 2013-2017 Brian Woodward
18
19
  require,limiter,MIT,Copyright 2011 John Hurliman
19
20
  require,lodash.kebabcase,MIT,Copyright JS Foundation and other contributors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "4.16.0",
3
+ "version": "4.17.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -70,9 +70,9 @@
70
70
  "dependencies": {
71
71
  "@datadog/native-appsec": "^4.0.0",
72
72
  "@datadog/native-iast-rewriter": "2.1.3",
73
- "@datadog/native-iast-taint-tracking": "1.5.0",
73
+ "@datadog/native-iast-taint-tracking": "1.6.1",
74
74
  "@datadog/native-metrics": "^2.0.0",
75
- "@datadog/pprof": "3.2.0",
75
+ "@datadog/pprof": "4.0.0",
76
76
  "@datadog/sketches-js": "^2.1.0",
77
77
  "@opentelemetry/api": "^1.0.0",
78
78
  "@opentelemetry/core": "^1.14.0",
@@ -83,6 +83,7 @@
83
83
  "int64-buffer": "^0.1.9",
84
84
  "ipaddr.js": "^2.1.0",
85
85
  "istanbul-lib-coverage": "3.2.0",
86
+ "jest-docblock": "^29.7.0",
86
87
  "koalas": "^1.0.2",
87
88
  "limiter": "^1.1.4",
88
89
  "lodash.kebabcase": "^4.1.1",
@@ -10,8 +10,9 @@ function publishRequestBodyAndNext (req, res, next) {
10
10
  return function () {
11
11
  if (bodyParserReadCh.hasSubscribers && req) {
12
12
  const abortController = new AbortController()
13
+ const body = req.body
13
14
 
14
- bodyParserReadCh.publish({ req, res, abortController })
15
+ bodyParserReadCh.publish({ req, res, body, abortController })
15
16
 
16
17
  if (abortController.signal.aborted) return
17
18
  }
@@ -31,6 +31,10 @@ const {
31
31
  getTestSuitePath
32
32
  } = require('../../dd-trace/src/plugins/util/test')
33
33
 
34
+ const isMarkedAsUnskippable = (pickle) => {
35
+ return !!pickle.tags.find(tag => tag.name === '@datadog:unskippable')
36
+ }
37
+
34
38
  // We'll preserve the original coverage here
35
39
  const originalCoverageMap = createCoverageMap()
36
40
 
@@ -39,6 +43,9 @@ const patched = new WeakSet()
39
43
 
40
44
  let pickleByFile = {}
41
45
  const pickleResultByFile = {}
46
+ let skippableSuites = []
47
+ let isForcedToRun = false
48
+ let isUnskippable = false
42
49
 
43
50
  function getSuiteStatusFromTestStatuses (testStatuses) {
44
51
  if (testStatuses.some(status => status === 'fail')) {
@@ -91,7 +98,11 @@ function wrapRun (pl, isLatestVersion) {
91
98
  const testSuiteFullPath = this.pickle.uri
92
99
 
93
100
  if (!pickleResultByFile[testSuiteFullPath]) { // first test in suite
94
- testSuiteStartCh.publish(testSuiteFullPath)
101
+ isUnskippable = isMarkedAsUnskippable(this.pickle)
102
+ const testSuitePath = getTestSuitePath(testSuiteFullPath, process.cwd())
103
+ isForcedToRun = isUnskippable && skippableSuites.includes(testSuitePath)
104
+
105
+ testSuiteStartCh.publish({ testSuitePath, isUnskippable, isForcedToRun })
95
106
  }
96
107
 
97
108
  const testSourceLine = this.gherkinDocument &&
@@ -221,8 +232,11 @@ function getFilteredPickles (runtime, suitesToSkip) {
221
232
  return runtime.pickleIds.reduce((acc, pickleId) => {
222
233
  const test = runtime.eventDataCollector.getPickle(pickleId)
223
234
  const testSuitePath = getTestSuitePath(test.uri, process.cwd())
235
+
236
+ const isUnskippable = isMarkedAsUnskippable(test)
224
237
  const isSkipped = suitesToSkip.includes(testSuitePath)
225
- if (isSkipped) {
238
+
239
+ if (isSkipped && !isUnskippable) {
226
240
  acc.skippedSuites.add(testSuitePath)
227
241
  } else {
228
242
  acc.picklesToRun.push(pickleId)
@@ -270,7 +284,11 @@ addHook({
270
284
  skippableSuitesCh.publish({ onDone })
271
285
  })
272
286
 
273
- const { err, skippableSuites } = await skippableSuitesPromise
287
+ const skippableResponse = await skippableSuitesPromise
288
+
289
+ const err = skippableResponse.err
290
+ skippableSuites = skippableResponse.skippableSuites
291
+
274
292
  let skippedSuites = []
275
293
  let isSuitesSkipped = false
276
294
 
@@ -315,7 +333,9 @@ addHook({
315
333
  status: success ? 'pass' : 'fail',
316
334
  isSuitesSkipped,
317
335
  testCodeCoverageLinesTotal,
318
- numSkippedSuites: skippedSuites.length
336
+ numSkippedSuites: skippedSuites.length,
337
+ hasUnskippableSuites: isUnskippable,
338
+ hasForcedToRunSuites: isForcedToRun
319
339
  })
320
340
  })
321
341
  return success
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook
6
+ } = require('./helpers/instrument')
7
+ const shimmer = require('../../datadog-shimmer')
8
+
9
+ const sanitizeMethodFinished = channel('datadog:express-mongo-sanitize:sanitize:finish')
10
+ const sanitizeMiddlewareFinished = channel('datadog:express-mongo-sanitize:filter:finish')
11
+
12
+ const propertiesToSanitize = ['body', 'params', 'headers', 'query']
13
+
14
+ addHook({ name: 'express-mongo-sanitize', versions: ['>=1.0.0'] }, expressMongoSanitize => {
15
+ shimmer.wrap(expressMongoSanitize, 'sanitize', sanitize => function () {
16
+ const sanitizedObject = sanitize.apply(this, arguments)
17
+
18
+ if (sanitizeMethodFinished.hasSubscribers) {
19
+ sanitizeMethodFinished.publish({ sanitizedObject })
20
+ }
21
+
22
+ return sanitizedObject
23
+ })
24
+
25
+ return shimmer.wrap(expressMongoSanitize, function () {
26
+ const middleware = expressMongoSanitize.apply(this, arguments)
27
+
28
+ return shimmer.wrap(middleware, function (req, res, next) {
29
+ if (!sanitizeMiddlewareFinished.hasSubscribers) {
30
+ return middleware.apply(this, arguments)
31
+ }
32
+
33
+ const wrappedNext = shimmer.wrap(next, function () {
34
+ sanitizeMiddlewareFinished.publish({
35
+ sanitizedProperties: propertiesToSanitize,
36
+ req
37
+ })
38
+
39
+ return next.apply(this, arguments)
40
+ })
41
+
42
+ return middleware.call(this, req, res, wrappedNext)
43
+ })
44
+ })
45
+ })
@@ -33,8 +33,9 @@ function publishQueryParsedAndNext (req, res, next) {
33
33
  return function () {
34
34
  if (queryParserReadCh.hasSubscribers && req) {
35
35
  const abortController = new AbortController()
36
+ const query = req.query
36
37
 
37
- queryParserReadCh.publish({ req, res, abortController })
38
+ queryParserReadCh.publish({ req, res, query, abortController })
38
39
 
39
40
  if (abortController.signal.aborted) return
40
41
  }
@@ -38,6 +38,7 @@ module.exports = {
38
38
  'dns': () => require('../dns'),
39
39
  'elasticsearch': () => require('../elasticsearch'),
40
40
  'express': () => require('../express'),
41
+ 'express-mongo-sanitize': () => require('../express-mongo-sanitize'),
41
42
  'fastify': () => require('../fastify'),
42
43
  'find-my-way': () => require('../find-my-way'),
43
44
  'fs': () => require('../fs'),
@@ -68,7 +69,7 @@ module.exports = {
68
69
  'mocha': () => require('../mocha'),
69
70
  'mocha-each': () => require('../mocha'),
70
71
  'moleculer': () => require('../moleculer'),
71
- 'mongodb': () => require('../mongodb-core'),
72
+ 'mongodb': () => require('../mongodb'),
72
73
  'mongodb-core': () => require('../mongodb-core'),
73
74
  'mongoose': () => require('../mongoose'),
74
75
  'mysql': () => require('../mysql'),
@@ -46,6 +46,8 @@ let isCodeCoverageEnabled = false
46
46
  let isSuitesSkippingEnabled = false
47
47
  let isSuitesSkipped = false
48
48
  let numSkippedSuites = 0
49
+ let hasUnskippableSuites = false
50
+ let hasForcedToRunSuites = false
49
51
 
50
52
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
51
53
 
@@ -205,15 +207,17 @@ addHook({
205
207
  const [test] = shardedTests
206
208
  const rootDir = test && test.context && test.context.config && test.context.config.rootDir
207
209
 
208
- const { skippedSuites, suitesToRun } = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
210
+ const jestSuitesToRun = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
211
+ hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
212
+ hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
209
213
 
210
- isSuitesSkipped = suitesToRun.length !== shardedTests.length
211
- numSkippedSuites = skippedSuites.length
214
+ isSuitesSkipped = jestSuitesToRun.suitesToRun.length !== shardedTests.length
215
+ numSkippedSuites = jestSuitesToRun.skippedSuites.length
212
216
 
213
- itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
217
+ itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
214
218
 
215
219
  skippableSuites = []
216
- return suitesToRun
220
+ return jestSuitesToRun.suitesToRun
217
221
  })
218
222
  return sequencerPackage
219
223
  })
@@ -285,7 +289,9 @@ function cliWrapper (cli, jestVersion) {
285
289
  isSuitesSkippingEnabled,
286
290
  isCodeCoverageEnabled,
287
291
  testCodeCoverageLinesTotal,
288
- numSkippedSuites
292
+ numSkippedSuites,
293
+ hasUnskippableSuites,
294
+ hasForcedToRunSuites
289
295
  })
290
296
  })
291
297
 
@@ -500,16 +506,19 @@ addHook({
500
506
  const testPaths = await getTestPaths.apply(this, arguments)
501
507
  const { tests } = testPaths
502
508
 
503
- const { skippedSuites, suitesToRun } = getJestSuitesToRun(skippableSuites, tests, rootDir)
509
+ const jestSuitesToRun = getJestSuitesToRun(skippableSuites, tests, rootDir)
504
510
 
505
- isSuitesSkipped = suitesToRun.length !== tests.length
506
- numSkippedSuites = skippedSuites.length
511
+ hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
512
+ hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
507
513
 
508
- itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
514
+ isSuitesSkipped = jestSuitesToRun.suitesToRun.length !== tests.length
515
+ numSkippedSuites = jestSuitesToRun.skippedSuites.length
516
+
517
+ itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
509
518
 
510
519
  skippableSuites = []
511
520
 
512
- return { ...testPaths, tests: suitesToRun }
521
+ return { ...testPaths, tests: jestSuitesToRun.suitesToRun }
513
522
  })
514
523
 
515
524
  return searchSourcePackage
@@ -1,9 +1,12 @@
1
1
  'use strict'
2
2
 
3
- const { addHook } = require('./helpers/instrument')
3
+ const { addHook, channel } = require('./helpers/instrument')
4
4
  const { wrapThen } = require('./helpers/promise')
5
5
  const shimmer = require('../../datadog-shimmer')
6
6
 
7
+ const startRawQueryCh = channel('datadog:knex:raw:start')
8
+ const finishRawQueryCh = channel('datadog:knex:raw:finish')
9
+
7
10
  patch('lib/query/builder.js')
8
11
  patch('lib/raw.js')
9
12
  patch('lib/schema/builder.js')
@@ -18,3 +21,61 @@ function patch (file) {
18
21
  return Builder
19
22
  })
20
23
  }
24
+
25
+ addHook({
26
+ name: 'knex',
27
+ versions: ['>=2'],
28
+ file: 'lib/knex-builder/Knex.js'
29
+ }, Knex => {
30
+ shimmer.wrap(Knex.Client.prototype, 'raw', raw => function () {
31
+ if (!startRawQueryCh.hasSubscribers) {
32
+ return raw.apply(this, arguments)
33
+ }
34
+
35
+ const sql = arguments[0]
36
+
37
+ // Skip query done by Knex to get the value used for undefined
38
+ if (sql === 'DEFAULT') {
39
+ return raw.apply(this, arguments)
40
+ }
41
+
42
+ function finish () {
43
+ finishRawQueryCh.publish()
44
+ }
45
+
46
+ startRawQueryCh.publish({ sql, dialect: this.dialect })
47
+
48
+ const rawResult = raw.apply(this, arguments)
49
+
50
+ shimmer.wrap(rawResult, 'then', originalThen => function () {
51
+ arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
52
+ arguments[1] = wrapCallbackWithFinish(arguments[1], finish)
53
+
54
+ const originalThenResult = originalThen.apply(this, arguments)
55
+
56
+ shimmer.wrap(originalThenResult, 'catch', originalCatch => function () {
57
+ arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
58
+ return originalCatch.apply(this, arguments)
59
+ })
60
+
61
+ return originalThenResult
62
+ })
63
+
64
+ shimmer.wrap(rawResult, 'asCallback', originalAsCallback => function () {
65
+ arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
66
+ return originalAsCallback.apply(this, arguments)
67
+ })
68
+
69
+ return rawResult
70
+ })
71
+ return Knex
72
+ })
73
+
74
+ function wrapCallbackWithFinish (callback, finish) {
75
+ if (typeof callback !== 'function') return callback
76
+
77
+ return function () {
78
+ finish()
79
+ callback.apply(this, arguments)
80
+ }
81
+ }
@@ -1,9 +1,10 @@
1
1
  const { createCoverageMap } = require('istanbul-lib-coverage')
2
2
 
3
+ const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
4
+
3
5
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
4
6
  const shimmer = require('../../datadog-shimmer')
5
7
  const log = require('../../dd-trace/src/log')
6
-
7
8
  const {
8
9
  getCoveredFilenamesFromCoverage,
9
10
  resetCoverage,
@@ -50,6 +51,8 @@ let suitesToSkip = []
50
51
  let frameworkVersion
51
52
  let isSuitesSkipped = false
52
53
  let skippedSuites = []
54
+ const unskippableSuites = []
55
+ let isForcedToRun = false
53
56
 
54
57
  function getSuitesByTestFile (root) {
55
58
  const suitesByTestFile = {}
@@ -104,7 +107,8 @@ function getFilteredSuites (originalSuites) {
104
107
  return originalSuites.reduce((acc, suite) => {
105
108
  const testPath = getTestSuitePath(suite.file, process.cwd())
106
109
  const shouldSkip = suitesToSkip.includes(testPath)
107
- if (shouldSkip) {
110
+ const isUnskippable = unskippableSuites.includes(suite.file)
111
+ if (shouldSkip && !isUnskippable) {
108
112
  acc.skippedSuites.add(testPath)
109
113
  } else {
110
114
  acc.suitesToRun.push(suite)
@@ -151,7 +155,9 @@ function mochaHook (Runner) {
151
155
  status,
152
156
  isSuitesSkipped,
153
157
  testCodeCoverageLinesTotal,
154
- numSkippedSuites: skippedSuites.length
158
+ numSkippedSuites: skippedSuites.length,
159
+ hasForcedToRunSuites: isForcedToRun,
160
+ hasUnskippableSuites: !!unskippableSuites.length
155
161
  })
156
162
  }))
157
163
 
@@ -172,8 +178,10 @@ function mochaHook (Runner) {
172
178
  if (!asyncResource) {
173
179
  asyncResource = new AsyncResource('bound-anonymous-fn')
174
180
  testFileToSuiteAr.set(suite.file, asyncResource)
181
+ const isUnskippable = unskippableSuites.includes(suite.file)
182
+ isForcedToRun = isUnskippable && suitesToSkip.includes(getTestSuitePath(suite.file, process.cwd()))
175
183
  asyncResource.runInAsyncScope(() => {
176
- testSuiteStartCh.publish(suite)
184
+ testSuiteStartCh.publish({ testSuite: suite.file, isUnskippable, isForcedToRun })
177
185
  })
178
186
  }
179
187
  })
@@ -370,6 +378,13 @@ addHook({
370
378
 
371
379
  const runner = run.apply(this, arguments)
372
380
 
381
+ this.files.forEach(path => {
382
+ const isUnskippable = isMarkedAsUnskippable({ path })
383
+ if (isUnskippable) {
384
+ unskippableSuites.push(path)
385
+ }
386
+ })
387
+
373
388
  const onReceivedSkippableSuites = ({ err, skippableSuites }) => {
374
389
  if (err) {
375
390
  suitesToSkip = []
@@ -0,0 +1,63 @@
1
+ 'use strict'
2
+
3
+ require('./mongodb-core')
4
+
5
+ const {
6
+ channel,
7
+ addHook,
8
+ AsyncResource
9
+ } = require('./helpers/instrument')
10
+ const shimmer = require('../../datadog-shimmer')
11
+
12
+ // collection methods with filter
13
+ const collectionMethodsWithFilter = [
14
+ 'count',
15
+ 'countDocuments',
16
+ 'deleteMany',
17
+ 'deleteOne',
18
+ 'find',
19
+ 'findOneAndDelete',
20
+ 'findOneAndReplace',
21
+ 'replaceOne'
22
+ ] // findOne is ignored because it calls to find
23
+
24
+ const collectionMethodsWithTwoFilters = [
25
+ 'findOneAndUpdate',
26
+ 'updateMany',
27
+ 'updateOne'
28
+ ]
29
+
30
+ const startCh = channel('datadog:mongodb:collection:filter:start')
31
+
32
+ addHook({ name: 'mongodb', versions: ['>=3.3 <5', '5', '>=6'] }, mongodb => {
33
+ [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters].forEach(methodName => {
34
+ if (!(methodName in mongodb.Collection.prototype)) return
35
+
36
+ const useTwoArguments = collectionMethodsWithTwoFilters.includes(methodName)
37
+
38
+ shimmer.wrap(mongodb.Collection.prototype, methodName, method => {
39
+ return function () {
40
+ if (!startCh.hasSubscribers) {
41
+ return method.apply(this, arguments)
42
+ }
43
+
44
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
45
+
46
+ return asyncResource.runInAsyncScope(() => {
47
+ const filters = [arguments[0]]
48
+ if (useTwoArguments) {
49
+ filters.push(arguments[1])
50
+ }
51
+
52
+ startCh.publish({
53
+ filters,
54
+ methodName
55
+ })
56
+
57
+ return method.apply(this, arguments)
58
+ })
59
+ }
60
+ })
61
+ })
62
+ return mongodb
63
+ })
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { addHook } = require('./helpers/instrument')
3
+ const { addHook, channel } = require('./helpers/instrument')
4
4
  const { wrapThen } = require('./helpers/promise')
5
5
  const { AsyncResource } = require('./helpers/instrument')
6
6
  const shimmer = require('../../datadog-shimmer')
@@ -26,5 +26,144 @@ addHook({
26
26
  }
27
27
 
28
28
  shimmer.wrap(mongoose.Collection.prototype, 'addQueue', wrapAddQueue)
29
+
29
30
  return mongoose
30
31
  })
32
+
33
+ const startCh = channel('datadog:mongoose:model:filter:start')
34
+ const finishCh = channel('datadog:mongoose:model:filter:finish')
35
+
36
+ const collectionMethodsWithFilter = [
37
+ 'count',
38
+ 'countDocuments',
39
+ 'deleteMany',
40
+ 'deleteOne',
41
+ 'find',
42
+ 'findOne',
43
+ 'findOneAndDelete',
44
+ 'findOneAndReplace',
45
+ 'replaceOne',
46
+ 'remove'
47
+ ]
48
+
49
+ const collectionMethodsWithTwoFilters = [
50
+ 'findOneAndUpdate',
51
+ 'updateMany',
52
+ 'updateOne'
53
+ ]
54
+
55
+ addHook({
56
+ name: 'mongoose',
57
+ versions: ['>=4.6.4 <5', '5', '6', '>=7'],
58
+ file: 'lib/model.js'
59
+ }, Model => {
60
+ [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters].forEach(methodName => {
61
+ const useTwoArguments = collectionMethodsWithTwoFilters.includes(methodName)
62
+ if (!(methodName in Model)) return
63
+
64
+ shimmer.wrap(Model, methodName, method => {
65
+ return function wrappedModelMethod () {
66
+ if (!startCh.hasSubscribers) {
67
+ return method.apply(this, arguments)
68
+ }
69
+
70
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
71
+
72
+ const filters = [arguments[0]]
73
+ if (useTwoArguments) {
74
+ filters.push(arguments[1])
75
+ }
76
+
77
+ const finish = asyncResource.bind(function () {
78
+ finishCh.publish()
79
+ })
80
+
81
+ let callbackWrapped = false
82
+ const lastArgumentIndex = arguments.length - 1
83
+
84
+ if (typeof arguments[lastArgumentIndex] === 'function') {
85
+ // is a callback, wrap it to execute finish()
86
+ shimmer.wrap(arguments, lastArgumentIndex, originalCb => {
87
+ return function () {
88
+ finish()
89
+
90
+ return originalCb.apply(this, arguments)
91
+ }
92
+ })
93
+
94
+ callbackWrapped = true
95
+ }
96
+
97
+ return asyncResource.runInAsyncScope(() => {
98
+ startCh.publish({
99
+ filters,
100
+ methodName
101
+ })
102
+
103
+ const res = method.apply(this, arguments)
104
+
105
+ // if it is not callback, wrap exec method and its then
106
+ if (!callbackWrapped) {
107
+ shimmer.wrap(res, 'exec', originalExec => {
108
+ return function wrappedExec () {
109
+ const execResult = originalExec.apply(this, arguments)
110
+
111
+ // wrap them method, wrap resolve and reject methods
112
+ shimmer.wrap(execResult, 'then', originalThen => {
113
+ return function wrappedThen () {
114
+ const resolve = arguments[0]
115
+ const reject = arguments[1]
116
+
117
+ // not using shimmer here because resolve/reject could be empty
118
+ arguments[0] = function wrappedResolve () {
119
+ finish()
120
+
121
+ if (resolve) {
122
+ return resolve.apply(this, arguments)
123
+ }
124
+ }
125
+
126
+ arguments[1] = function wrappedReject () {
127
+ finish()
128
+
129
+ if (reject) {
130
+ return reject.apply(this, arguments)
131
+ }
132
+ }
133
+
134
+ return originalThen.apply(this, arguments)
135
+ }
136
+ })
137
+
138
+ return execResult
139
+ }
140
+ })
141
+ }
142
+ return res
143
+ })
144
+ }
145
+ })
146
+ })
147
+
148
+ return Model
149
+ })
150
+
151
+ const sanitizeFilterFinishCh = channel('datadog:mongoose:sanitize-filter:finish')
152
+
153
+ addHook({
154
+ name: 'mongoose',
155
+ versions: ['6', '>=7'],
156
+ file: 'lib/helpers/query/sanitizeFilter.js'
157
+ }, sanitizeFilter => {
158
+ return shimmer.wrap(sanitizeFilter, function wrappedSanitizeFilter () {
159
+ const sanitizedObject = sanitizeFilter.apply(this, arguments)
160
+
161
+ if (sanitizeFilterFinishCh.hasSubscribers) {
162
+ sanitizeFilterFinishCh.publish({
163
+ sanitizedObject
164
+ })
165
+ }
166
+
167
+ return sanitizedObject
168
+ })
169
+ })
@@ -10,6 +10,8 @@ const startChannel = channel('apm:next:request:start')
10
10
  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
+ const bodyParsedChannel = channel('apm:next:body-parsed')
14
+ const queryParsedChannel = channel('apm:next:query-parsed')
13
15
 
14
16
  const requests = new WeakSet()
15
17
 
@@ -212,3 +214,41 @@ addHook({
212
214
 
213
215
  return nextServer
214
216
  })
217
+
218
+ addHook({
219
+ name: 'next',
220
+ versions: ['>=13'],
221
+ file: 'dist/server/web/spec-extension/request.js'
222
+ }, request => {
223
+ const nextUrlDescriptor = Object.getOwnPropertyDescriptor(request.NextRequest.prototype, 'nextUrl')
224
+ shimmer.wrap(nextUrlDescriptor, 'get', function (originalGet) {
225
+ return function wrappedGet () {
226
+ const nextUrl = originalGet.apply(this, arguments)
227
+ if (queryParsedChannel.hasSubscribers) {
228
+ const query = {}
229
+ for (const key of nextUrl.searchParams.keys()) {
230
+ if (!query[key]) {
231
+ query[key] = nextUrl.searchParams.getAll(key)
232
+ }
233
+ }
234
+
235
+ queryParsedChannel.publish({ query })
236
+ }
237
+ return nextUrl
238
+ }
239
+ })
240
+
241
+ Object.defineProperty(request.NextRequest.prototype, 'nextUrl', nextUrlDescriptor)
242
+
243
+ shimmer.massWrap(request.NextRequest.prototype, ['text', 'json'], function (originalMethod) {
244
+ return async function wrappedJson () {
245
+ const body = await originalMethod.apply(this, arguments)
246
+ bodyParsedChannel.publish({
247
+ body
248
+ })
249
+ return body
250
+ }
251
+ })
252
+
253
+ return request
254
+ })