dd-trace 5.2.0 → 5.4.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 (86) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +1 -32
  3. package/ci/init.js +1 -4
  4. package/index.d.ts +21 -0
  5. package/package.json +7 -6
  6. package/packages/datadog-instrumentations/src/amqplib.js +1 -1
  7. package/packages/datadog-instrumentations/src/child_process.js +150 -0
  8. package/packages/datadog-instrumentations/src/cucumber.js +12 -12
  9. package/packages/datadog-instrumentations/src/express.js +20 -0
  10. package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
  12. package/packages/datadog-instrumentations/src/jest.js +149 -11
  13. package/packages/datadog-instrumentations/src/mocha.js +142 -16
  14. package/packages/datadog-instrumentations/src/mongoose.js +23 -10
  15. package/packages/datadog-instrumentations/src/next.js +17 -3
  16. package/packages/datadog-instrumentations/src/playwright.js +41 -9
  17. package/packages/datadog-plugin-amqplib/src/consumer.js +10 -1
  18. package/packages/datadog-plugin-amqplib/src/producer.js +14 -1
  19. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +107 -1
  20. package/packages/datadog-plugin-child_process/src/index.js +91 -0
  21. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
  22. package/packages/datadog-plugin-cucumber/src/index.js +16 -11
  23. package/packages/datadog-plugin-cypress/src/plugin.js +52 -23
  24. package/packages/datadog-plugin-grpc/src/client.js +16 -2
  25. package/packages/datadog-plugin-http/src/client.js +1 -1
  26. package/packages/datadog-plugin-jest/src/index.js +43 -6
  27. package/packages/datadog-plugin-kafkajs/src/consumer.js +16 -0
  28. package/packages/datadog-plugin-mocha/src/index.js +47 -17
  29. package/packages/datadog-plugin-playwright/src/index.js +19 -5
  30. package/packages/datadog-plugin-rhea/src/consumer.js +11 -1
  31. package/packages/datadog-plugin-rhea/src/producer.js +11 -0
  32. package/packages/dd-trace/src/appsec/addresses.js +2 -0
  33. package/packages/dd-trace/src/appsec/api_security_sampler.js +16 -3
  34. package/packages/dd-trace/src/appsec/channels.js +2 -1
  35. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
  36. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
  37. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
  38. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
  39. package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
  40. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +12 -1
  41. package/packages/dd-trace/src/appsec/iast/index.js +4 -4
  42. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
  44. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
  45. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
  46. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
  47. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
  48. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
  49. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
  50. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +29 -2
  51. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  52. package/packages/dd-trace/src/appsec/index.js +17 -2
  53. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  54. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
  55. package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
  56. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +83 -0
  57. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
  58. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
  59. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +83 -41
  60. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +30 -8
  61. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -1
  62. package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → requests/get-library-configuration.js} +18 -6
  63. package/packages/dd-trace/src/config.js +22 -9
  64. package/packages/dd-trace/src/datastreams/processor.js +6 -0
  65. package/packages/dd-trace/src/datastreams/writer.js +2 -5
  66. package/packages/dd-trace/src/dogstatsd.js +3 -5
  67. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +5 -3
  68. package/packages/dd-trace/src/exporters/common/request.js +21 -3
  69. package/packages/dd-trace/src/format.js +25 -1
  70. package/packages/dd-trace/src/noop/span.js +1 -0
  71. package/packages/dd-trace/src/opentelemetry/span.js +9 -2
  72. package/packages/dd-trace/src/opentracing/span.js +38 -0
  73. package/packages/dd-trace/src/opentracing/span_context.js +12 -6
  74. package/packages/dd-trace/src/opentracing/tracer.js +2 -1
  75. package/packages/dd-trace/src/plugins/ci_plugin.js +25 -9
  76. package/packages/dd-trace/src/plugins/index.js +1 -0
  77. package/packages/dd-trace/src/plugins/util/git.js +6 -0
  78. package/packages/dd-trace/src/plugins/util/test.js +53 -8
  79. package/packages/dd-trace/src/profiling/config.js +22 -22
  80. package/packages/dd-trace/src/proxy.js +31 -23
  81. package/packages/dd-trace/src/span_processor.js +5 -1
  82. package/packages/dd-trace/src/telemetry/index.js +6 -0
  83. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  84. package/packages/dd-trace/src/telemetry/send-data.js +0 -3
  85. package/packages/datadog-instrumentations/src/child-process.js +0 -29
  86. package/packages/dd-trace/src/plugins/util/exec.js +0 -34
@@ -30,6 +30,7 @@ require,protobufjs,BSD-3-Clause,Copyright 2016 Daniel Wirtz
30
30
  require,tlhunter-sorted-set,MIT,Copyright (c) 2023 Datadog Inc.
31
31
  require,retry,MIT,Copyright 2011 Tim Koschützki Felix Geisendörfer
32
32
  require,semver,ISC,Copyright Isaac Z. Schlueter and Contributors
33
+ require,shell-quote,mit,Copyright (c) 2013 James Halliday
33
34
  dev,@types/node,MIT,Copyright Authors
34
35
  dev,autocannon,MIT,Copyright 2016 Matteo Collina
35
36
  dev,aws-sdk,Apache 2.0,Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
package/README.md CHANGED
@@ -194,38 +194,7 @@ Regardless of where you open the issue, someone at Datadog will try to help.
194
194
 
195
195
  ## Bundling
196
196
 
197
- Generally, `dd-trace` works by intercepting `require()` calls that a Node.js application makes when loading modules. This includes modules that are built-in to Node.js, like the `fs` module for accessing the filesystem, as well as modules installed from the npm registry, like the `pg` database module.
198
-
199
- Also generally, bundlers work by crawling all of the `require()` calls that an application makes to files on disk, replacing the `require()` calls with custom code, and then concatenating all of the resulting JavaScript into one "bundled" file. When a built-in module is loaded, like `require('fs')`, that call can then remain the same in the resulting bundle.
200
-
201
- Fundamentally APM tools like `dd-trace` stop working at this point. Perhaps they continue to intercept the calls for built-in modules but don't intercept calls to third party libraries. This means that by default when you bundle a `dd-trace` app with a bundler it is likely to capture information about disk access (via `fs`) and outbound HTTP requests (via `http`), but will otherwise omit calls to third party libraries (like extracting incoming request route information for the `express` framework or showing which query is run for the `mysql` database client).
202
-
203
- To get around this, one can treat all third party modules, or at least third party modules that the APM needs to instrument, as being "external" to the bundler. With this setting the instrumented modules remain on disk and continue to be loaded via `require()` while the non-instrumented modules are bundled. Sadly this results in a build with many extraneous files and starts to defeat the purpose of bundling.
204
-
205
- For these reasons it's necessary to have custom-built bundler plugins. Such plugins are able to instruct the bundler on how to behave, injecting intermediary code and otherwise intercepting the "translated" `require()` calls. The result is that many more packages are then included in the bundled JavaScript file. Some applications can have 100% of modules bundled, however native modules still need to remain external to the bundle.
206
-
207
- ### ESBuild Support
208
-
209
- This library provides experimental ESBuild support in the form of an ESBuild plugin. Require the `dd-trace/esbuild` module when building your bundle to enable the plugin.
210
-
211
- Here's an example of how one might use `dd-trace` with ESBuild:
212
-
213
- ```javascript
214
- const ddPlugin = require('dd-trace/esbuild')
215
- const esbuild = require('esbuild')
216
-
217
- esbuild.build({
218
- entryPoints: ['app.js'],
219
- bundle: true,
220
- outfile: 'out.js',
221
- plugins: [ddPlugin],
222
- platform: 'node', // allows built-in modules to be required
223
- target: ['node18']
224
- }).catch((err) => {
225
- console.error(err)
226
- process.exit(1)
227
- })
228
- ```
197
+ If you would like to trace your bundled application then please read this page on [bundling and dd-trace](https://docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/dd_libraries/nodejs/#bundling). It includes information on how to use our ESBuild plugin and includes caveats for other bundlers.
229
198
 
230
199
 
231
200
  ## Security Vulnerabilities
package/ci/init.js CHANGED
@@ -1,15 +1,11 @@
1
1
  /* eslint-disable no-console */
2
2
  const tracer = require('../packages/dd-trace')
3
- const { ORIGIN_KEY } = require('../packages/dd-trace/src/constants')
4
3
  const { isTrue } = require('../packages/dd-trace/src/util')
5
4
 
6
5
  const isJestWorker = !!process.env.JEST_WORKER_ID
7
6
 
8
7
  const options = {
9
8
  startupLogs: false,
10
- tags: {
11
- [ORIGIN_KEY]: 'ciapp-test'
12
- },
13
9
  isCiVisibility: true,
14
10
  flushInterval: isJestWorker ? 0 : 5000
15
11
  }
@@ -44,6 +40,7 @@ if (isJestWorker) {
44
40
  if (shouldInit) {
45
41
  tracer.init(options)
46
42
  tracer.use('fs', false)
43
+ tracer.use('child_process', false)
47
44
  }
48
45
 
49
46
  module.exports = tracer
package/index.d.ts CHANGED
@@ -143,6 +143,11 @@ export declare interface TraceOptions extends Analyzable {
143
143
  * The type of request.
144
144
  */
145
145
  type?: string
146
+
147
+ /**
148
+ * An array of span links
149
+ */
150
+ links?: Array<{ context: SpanContext, attributes?: Object }>
146
151
  }
147
152
 
148
153
  /**
@@ -154,6 +159,14 @@ export declare interface TraceOptions extends Analyzable {
154
159
  */
155
160
  export declare interface Span extends opentracing.Span {
156
161
  context (): SpanContext;
162
+
163
+ /**
164
+ * Causally links another span to the current span
165
+ * @param {SpanContext} context The context of the span to link to.
166
+ * @param {Object} attributes An optional key value pair of arbitrary values.
167
+ * @returns {void}
168
+ */
169
+ addLink (context: SpanContext, attributes?: Object): void;
157
170
  }
158
171
 
159
172
  /**
@@ -1903,6 +1916,14 @@ export namespace opentelemetry {
1903
1916
  * use the current time.
1904
1917
  */
1905
1918
  recordException(exception: Exception, time?: TimeInput): void;
1919
+
1920
+ /**
1921
+ * Causally links another span to the current span
1922
+ * @param {otel.SpanContext} context The context of the span to link to.
1923
+ * @param {SpanAttributes} attributes An optional key value pair of arbitrary values.
1924
+ * @returns {void}
1925
+ */
1926
+ addLink (context: otel.SpanContext, attributes?: SpanAttributes): void;
1906
1927
  }
1907
1928
 
1908
1929
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.2.0",
3
+ "version": "5.4.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -70,15 +70,15 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "@datadog/native-appsec": "7.0.0",
73
- "@datadog/native-iast-rewriter": "2.2.2",
74
- "@datadog/native-iast-taint-tracking": "1.6.4",
73
+ "@datadog/native-iast-rewriter": "2.2.3",
74
+ "@datadog/native-iast-taint-tracking": "1.7.0",
75
75
  "@datadog/native-metrics": "^2.0.0",
76
76
  "@datadog/pprof": "5.0.0",
77
77
  "@datadog/sketches-js": "^2.1.0",
78
78
  "@opentelemetry/api": "^1.0.0",
79
79
  "@opentelemetry/core": "^1.14.0",
80
80
  "crypto-randomuuid": "^1.0.0",
81
- "dc-polyfill": "^0.1.2",
81
+ "dc-polyfill": "^0.1.4",
82
82
  "ignore": "^5.2.4",
83
83
  "import-in-the-middle": "^1.7.3",
84
84
  "int64-buffer": "^0.1.9",
@@ -99,13 +99,14 @@
99
99
  "protobufjs": "^7.2.5",
100
100
  "retry": "^0.13.1",
101
101
  "semver": "^7.5.4",
102
+ "shell-quote": "^1.8.1",
102
103
  "tlhunter-sorted-set": "^0.1.0"
103
104
  },
104
105
  "devDependencies": {
105
106
  "@types/node": ">=18",
106
107
  "autocannon": "^4.5.2",
107
108
  "aws-sdk": "^2.1446.0",
108
- "axios": "^0.21.2",
109
+ "axios": "^1.6.7",
109
110
  "benchmark": "^2.1.4",
110
111
  "body-parser": "^1.20.2",
111
112
  "chai": "^4.3.7",
@@ -129,7 +130,7 @@
129
130
  "jszip": "^3.5.0",
130
131
  "knex": "^2.4.2",
131
132
  "mkdirp": "^3.0.1",
132
- "mocha": "8",
133
+ "mocha": "^9",
133
134
  "multer": "^1.4.5-lts.1",
134
135
  "nock": "^11.3.3",
135
136
  "nyc": "^15.1.0",
@@ -28,7 +28,7 @@ addHook({ name: 'amqplib', file: 'lib/channel.js', versions: ['>=0.5'] }, channe
28
28
  })
29
29
 
30
30
  shimmer.wrap(channel.Channel.prototype, 'sendMessage', sendMessage => function (fields) {
31
- return instrument(sendMessage, this, arguments, 'basic.publish', fields)
31
+ return instrument(sendMessage, this, arguments, 'basic.publish', fields, arguments[2])
32
32
  })
33
33
 
34
34
  shimmer.wrap(channel.BaseChannel.prototype, 'dispatchMessage', dispatchMessage => function (fields, message) {
@@ -0,0 +1,150 @@
1
+ 'use strict'
2
+
3
+ const util = require('util')
4
+
5
+ const {
6
+ addHook,
7
+ AsyncResource
8
+ } = require('./helpers/instrument')
9
+ const shimmer = require('../../datadog-shimmer')
10
+ const dc = require('dc-polyfill')
11
+
12
+ const childProcessChannel = dc.tracingChannel('datadog:child_process:execution')
13
+
14
+ // ignored exec method because it calls to execFile directly
15
+ const execAsyncMethods = ['execFile', 'spawn']
16
+ const execSyncMethods = ['execFileSync', 'spawnSync']
17
+
18
+ const names = ['child_process', 'node:child_process']
19
+
20
+ // child_process and node:child_process returns the same object instance, we only want to add hooks once
21
+ let patched = false
22
+ names.forEach(name => {
23
+ addHook({ name }, childProcess => {
24
+ if (!patched) {
25
+ patched = true
26
+ shimmer.massWrap(childProcess, execAsyncMethods, wrapChildProcessAsyncMethod())
27
+ shimmer.massWrap(childProcess, execSyncMethods, wrapChildProcessSyncMethod())
28
+ shimmer.wrap(childProcess, 'execSync', wrapChildProcessSyncMethod(true))
29
+ }
30
+
31
+ return childProcess
32
+ })
33
+ })
34
+
35
+ function normalizeArgs (args, shell) {
36
+ const childProcessInfo = {
37
+ command: args[0]
38
+ }
39
+
40
+ if (Array.isArray(args[1])) {
41
+ childProcessInfo.command = childProcessInfo.command + ' ' + args[1].join(' ')
42
+ if (args[2] != null && typeof args[2] === 'object') {
43
+ childProcessInfo.options = args[2]
44
+ }
45
+ } else if (args[1] != null && typeof args[1] === 'object') {
46
+ childProcessInfo.options = args[1]
47
+ }
48
+ childProcessInfo.shell = shell ||
49
+ childProcessInfo.options?.shell === true ||
50
+ typeof childProcessInfo.options?.shell === 'string'
51
+
52
+ return childProcessInfo
53
+ }
54
+
55
+ function wrapChildProcessSyncMethod (shell = false) {
56
+ return function wrapMethod (childProcessMethod) {
57
+ return function () {
58
+ if (!childProcessChannel.start.hasSubscribers || arguments.length === 0) {
59
+ return childProcessMethod.apply(this, arguments)
60
+ }
61
+
62
+ const childProcessInfo = normalizeArgs(arguments, shell)
63
+
64
+ return childProcessChannel.traceSync(
65
+ childProcessMethod,
66
+ {
67
+ command: childProcessInfo.command,
68
+ shell: childProcessInfo.shell
69
+ },
70
+ this,
71
+ ...arguments)
72
+ }
73
+ }
74
+ }
75
+
76
+ function wrapChildProcessCustomPromisifyMethod (customPromisifyMethod, shell) {
77
+ return function () {
78
+ if (!childProcessChannel.start.hasSubscribers || arguments.length === 0) {
79
+ return customPromisifyMethod.apply(this, arguments)
80
+ }
81
+
82
+ const childProcessInfo = normalizeArgs(arguments, shell)
83
+
84
+ return childProcessChannel.tracePromise(
85
+ customPromisifyMethod,
86
+ {
87
+ command: childProcessInfo.command,
88
+ shell: childProcessInfo.shell
89
+ },
90
+ this,
91
+ ...arguments)
92
+ }
93
+ }
94
+
95
+ function wrapChildProcessAsyncMethod (shell = false) {
96
+ return function wrapMethod (childProcessMethod) {
97
+ function wrappedChildProcessMethod () {
98
+ if (!childProcessChannel.start.hasSubscribers || arguments.length === 0) {
99
+ return childProcessMethod.apply(this, arguments)
100
+ }
101
+
102
+ const childProcessInfo = normalizeArgs(arguments, shell)
103
+
104
+ const innerResource = new AsyncResource('bound-anonymous-fn')
105
+ return innerResource.runInAsyncScope(() => {
106
+ childProcessChannel.start.publish({ command: childProcessInfo.command, shell: childProcessInfo.shell })
107
+
108
+ const childProcess = childProcessMethod.apply(this, arguments)
109
+ if (childProcess) {
110
+ let errorExecuted = false
111
+
112
+ childProcess.on('error', (e) => {
113
+ errorExecuted = true
114
+ childProcessChannel.error.publish(e)
115
+ })
116
+
117
+ childProcess.on('close', (code) => {
118
+ code = code || 0
119
+ if (!errorExecuted && code !== 0) {
120
+ childProcessChannel.error.publish()
121
+ }
122
+ childProcessChannel.asyncEnd.publish({
123
+ command: childProcessInfo.command,
124
+ shell: childProcessInfo.shell,
125
+ result: code
126
+ })
127
+ })
128
+ }
129
+
130
+ return childProcess
131
+ })
132
+ }
133
+
134
+ if (childProcessMethod[util.promisify.custom]) {
135
+ const wrapedChildProcessCustomPromisifyMethod =
136
+ shimmer.wrap(childProcessMethod[util.promisify.custom],
137
+ wrapChildProcessCustomPromisifyMethod(childProcessMethod[util.promisify.custom]), shell)
138
+
139
+ // should do it in this way because the original property is readonly
140
+ const descriptor = Object.getOwnPropertyDescriptor(childProcessMethod, util.promisify.custom)
141
+ Object.defineProperty(wrappedChildProcessMethod,
142
+ util.promisify.custom,
143
+ {
144
+ ...descriptor,
145
+ value: wrapedChildProcessCustomPromisifyMethod
146
+ })
147
+ }
148
+ return wrappedChildProcessMethod
149
+ }
150
+ }
@@ -16,7 +16,7 @@ const testSuiteStartCh = channel('ci:cucumber:test-suite:start')
16
16
  const testSuiteFinishCh = channel('ci:cucumber:test-suite:finish')
17
17
  const testSuiteCodeCoverageCh = channel('ci:cucumber:test-suite:code-coverage')
18
18
 
19
- const itrConfigurationCh = channel('ci:cucumber:itr-configuration')
19
+ const libraryConfigurationCh = channel('ci:cucumber:library-configuration')
20
20
  const skippableSuitesCh = channel('ci:cucumber:test-suite:skippable')
21
21
  const sessionStartCh = channel('ci:cucumber:session:start')
22
22
  const sessionFinishCh = channel('ci:cucumber:session:finish')
@@ -96,11 +96,11 @@ function wrapRun (pl, isLatestVersion) {
96
96
 
97
97
  const asyncResource = new AsyncResource('bound-anonymous-fn')
98
98
  return asyncResource.runInAsyncScope(() => {
99
- const testSuiteFullPath = this.pickle.uri
99
+ const testFileAbsolutePath = this.pickle.uri
100
100
 
101
- if (!pickleResultByFile[testSuiteFullPath]) { // first test in suite
101
+ if (!pickleResultByFile[testFileAbsolutePath]) { // first test in suite
102
102
  isUnskippable = isMarkedAsUnskippable(this.pickle)
103
- const testSuitePath = getTestSuitePath(testSuiteFullPath, process.cwd())
103
+ const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
104
104
  isForcedToRun = isUnskippable && skippableSuites.includes(testSuitePath)
105
105
 
106
106
  testSuiteStartCh.publish({ testSuitePath, isUnskippable, isForcedToRun, itrCorrelationId })
@@ -113,7 +113,7 @@ function wrapRun (pl, isLatestVersion) {
113
113
 
114
114
  testStartCh.publish({
115
115
  testName: this.pickle.name,
116
- fullTestSuite: testSuiteFullPath,
116
+ testFileAbsolutePath,
117
117
  testSourceLine
118
118
  })
119
119
  try {
@@ -123,21 +123,21 @@ function wrapRun (pl, isLatestVersion) {
123
123
  const { status, skipReason, errorMessage } = isLatestVersion
124
124
  ? getStatusFromResultLatest(result) : getStatusFromResult(result)
125
125
 
126
- if (!pickleResultByFile[testSuiteFullPath]) {
127
- pickleResultByFile[testSuiteFullPath] = [status]
126
+ if (!pickleResultByFile[testFileAbsolutePath]) {
127
+ pickleResultByFile[testFileAbsolutePath] = [status]
128
128
  } else {
129
- pickleResultByFile[testSuiteFullPath].push(status)
129
+ pickleResultByFile[testFileAbsolutePath].push(status)
130
130
  }
131
131
  testFinishCh.publish({ status, skipReason, errorMessage })
132
132
  // last test in suite
133
- if (pickleResultByFile[testSuiteFullPath].length === pickleByFile[testSuiteFullPath].length) {
134
- const testSuiteStatus = getSuiteStatusFromTestStatuses(pickleResultByFile[testSuiteFullPath])
133
+ if (pickleResultByFile[testFileAbsolutePath].length === pickleByFile[testFileAbsolutePath].length) {
134
+ const testSuiteStatus = getSuiteStatusFromTestStatuses(pickleResultByFile[testFileAbsolutePath])
135
135
  if (global.__coverage__) {
136
136
  const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
137
137
 
138
138
  testSuiteCodeCoverageCh.publish({
139
139
  coverageFiles,
140
- suiteFile: testSuiteFullPath
140
+ suiteFile: testFileAbsolutePath
141
141
  })
142
142
  // We need to reset coverage to get a code coverage per suite
143
143
  // Before that, we preserve the original coverage
@@ -272,7 +272,7 @@ addHook({
272
272
  })
273
273
 
274
274
  asyncResource.runInAsyncScope(() => {
275
- itrConfigurationCh.publish({ onDone })
275
+ libraryConfigurationCh.publish({ onDone })
276
276
  })
277
277
 
278
278
  await configPromise
@@ -19,11 +19,31 @@ function wrapHandle (handle) {
19
19
 
20
20
  const wrapRouterMethod = createWrapRouterMethod('express')
21
21
 
22
+ const responseJsonChannel = channel('datadog:express:response:json:start')
23
+
24
+ function wrapResponseJson (json) {
25
+ return function wrappedJson (obj) {
26
+ if (responseJsonChannel.hasSubscribers) {
27
+ // backward compat as express 4.x supports deprecated 3.x signature
28
+ if (arguments.length === 2 && typeof arguments[1] !== 'number') {
29
+ obj = arguments[1]
30
+ }
31
+
32
+ responseJsonChannel.publish({ req: this.req, body: obj })
33
+ }
34
+
35
+ return json.apply(this, arguments)
36
+ }
37
+ }
38
+
22
39
  addHook({ name: 'express', versions: ['>=4'] }, express => {
23
40
  shimmer.wrap(express.application, 'handle', wrapHandle)
24
41
  shimmer.wrap(express.Router, 'use', wrapRouterMethod)
25
42
  shimmer.wrap(express.Router, 'route', wrapRouterMethod)
26
43
 
44
+ shimmer.wrap(express.response, 'json', wrapResponseJson)
45
+ shimmer.wrap(express.response, 'jsonp', wrapResponseJson)
46
+
27
47
  return express
28
48
  })
29
49
 
@@ -15,54 +15,52 @@ const errorChannel = channel('apm:grpc:client:request:error')
15
15
  const finishChannel = channel('apm:grpc:client:request:finish')
16
16
  const emitChannel = channel('apm:grpc:client:request:emit')
17
17
 
18
- function createWrapMakeRequest (type) {
18
+ function createWrapMakeRequest (type, hasPeer = false) {
19
19
  return function wrapMakeRequest (makeRequest) {
20
20
  return function (path) {
21
21
  const args = ensureMetadata(this, arguments, 4)
22
22
 
23
- return callMethod(this, makeRequest, args, path, args[4], type)
23
+ return callMethod(this, makeRequest, args, path, args[4], type, hasPeer)
24
24
  }
25
25
  }
26
26
  }
27
27
 
28
- function createWrapLoadPackageDefinition () {
28
+ function createWrapLoadPackageDefinition (hasPeer = false) {
29
29
  return function wrapLoadPackageDefinition (loadPackageDefinition) {
30
30
  return function (packageDef) {
31
31
  const result = loadPackageDefinition.apply(this, arguments)
32
32
 
33
33
  if (!result) return result
34
34
 
35
- wrapPackageDefinition(result)
35
+ wrapPackageDefinition(result, hasPeer)
36
36
 
37
37
  return result
38
38
  }
39
39
  }
40
40
  }
41
41
 
42
- function createWrapMakeClientConstructor () {
42
+ function createWrapMakeClientConstructor (hasPeer = false) {
43
43
  return function wrapMakeClientConstructor (makeClientConstructor) {
44
44
  return function (methods) {
45
45
  const ServiceClient = makeClientConstructor.apply(this, arguments)
46
-
47
- wrapClientConstructor(ServiceClient, methods)
48
-
46
+ wrapClientConstructor(ServiceClient, methods, hasPeer)
49
47
  return ServiceClient
50
48
  }
51
49
  }
52
50
  }
53
51
 
54
- function wrapPackageDefinition (def) {
52
+ function wrapPackageDefinition (def, hasPeer = false) {
55
53
  for (const name in def) {
56
54
  if (def[name].format) continue
57
55
  if (def[name].service && def[name].prototype) {
58
- wrapClientConstructor(def[name], def[name].service)
56
+ wrapClientConstructor(def[name], def[name].service, hasPeer)
59
57
  } else {
60
- wrapPackageDefinition(def[name])
58
+ wrapPackageDefinition(def[name], hasPeer)
61
59
  }
62
60
  }
63
61
  }
64
62
 
65
- function wrapClientConstructor (ServiceClient, methods) {
63
+ function wrapClientConstructor (ServiceClient, methods, hasPeer = false) {
66
64
  const proto = ServiceClient.prototype
67
65
 
68
66
  if (typeof methods !== 'object' || 'format' in methods) return
@@ -76,24 +74,23 @@ function wrapClientConstructor (ServiceClient, methods) {
76
74
  const type = getType(methods[name])
77
75
 
78
76
  if (methods[name]) {
79
- proto[name] = wrapMethod(proto[name], path, type)
77
+ proto[name] = wrapMethod(proto[name], path, type, hasPeer)
80
78
  }
81
79
 
82
80
  if (originalName) {
83
- proto[originalName] = wrapMethod(proto[originalName], path, type)
81
+ proto[originalName] = wrapMethod(proto[originalName], path, type, hasPeer)
84
82
  }
85
83
  })
86
84
  }
87
85
 
88
- function wrapMethod (method, path, type) {
86
+ function wrapMethod (method, path, type, hasPeer) {
89
87
  if (typeof method !== 'function' || patched.has(method)) {
90
88
  return method
91
89
  }
92
90
 
93
91
  const wrapped = function () {
94
92
  const args = ensureMetadata(this, arguments, 1)
95
-
96
- return callMethod(this, method, args, path, args[1], type)
93
+ return callMethod(this, method, args, path, args[1], type, hasPeer)
97
94
  }
98
95
 
99
96
  Object.assign(wrapped, method)
@@ -117,7 +114,20 @@ function wrapCallback (ctx, callback = () => { }) {
117
114
  }
118
115
  }
119
116
 
120
- function createWrapEmit (ctx) {
117
+ function createWrapEmit (ctx, hasPeer = false) {
118
+ const onStatusWithPeer = function (ctx, arg1, thisArg) {
119
+ ctx.result = arg1
120
+ ctx.peer = thisArg.getPeer()
121
+ finishChannel.publish(ctx)
122
+ }
123
+
124
+ const onStatusWithoutPeer = function (ctx, arg1, thisArg) {
125
+ ctx.result = arg1
126
+ finishChannel.publish(ctx)
127
+ }
128
+
129
+ const onStatus = hasPeer ? onStatusWithPeer : onStatusWithoutPeer
130
+
121
131
  return function wrapEmit (emit) {
122
132
  return function (event, arg1) {
123
133
  switch (event) {
@@ -126,8 +136,7 @@ function createWrapEmit (ctx) {
126
136
  errorChannel.publish(ctx)
127
137
  break
128
138
  case 'status':
129
- ctx.result = arg1
130
- finishChannel.publish(ctx)
139
+ onStatus(ctx, arg1, this)
131
140
  break
132
141
  }
133
142
 
@@ -138,7 +147,7 @@ function createWrapEmit (ctx) {
138
147
  }
139
148
  }
140
149
 
141
- function callMethod (client, method, args, path, metadata, type) {
150
+ function callMethod (client, method, args, path, metadata, type, hasPeer = false) {
142
151
  if (!startChannel.hasSubscribers) return method.apply(client, args)
143
152
 
144
153
  const length = args.length
@@ -159,7 +168,7 @@ function callMethod (client, method, args, path, metadata, type) {
159
168
  const call = method.apply(client, args)
160
169
 
161
170
  if (call && typeof call.emit === 'function') {
162
- shimmer.wrap(call, 'emit', createWrapEmit(ctx))
171
+ shimmer.wrap(call, 'emit', createWrapEmit(ctx, hasPeer))
163
172
  }
164
173
 
165
174
  return call
@@ -223,34 +232,45 @@ function getGrpc (client) {
223
232
  } while ((proto = Object.getPrototypeOf(proto)))
224
233
  }
225
234
 
226
- function patch (grpc) {
227
- const proto = grpc.Client.prototype
235
+ function patch (hasPeer = false) {
236
+ return function patch (grpc) {
237
+ const proto = grpc.Client.prototype
228
238
 
229
- instances.set(proto, grpc)
239
+ instances.set(proto, grpc)
230
240
 
231
- shimmer.wrap(proto, 'makeBidiStreamRequest', createWrapMakeRequest(types.bidi))
232
- shimmer.wrap(proto, 'makeClientStreamRequest', createWrapMakeRequest(types.clientStream))
233
- shimmer.wrap(proto, 'makeServerStreamRequest', createWrapMakeRequest(types.serverStream))
234
- shimmer.wrap(proto, 'makeUnaryRequest', createWrapMakeRequest(types.unary))
241
+ shimmer.wrap(proto, 'makeBidiStreamRequest', createWrapMakeRequest(types.bidi, hasPeer))
242
+ shimmer.wrap(proto, 'makeClientStreamRequest', createWrapMakeRequest(types.clientStream, hasPeer))
243
+ shimmer.wrap(proto, 'makeServerStreamRequest', createWrapMakeRequest(types.serverStream, hasPeer))
244
+ shimmer.wrap(proto, 'makeUnaryRequest', createWrapMakeRequest(types.unary, hasPeer))
235
245
 
236
- return grpc
246
+ return grpc
247
+ }
237
248
  }
238
249
 
239
250
  if (nodeMajor <= 14) {
240
- addHook({ name: 'grpc', versions: ['>=1.24.3'] }, patch)
251
+ addHook({ name: 'grpc', versions: ['>=1.24.3'] }, patch(true))
241
252
 
242
253
  addHook({ name: 'grpc', versions: ['>=1.24.3'], file: 'src/client.js' }, client => {
243
- shimmer.wrap(client, 'makeClientConstructor', createWrapMakeClientConstructor())
254
+ shimmer.wrap(client, 'makeClientConstructor', createWrapMakeClientConstructor(true))
244
255
 
245
256
  return client
246
257
  })
247
258
  }
248
259
 
249
- addHook({ name: '@grpc/grpc-js', versions: ['>=1.0.3'] }, patch)
260
+ addHook({ name: '@grpc/grpc-js', versions: ['>=1.0.3 <1.1.4'] }, patch(false))
261
+
262
+ addHook({ name: '@grpc/grpc-js', versions: ['>=1.0.3 <1.1.4'], file: 'build/src/make-client.js' }, client => {
263
+ shimmer.wrap(client, 'makeClientConstructor', createWrapMakeClientConstructor(false))
264
+ shimmer.wrap(client, 'loadPackageDefinition', createWrapLoadPackageDefinition(false))
265
+
266
+ return client
267
+ })
268
+
269
+ addHook({ name: '@grpc/grpc-js', versions: ['>=1.1.4'] }, patch(true))
250
270
 
251
- addHook({ name: '@grpc/grpc-js', versions: ['>=1.0.3'], file: 'build/src/make-client.js' }, client => {
252
- shimmer.wrap(client, 'makeClientConstructor', createWrapMakeClientConstructor())
253
- shimmer.wrap(client, 'loadPackageDefinition', createWrapLoadPackageDefinition())
271
+ addHook({ name: '@grpc/grpc-js', versions: ['>=1.1.4'], file: 'build/src/make-client.js' }, client => {
272
+ shimmer.wrap(client, 'makeClientConstructor', createWrapMakeClientConstructor(true))
273
+ shimmer.wrap(client, 'loadPackageDefinition', createWrapLoadPackageDefinition(true))
254
274
 
255
275
  return client
256
276
  })
@@ -30,7 +30,7 @@ module.exports = {
30
30
  'body-parser': () => require('../body-parser'),
31
31
  'bunyan': () => require('../bunyan'),
32
32
  'cassandra-driver': () => require('../cassandra-driver'),
33
- 'child_process': () => require('../child-process'),
33
+ 'child_process': () => require('../child_process'),
34
34
  'connect': () => require('../connect'),
35
35
  'cookie': () => require('../cookie'),
36
36
  'cookie-parser': () => require('../cookie-parser'),
@@ -78,7 +78,7 @@ module.exports = {
78
78
  'mysql2': () => require('../mysql2'),
79
79
  'net': () => require('../net'),
80
80
  'next': () => require('../next'),
81
- 'node:child_process': () => require('../child-process'),
81
+ 'node:child_process': () => require('../child_process'),
82
82
  'node:crypto': () => require('../crypto'),
83
83
  'node:dns': () => require('../dns'),
84
84
  'node:http': () => require('../http'),