dd-trace 3.47.0 → 3.48.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 (68) 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 +6 -5
  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 +147 -10
  13. package/packages/datadog-instrumentations/src/mocha.js +3 -3
  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 +25 -12
  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 +47 -6
  27. package/packages/datadog-plugin-mocha/src/index.js +14 -5
  28. package/packages/datadog-plugin-playwright/src/index.js +19 -5
  29. package/packages/datadog-plugin-rhea/src/consumer.js +11 -1
  30. package/packages/datadog-plugin-rhea/src/producer.js +11 -0
  31. package/packages/dd-trace/src/appsec/addresses.js +2 -0
  32. package/packages/dd-trace/src/appsec/api_security_sampler.js +16 -3
  33. package/packages/dd-trace/src/appsec/channels.js +2 -1
  34. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
  35. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
  36. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
  37. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -1
  38. package/packages/dd-trace/src/appsec/index.js +17 -2
  39. package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
  40. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +83 -0
  41. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
  42. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
  43. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +83 -41
  44. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +30 -8
  45. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -1
  46. package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → requests/get-library-configuration.js} +18 -6
  47. package/packages/dd-trace/src/config.js +22 -9
  48. package/packages/dd-trace/src/datastreams/processor.js +6 -0
  49. package/packages/dd-trace/src/datastreams/writer.js +2 -5
  50. package/packages/dd-trace/src/dogstatsd.js +3 -5
  51. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +5 -3
  52. package/packages/dd-trace/src/exporters/common/request.js +21 -3
  53. package/packages/dd-trace/src/format.js +25 -1
  54. package/packages/dd-trace/src/noop/span.js +1 -0
  55. package/packages/dd-trace/src/opentelemetry/span.js +9 -2
  56. package/packages/dd-trace/src/opentracing/span.js +38 -0
  57. package/packages/dd-trace/src/opentracing/span_context.js +12 -6
  58. package/packages/dd-trace/src/opentracing/tracer.js +2 -1
  59. package/packages/dd-trace/src/plugins/ci_plugin.js +24 -8
  60. package/packages/dd-trace/src/plugins/index.js +1 -0
  61. package/packages/dd-trace/src/plugins/util/git.js +6 -0
  62. package/packages/dd-trace/src/plugins/util/test.js +36 -7
  63. package/packages/dd-trace/src/profiling/config.js +22 -22
  64. package/packages/dd-trace/src/proxy.js +31 -23
  65. package/packages/dd-trace/src/span_processor.js +5 -1
  66. package/packages/dd-trace/src/telemetry/index.js +3 -0
  67. package/packages/datadog-instrumentations/src/child-process.js +0 -29
  68. package/packages/dd-trace/src/plugins/util/exec.js +0 -34
@@ -0,0 +1,91 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+ const scrubChildProcessCmd = require('./scrub-cmd-params')
5
+
6
+ const MAX_ARG_SIZE = 4096 // 4kB
7
+
8
+ function truncateCommand (cmdFields) {
9
+ let size = cmdFields[0].length
10
+ let truncated = false
11
+ for (let i = 1; i < cmdFields.length; i++) {
12
+ if (size >= MAX_ARG_SIZE) {
13
+ truncated = true
14
+ cmdFields[i] = ''
15
+ continue
16
+ }
17
+
18
+ const argLen = cmdFields[i].length
19
+ if (size < MAX_ARG_SIZE && size + argLen > MAX_ARG_SIZE) {
20
+ cmdFields[i] = cmdFields[i].substring(0, 2)
21
+ truncated = true
22
+ }
23
+
24
+ size += argLen
25
+ }
26
+
27
+ return truncated
28
+ }
29
+
30
+ class ChildProcessPlugin extends TracingPlugin {
31
+ static get id () { return 'child_process' }
32
+ static get prefix () { return 'tracing:datadog:child_process:execution' }
33
+
34
+ get tracer () {
35
+ return this._tracer
36
+ }
37
+
38
+ start ({ command, shell }) {
39
+ if (typeof command !== 'string') {
40
+ return
41
+ }
42
+
43
+ const cmdFields = scrubChildProcessCmd(command)
44
+ const truncated = truncateCommand(cmdFields)
45
+ const property = (shell === true) ? 'cmd.shell' : 'cmd.exec'
46
+
47
+ const meta = {
48
+ 'component': 'subprocess',
49
+ [property]: (shell === true) ? cmdFields.join(' ') : JSON.stringify(cmdFields)
50
+ }
51
+
52
+ if (truncated) {
53
+ meta['cmd.truncated'] = `${truncated}`
54
+ }
55
+
56
+ this.startSpan('command_execution', {
57
+ service: this.config.service,
58
+ resource: (shell === true) ? 'sh' : cmdFields[0],
59
+ type: 'system',
60
+ meta
61
+ })
62
+ }
63
+
64
+ end ({ result, error }) {
65
+ let exitCode
66
+
67
+ if (result !== undefined) {
68
+ exitCode = result?.status || 0
69
+ } else if (error !== undefined) {
70
+ exitCode = error?.status || error?.code || 0
71
+ } else {
72
+ // TracingChannels call start, end synchronously. Later when the promise is resolved then asyncStart asyncEnd.
73
+ // Therefore in the case of calling end with neither result nor error means that they will come in the asyncEnd.
74
+ return
75
+ }
76
+
77
+ this.activeSpan?.setTag('cmd.exit_code', `${exitCode}`)
78
+ this.activeSpan?.finish()
79
+ }
80
+
81
+ error (error) {
82
+ this.addError(error)
83
+ }
84
+
85
+ asyncEnd ({ result }) {
86
+ this.activeSpan?.setTag('cmd.exit_code', `${result}`)
87
+ this.activeSpan?.finish()
88
+ }
89
+ }
90
+
91
+ module.exports = ChildProcessPlugin
@@ -0,0 +1,125 @@
1
+ 'use strict'
2
+
3
+ const shellParser = require('shell-quote/parse')
4
+
5
+ const ALLOWED_ENV_VARIABLES = ['LD_PRELOAD', 'LD_LIBRARY_PATH', 'PATH']
6
+ const PROCESS_DENYLIST = ['md5']
7
+
8
+ const VARNAMES_REGEX = /\$([\w\d_]*)(?:[^\w\d_]|$)/gmi
9
+ // eslint-disable-next-line max-len
10
+ const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|api_?key|secret|a(?:ccess|uth)_token|mysql_pwd|credentials|(?:stripe)?token)$'
11
+ const regexParam = new RegExp(PARAM_PATTERN, 'i')
12
+ const ENV_PATTERN = '^(\\w+=\\w+;)*\\w+=\\w+;?$'
13
+ const envvarRegex = new RegExp(ENV_PATTERN)
14
+ const REDACTED = '?'
15
+
16
+ function extractVarNames (expression) {
17
+ const varNames = new Set()
18
+ let match
19
+
20
+ while ((match = VARNAMES_REGEX.exec(expression))) {
21
+ varNames.add(match[1])
22
+ }
23
+
24
+ const varNamesObject = {}
25
+ for (const varName of varNames.keys()) {
26
+ varNamesObject[varName] = `$${varName}`
27
+ }
28
+ return varNamesObject
29
+ }
30
+
31
+ function getTokensByExpression (expressionTokens) {
32
+ const expressionListTokens = []
33
+ let wipExpressionTokens = []
34
+ let isNewExpression = true
35
+
36
+ expressionTokens.forEach(token => {
37
+ if (isNewExpression) {
38
+ expressionListTokens.push(wipExpressionTokens)
39
+ isNewExpression = false
40
+ }
41
+
42
+ wipExpressionTokens.push(token)
43
+
44
+ if (token.op) {
45
+ wipExpressionTokens = []
46
+ isNewExpression = true
47
+ }
48
+ })
49
+ return expressionListTokens
50
+ }
51
+
52
+ function scrubChildProcessCmd (expression) {
53
+ const varNames = extractVarNames(expression)
54
+ const expressionTokens = shellParser(expression, varNames)
55
+
56
+ const expressionListTokens = getTokensByExpression(expressionTokens)
57
+
58
+ const result = []
59
+ expressionListTokens.forEach((expressionTokens) => {
60
+ let foundBinary = false
61
+ for (let index = 0; index < expressionTokens.length; index++) {
62
+ const token = expressionTokens[index]
63
+
64
+ if (typeof token === 'object') {
65
+ if (token.pattern) {
66
+ result.push(token.pattern)
67
+ } else if (token.op) {
68
+ result.push(token.op)
69
+ } else if (token.comment) {
70
+ result.push(`#${token.comment}`)
71
+ }
72
+ } else if (!foundBinary) {
73
+ if (envvarRegex.test(token)) {
74
+ const envSplit = token.split('=')
75
+
76
+ if (!ALLOWED_ENV_VARIABLES.includes(envSplit[0])) {
77
+ envSplit[1] = REDACTED
78
+
79
+ const newToken = envSplit.join('=')
80
+ expressionTokens[index] = newToken
81
+
82
+ result.push(newToken)
83
+ } else {
84
+ result.push(token)
85
+ }
86
+ } else {
87
+ foundBinary = true
88
+ result.push(token)
89
+
90
+ if (PROCESS_DENYLIST.includes(token)) {
91
+ for (index++; index < expressionTokens.length; index++) {
92
+ const token = expressionTokens[index]
93
+
94
+ if (token.op) {
95
+ result.push(token.op)
96
+ } else {
97
+ expressionTokens[index] = REDACTED
98
+ result.push(REDACTED)
99
+ }
100
+ }
101
+ break
102
+ }
103
+ }
104
+ } else {
105
+ const paramKeyValue = token.split('=')
106
+ const paramKey = paramKeyValue[0]
107
+
108
+ if (regexParam.test(paramKey)) {
109
+ if (paramKeyValue.length === 1) {
110
+ expressionTokens[index + 1] = REDACTED
111
+ result.push(token)
112
+ } else {
113
+ result.push(`${paramKey}=${REDACTED}`)
114
+ }
115
+ } else {
116
+ result.push(token)
117
+ }
118
+ }
119
+ }
120
+ })
121
+
122
+ return result
123
+ }
124
+
125
+ module.exports = scrubChildProcessCmd
@@ -14,7 +14,8 @@ const {
14
14
  TEST_ITR_UNSKIPPABLE,
15
15
  TEST_ITR_FORCED_RUN,
16
16
  TEST_CODE_OWNERS,
17
- ITR_CORRELATION_ID
17
+ ITR_CORRELATION_ID,
18
+ TEST_SOURCE_FILE
18
19
  } = require('../../dd-trace/src/plugins/util/test')
19
20
  const { RESOURCE_NAME } = require('../../../ext/tags')
20
21
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -47,7 +48,7 @@ class CucumberPlugin extends CiPlugin {
47
48
  hasUnskippableSuites,
48
49
  hasForcedToRunSuites
49
50
  }) => {
50
- const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
51
+ const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
51
52
  addIntelligentTestRunnerSpanTags(
52
53
  this.testSessionSpan,
53
54
  this.testModuleSpan,
@@ -71,7 +72,7 @@ class CucumberPlugin extends CiPlugin {
71
72
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
72
73
  finishAllTraceSpans(this.testSessionSpan)
73
74
 
74
- this.itrConfig = null
75
+ this.libraryConfig = null
75
76
  this.tracer._exporter.flush()
76
77
  })
77
78
 
@@ -102,7 +103,7 @@ class CucumberPlugin extends CiPlugin {
102
103
  }
103
104
  })
104
105
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
105
- if (this.itrConfig?.isCodeCoverageEnabled) {
106
+ if (this.libraryConfig?.isCodeCoverageEnabled) {
106
107
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
107
108
  }
108
109
  })
@@ -114,7 +115,7 @@ class CucumberPlugin extends CiPlugin {
114
115
  })
115
116
 
116
117
  this.addSub('ci:cucumber:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
117
- if (!this.itrConfig?.isCodeCoverageEnabled) {
118
+ if (!this.libraryConfig?.isCodeCoverageEnabled) {
118
119
  return
119
120
  }
120
121
  if (!coverageFiles.length) {
@@ -122,7 +123,7 @@ class CucumberPlugin extends CiPlugin {
122
123
  }
123
124
 
124
125
  const relativeCoverageFiles = [...coverageFiles, suiteFile]
125
- .map(filename => getTestSuitePath(filename, this.sourceRoot))
126
+ .map(filename => getTestSuitePath(filename, this.repositoryRoot))
126
127
 
127
128
  this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
128
129
 
@@ -136,10 +137,11 @@ class CucumberPlugin extends CiPlugin {
136
137
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
137
138
  })
138
139
 
139
- this.addSub('ci:cucumber:test:start', ({ testName, fullTestSuite, testSourceLine }) => {
140
+ this.addSub('ci:cucumber:test:start', ({ testName, testFileAbsolutePath, testSourceLine }) => {
140
141
  const store = storage.getStore()
141
- const testSuite = getTestSuitePath(fullTestSuite, this.sourceRoot)
142
- const testSpan = this.startTestSpan(testName, testSuite, testSourceLine)
142
+ const testSuite = getTestSuitePath(testFileAbsolutePath, this.sourceRoot)
143
+ const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
144
+ const testSpan = this.startTestSpan(testName, testSuite, testSourceFile, testSourceLine)
143
145
 
144
146
  this.enter(testSpan, store)
145
147
  })
@@ -191,12 +193,15 @@ class CucumberPlugin extends CiPlugin {
191
193
  })
192
194
  }
193
195
 
194
- startTestSpan (testName, testSuite, testSourceLine) {
196
+ startTestSpan (testName, testSuite, testSourceFile, testSourceLine) {
195
197
  return super.startTestSpan(
196
198
  testName,
197
199
  testSuite,
198
200
  this.testSuiteSpan,
199
- { [TEST_SOURCE_START]: testSourceLine }
201
+ {
202
+ [TEST_SOURCE_START]: testSourceLine,
203
+ [TEST_SOURCE_FILE]: testSourceFile
204
+ }
200
205
  )
201
206
  }
202
207
  }
@@ -24,7 +24,8 @@ const {
24
24
  TEST_SKIPPED_BY_ITR,
25
25
  TEST_ITR_UNSKIPPABLE,
26
26
  TEST_ITR_FORCED_RUN,
27
- ITR_CORRELATION_ID
27
+ ITR_CORRELATION_ID,
28
+ TEST_SOURCE_FILE
28
29
  } = require('../../dd-trace/src/plugins/util/test')
29
30
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
30
31
  const log = require('../../dd-trace/src/log')
@@ -45,7 +46,8 @@ const {
45
46
  GIT_REPOSITORY_URL,
46
47
  GIT_COMMIT_SHA,
47
48
  GIT_BRANCH,
48
- CI_PROVIDER_NAME
49
+ CI_PROVIDER_NAME,
50
+ CI_WORKSPACE_PATH
49
51
  } = require('../../dd-trace/src/plugins/util/tags')
50
52
  const {
51
53
  OS_VERSION,
@@ -119,14 +121,14 @@ function getSuiteStatus (suiteStats) {
119
121
  return 'pass'
120
122
  }
121
123
 
122
- function getItrConfig (tracer, testConfiguration) {
124
+ function getLibraryConfiguration (tracer, testConfiguration) {
123
125
  return new Promise(resolve => {
124
- if (!tracer._tracer._exporter || !tracer._tracer._exporter.getItrConfiguration) {
126
+ if (!tracer._tracer._exporter?.getLibraryConfiguration) {
125
127
  return resolve({ err: new Error('CI Visibility was not initialized correctly') })
126
128
  }
127
129
 
128
- tracer._tracer._exporter.getItrConfiguration(testConfiguration, (err, itrConfig) => {
129
- resolve({ err, itrConfig })
130
+ tracer._tracer._exporter.getLibraryConfiguration(testConfiguration, (err, libraryConfig) => {
131
+ resolve({ err, libraryConfig })
130
132
  })
131
133
  })
132
134
  }
@@ -136,7 +138,7 @@ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration)
136
138
  return Promise.resolve({ skippableTests: [] })
137
139
  }
138
140
  return new Promise(resolve => {
139
- if (!tracer._tracer._exporter || !tracer._tracer._exporter.getItrConfiguration) {
141
+ if (!tracer._tracer._exporter?.getLibraryConfiguration) {
140
142
  return resolve({ err: new Error('CI Visibility was not initialized correctly') })
141
143
  }
142
144
  tracer._tracer._exporter.getSkippableSuites(testConfiguration, (err, skippableTests, correlationId) => {
@@ -186,7 +188,8 @@ module.exports = (on, config) => {
186
188
  [RUNTIME_NAME]: runtimeName,
187
189
  [RUNTIME_VERSION]: runtimeVersion,
188
190
  [GIT_BRANCH]: branch,
189
- [CI_PROVIDER_NAME]: ciProviderName
191
+ [CI_PROVIDER_NAME]: ciProviderName,
192
+ [CI_WORKSPACE_PATH]: repositoryRoot
190
193
  } = testEnvironmentMetadata
191
194
 
192
195
  const isUnsupportedCIProvider = !ciProviderName
@@ -205,7 +208,7 @@ module.exports = (on, config) => {
205
208
  testLevel: 'test'
206
209
  }
207
210
 
208
- const codeOwnersEntries = getCodeOwnersFileEntries()
211
+ const codeOwnersEntries = getCodeOwnersFileEntries(repositoryRoot)
209
212
 
210
213
  let activeSpan = null
211
214
  let testSessionSpan = null
@@ -284,12 +287,12 @@ module.exports = (on, config) => {
284
287
  }
285
288
 
286
289
  on('before:run', (details) => {
287
- return getItrConfig(tracer, testConfiguration).then(({ err, itrConfig }) => {
290
+ return getLibraryConfiguration(tracer, testConfiguration).then(({ err, libraryConfig }) => {
288
291
  if (err) {
289
292
  log.error(err)
290
293
  } else {
291
- isSuitesSkippingEnabled = itrConfig.isSuitesSkippingEnabled
292
- isCodeCoverageEnabled = itrConfig.isCodeCoverageEnabled
294
+ isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
295
+ isCodeCoverageEnabled = libraryConfig.isCodeCoverageEnabled
293
296
  }
294
297
 
295
298
  return getSkippableTests(isSuitesSkippingEnabled, tracer, testConfiguration)
@@ -359,6 +362,11 @@ module.exports = (on, config) => {
359
362
  cypressTestName === test.name && spec.relative === test.suite
360
363
  )
361
364
  const skippedTestSpan = getTestSpan(cypressTestName, spec.relative)
365
+ if (spec.absolute && repositoryRoot) {
366
+ skippedTestSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, repositoryRoot))
367
+ } else {
368
+ skippedTestSpan.setTag(TEST_SOURCE_FILE, spec.relative)
369
+ }
362
370
  skippedTestSpan.setTag(TEST_STATUS, 'skip')
363
371
  if (isSkippedByItr) {
364
372
  skippedTestSpan.setTag(TEST_SKIPPED_BY_ITR, 'true')
@@ -390,6 +398,11 @@ module.exports = (on, config) => {
390
398
  if (itrCorrelationId) {
391
399
  finishedTest.testSpan.setTag(ITR_CORRELATION_ID, itrCorrelationId)
392
400
  }
401
+ if (spec.absolute && repositoryRoot) {
402
+ finishedTest.testSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, repositoryRoot))
403
+ } else {
404
+ finishedTest.testSpan.setTag(TEST_SOURCE_FILE, spec.relative)
405
+ }
393
406
  finishedTest.testSpan.finish(finishedTest.finishTime)
394
407
  })
395
408
 
@@ -41,7 +41,6 @@ class GrpcClientPlugin extends ClientPlugin {
41
41
  'grpc.status.code': 0
42
42
  }
43
43
  }, false)
44
-
45
44
  // needed as precursor for peer.service
46
45
  if (method.service && method.package) {
47
46
  span.setTag('rpc.service', method.package + '.' + method.service)
@@ -68,7 +67,7 @@ class GrpcClientPlugin extends ClientPlugin {
68
67
  this.addError(error, span)
69
68
  }
70
69
 
71
- finish ({ span, result }) {
70
+ finish ({ span, result, peer }) {
72
71
  if (!span) return
73
72
 
74
73
  const { code, metadata } = result || {}
@@ -80,6 +79,21 @@ class GrpcClientPlugin extends ClientPlugin {
80
79
  addMetadataTags(span, metadata, metadataFilter, 'response')
81
80
  }
82
81
 
82
+ if (peer) {
83
+ // The only scheme we want to support here is ipv[46]:port, although
84
+ // more are supported by the library
85
+ // https://github.com/grpc/grpc/blob/v1.60.0/doc/naming.md
86
+ const parts = peer.split(':')
87
+ if (parts[parts.length - 1].match(/^\d+/)) {
88
+ const port = parts[parts.length - 1]
89
+ const ip = parts.slice(0, -1).join(':')
90
+ span.setTag('network.destination.ip', ip)
91
+ span.setTag('network.destination.port', port)
92
+ } else {
93
+ span.setTag('network.destination.ip', peer)
94
+ }
95
+ }
96
+
83
97
  this.tagPeerService(span)
84
98
  span.finish()
85
99
  }
@@ -122,7 +122,7 @@ class HttpClientPlugin extends ClientPlugin {
122
122
  // conditions for no error:
123
123
  // 1. not using a custom agent instance with custom timeout specified
124
124
  // 2. no invocation of `req.setTimeout`
125
- if (!args.options.agent?.options.timeout && !customRequestTimeout) return
125
+ if (!args.options.agent?.options?.timeout && !customRequestTimeout) return
126
126
 
127
127
  span.setTag('error', 1)
128
128
  }
@@ -14,7 +14,12 @@ const {
14
14
  TEST_ITR_UNSKIPPABLE,
15
15
  TEST_ITR_FORCED_RUN,
16
16
  TEST_CODE_OWNERS,
17
- ITR_CORRELATION_ID
17
+ ITR_CORRELATION_ID,
18
+ TEST_SOURCE_FILE,
19
+ getTestSuitePath,
20
+ TEST_IS_NEW,
21
+ TEST_EARLY_FLAKE_IS_RETRY,
22
+ TEST_EARLY_FLAKE_IS_ENABLED
18
23
  } = require('../../dd-trace/src/plugins/util/test')
19
24
  const { COMPONENT } = require('../../dd-trace/src/constants')
20
25
  const id = require('../../dd-trace/src/id')
@@ -81,7 +86,9 @@ class JestPlugin extends CiPlugin {
81
86
  numSkippedSuites,
82
87
  hasUnskippableSuites,
83
88
  hasForcedToRunSuites,
84
- error
89
+ error,
90
+ isEarlyFlakeDetectionEnabled,
91
+ onDone
85
92
  }) => {
86
93
  this.testSessionSpan.setTag(TEST_STATUS, status)
87
94
  this.testModuleSpan.setTag(TEST_STATUS, status)
@@ -106,23 +113,34 @@ class JestPlugin extends CiPlugin {
106
113
  }
107
114
  )
108
115
 
116
+ if (isEarlyFlakeDetectionEnabled) {
117
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
118
+ }
119
+
109
120
  this.testModuleSpan.finish()
110
121
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
111
122
  this.testSessionSpan.finish()
112
123
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
113
124
  finishAllTraceSpans(this.testSessionSpan)
114
- this.tracer._exporter.flush()
125
+
126
+ this.tracer._exporter.flush(() => {
127
+ if (onDone) {
128
+ onDone()
129
+ }
130
+ })
115
131
  })
116
132
 
117
133
  // Test suites can be run in a different process from jest's main one.
118
134
  // This subscriber changes the configuration objects from jest to inject the trace id
119
- // of the test session to the processes that run the test suites.
135
+ // of the test session to the processes that run the test suites, and other data.
120
136
  this.addSub('ci:jest:session:configuration', configs => {
121
137
  configs.forEach(config => {
122
138
  config._ddTestSessionId = this.testSessionSpan.context().toTraceId()
123
139
  config._ddTestModuleId = this.testModuleSpan.context().toSpanId()
124
140
  config._ddTestCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
125
141
  config._ddItrCorrelationId = this.itrCorrelationId
142
+ config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
143
+ config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
126
144
  })
127
145
  })
128
146
 
@@ -223,7 +241,7 @@ class JestPlugin extends CiPlugin {
223
241
  })
224
242
 
225
243
  /**
226
- * This can't use `this.itrConfig` like `ci:mocha:test-suite:code-coverage`
244
+ * This can't use `this.libraryConfig` like `ci:mocha:test-suite:code-coverage`
227
245
  * because this subscription happens in a different process from the one
228
246
  * fetching the ITR config.
229
247
  */
@@ -286,7 +304,17 @@ class JestPlugin extends CiPlugin {
286
304
  }
287
305
 
288
306
  startTestSpan (test) {
289
- const { suite, name, runner, testParameters, frameworkVersion, testStartLine } = test
307
+ const {
308
+ suite,
309
+ name,
310
+ runner,
311
+ testParameters,
312
+ frameworkVersion,
313
+ testStartLine,
314
+ testFileAbsolutePath,
315
+ isNew,
316
+ isEfdRetry
317
+ } = test
290
318
 
291
319
  const extraTags = {
292
320
  [JEST_TEST_RUNNER]: runner,
@@ -296,6 +324,19 @@ class JestPlugin extends CiPlugin {
296
324
  if (testStartLine) {
297
325
  extraTags[TEST_SOURCE_START] = testStartLine
298
326
  }
327
+ if (testFileAbsolutePath) {
328
+ extraTags[TEST_SOURCE_FILE] = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
329
+ } else {
330
+ // If for whatever we don't have the full path, we'll set the source file to the suite name
331
+ extraTags[TEST_SOURCE_FILE] = suite
332
+ }
333
+
334
+ if (isNew) {
335
+ extraTags[TEST_IS_NEW] = 'true'
336
+ if (isEfdRetry) {
337
+ extraTags[TEST_EARLY_FLAKE_IS_RETRY] = 'true'
338
+ }
339
+ }
299
340
 
300
341
  return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags)
301
342
  }
@@ -15,7 +15,8 @@ const {
15
15
  TEST_ITR_UNSKIPPABLE,
16
16
  TEST_ITR_FORCED_RUN,
17
17
  TEST_CODE_OWNERS,
18
- ITR_CORRELATION_ID
18
+ ITR_CORRELATION_ID,
19
+ TEST_SOURCE_FILE
19
20
  } = require('../../dd-trace/src/plugins/util/test')
20
21
  const { COMPONENT } = require('../../dd-trace/src/constants')
21
22
  const {
@@ -42,7 +43,7 @@ class MochaPlugin extends CiPlugin {
42
43
  this.sourceRoot = process.cwd()
43
44
 
44
45
  this.addSub('ci:mocha:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
45
- if (!this.itrConfig || !this.itrConfig.isCodeCoverageEnabled) {
46
+ if (!this.libraryConfig?.isCodeCoverageEnabled) {
46
47
  return
47
48
  }
48
49
  const testSuiteSpan = this._testSuites.get(suiteFile)
@@ -98,7 +99,7 @@ class MochaPlugin extends CiPlugin {
98
99
  }
99
100
  })
100
101
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
101
- if (this.itrConfig?.isCodeCoverageEnabled) {
102
+ if (this.libraryConfig?.isCodeCoverageEnabled) {
102
103
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
103
104
  }
104
105
  if (itrCorrelationId) {
@@ -192,7 +193,7 @@ class MochaPlugin extends CiPlugin {
192
193
  error
193
194
  }) => {
194
195
  if (this.testSessionSpan) {
195
- const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
196
+ const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
196
197
  this.testSessionSpan.setTag(TEST_STATUS, status)
197
198
  this.testModuleSpan.setTag(TEST_STATUS, status)
198
199
 
@@ -222,7 +223,7 @@ class MochaPlugin extends CiPlugin {
222
223
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
223
224
  finishAllTraceSpans(this.testSessionSpan)
224
225
  }
225
- this.itrConfig = null
226
+ this.libraryConfig = null
226
227
  this.tracer._exporter.flush()
227
228
  })
228
229
  }
@@ -244,6 +245,14 @@ class MochaPlugin extends CiPlugin {
244
245
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
245
246
  const testSuiteSpan = this._testSuites.get(testSuiteAbsolutePath)
246
247
 
248
+ const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
249
+
250
+ if (testSourceFile) {
251
+ extraTags[TEST_SOURCE_FILE] = testSourceFile
252
+ } else {
253
+ extraTags[TEST_SOURCE_FILE] = testSuite
254
+ }
255
+
247
256
  return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
248
257
  }
249
258
  }
@@ -9,7 +9,9 @@ const {
9
9
  getTestSuitePath,
10
10
  getTestSuiteCommonTags,
11
11
  TEST_SOURCE_START,
12
- TEST_CODE_OWNERS
12
+ TEST_CODE_OWNERS,
13
+ TEST_SOURCE_FILE,
14
+ TEST_CONFIGURATION_BROWSER_NAME
13
15
  } = require('../../dd-trace/src/plugins/util/test')
14
16
  const { RESOURCE_NAME } = require('../../../ext/tags')
15
17
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -76,10 +78,11 @@ class PlaywrightPlugin extends CiPlugin {
76
78
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
77
79
  })
78
80
 
79
- this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine }) => {
81
+ this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine, browserName }) => {
80
82
  const store = storage.getStore()
81
83
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
82
- const span = this.startTestSpan(testName, testSuite, testSourceLine)
84
+ const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
85
+ const span = this.startTestSpan(testName, testSuite, testSourceFile, testSourceLine, browserName)
83
86
 
84
87
  this.enter(span, store)
85
88
  })
@@ -126,9 +129,20 @@ class PlaywrightPlugin extends CiPlugin {
126
129
  })
127
130
  }
128
131
 
129
- startTestSpan (testName, testSuite, testSourceLine) {
132
+ startTestSpan (testName, testSuite, testSourceFile, testSourceLine, browserName) {
130
133
  const testSuiteSpan = this._testSuites.get(testSuite)
131
- return super.startTestSpan(testName, testSuite, testSuiteSpan, { [TEST_SOURCE_START]: testSourceLine })
134
+
135
+ const extraTags = {
136
+ [TEST_SOURCE_START]: testSourceLine
137
+ }
138
+ if (testSourceFile) {
139
+ extraTags[TEST_SOURCE_FILE] = testSourceFile || testSuite
140
+ }
141
+ if (browserName) {
142
+ extraTags[TEST_CONFIGURATION_BROWSER_NAME] = browserName
143
+ }
144
+
145
+ return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
132
146
  }
133
147
  }
134
148