dd-trace 4.38.1 → 4.40.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 (73) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/README.md +8 -18
  3. package/ci/init.js +7 -0
  4. package/ext/exporters.d.ts +1 -0
  5. package/ext/exporters.js +2 -1
  6. package/ext/tags.d.ts +1 -0
  7. package/ext/tags.js +1 -0
  8. package/index.d.ts +18 -3
  9. package/initialize.mjs +52 -0
  10. package/package.json +9 -12
  11. package/packages/datadog-instrumentations/src/amqplib.js +5 -2
  12. package/packages/datadog-instrumentations/src/apollo-server-core.js +0 -1
  13. package/packages/datadog-instrumentations/src/apollo-server.js +0 -1
  14. package/packages/datadog-instrumentations/src/body-parser.js +0 -1
  15. package/packages/datadog-instrumentations/src/check_require_cache.js +67 -5
  16. package/packages/datadog-instrumentations/src/cookie-parser.js +0 -1
  17. package/packages/datadog-instrumentations/src/express.js +0 -1
  18. package/packages/datadog-instrumentations/src/graphql.js +0 -2
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/register.js +5 -2
  21. package/packages/datadog-instrumentations/src/http/server.js +0 -1
  22. package/packages/datadog-instrumentations/src/jest.js +6 -3
  23. package/packages/datadog-instrumentations/src/mocha/common.js +48 -0
  24. package/packages/datadog-instrumentations/src/mocha/main.js +487 -0
  25. package/packages/datadog-instrumentations/src/mocha/utils.js +306 -0
  26. package/packages/datadog-instrumentations/src/mocha/worker.js +51 -0
  27. package/packages/datadog-instrumentations/src/mocha.js +4 -673
  28. package/packages/datadog-instrumentations/src/openai.js +188 -17
  29. package/packages/datadog-instrumentations/src/playwright.js +4 -3
  30. package/packages/datadog-instrumentations/src/router.js +1 -1
  31. package/packages/datadog-instrumentations/src/selenium.js +13 -6
  32. package/packages/datadog-plugin-graphql/src/resolve.js +4 -0
  33. package/packages/datadog-plugin-mocha/src/index.js +82 -8
  34. package/packages/datadog-plugin-next/src/index.js +1 -2
  35. package/packages/datadog-plugin-openai/src/index.js +219 -73
  36. package/packages/dd-trace/src/appsec/addresses.js +4 -2
  37. package/packages/dd-trace/src/appsec/blocking.js +19 -25
  38. package/packages/dd-trace/src/appsec/channels.js +2 -1
  39. package/packages/dd-trace/src/appsec/graphql.js +10 -3
  40. package/packages/dd-trace/src/appsec/index.js +11 -4
  41. package/packages/dd-trace/src/appsec/rasp.js +35 -0
  42. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  43. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
  44. package/packages/dd-trace/src/appsec/rule_manager.js +15 -25
  45. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -5
  46. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +3 -1
  47. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -1
  48. package/packages/dd-trace/src/config.js +97 -22
  49. package/packages/dd-trace/src/constants.js +2 -0
  50. package/packages/dd-trace/src/encode/0.4.js +47 -8
  51. package/packages/dd-trace/src/exporter.js +1 -0
  52. package/packages/dd-trace/src/flare/file.js +44 -0
  53. package/packages/dd-trace/src/flare/index.js +98 -0
  54. package/packages/dd-trace/src/log/channels.js +54 -29
  55. package/packages/dd-trace/src/log/writer.js +7 -49
  56. package/packages/dd-trace/src/opentelemetry/span.js +8 -0
  57. package/packages/dd-trace/src/opentracing/propagation/text_map.js +57 -12
  58. package/packages/dd-trace/src/plugins/index.js +1 -0
  59. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  60. package/packages/dd-trace/src/plugins/util/test.js +6 -0
  61. package/packages/dd-trace/src/priority_sampler.js +8 -4
  62. package/packages/dd-trace/src/profiler.js +2 -1
  63. package/packages/dd-trace/src/profiling/config.js +1 -0
  64. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  65. package/packages/dd-trace/src/profiling/{ssi-telemetry.js → ssi-heuristics.js} +64 -36
  66. package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +4 -9
  67. package/packages/dd-trace/src/proxy.js +49 -15
  68. package/packages/dd-trace/src/ritm.js +13 -1
  69. package/packages/dd-trace/src/sampling_rule.js +2 -1
  70. package/packages/dd-trace/src/startup-log.js +19 -15
  71. package/packages/dd-trace/src/telemetry/index.js +6 -2
  72. package/packages/dd-trace/src/tracer.js +3 -0
  73. package/packages/dd-trace/src/plugins/util/ip_blocklist.js +0 -51
@@ -15,13 +15,15 @@ const V4_PACKAGE_SHIMS = [
15
15
  file: 'resources/chat/completions.js',
16
16
  targetClass: 'Completions',
17
17
  baseResource: 'chat.completions',
18
- methods: ['create']
18
+ methods: ['create'],
19
+ streamedResponse: true
19
20
  },
20
21
  {
21
22
  file: 'resources/completions.js',
22
23
  targetClass: 'Completions',
23
24
  baseResource: 'completions',
24
- methods: ['create']
25
+ methods: ['create'],
26
+ streamedResponse: true
25
27
  },
26
28
  {
27
29
  file: 'resources/embeddings.js',
@@ -121,7 +123,7 @@ addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0 <4'] }, expor
121
123
 
122
124
  return fn.apply(this, arguments)
123
125
  .then((response) => {
124
- finishCh.publish({
126
+ finish({
125
127
  headers: response.headers,
126
128
  body: response.data,
127
129
  path: response.request.path,
@@ -130,10 +132,10 @@ addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0 <4'] }, expor
130
132
 
131
133
  return response
132
134
  })
133
- .catch((err) => {
134
- errorCh.publish({ err })
135
+ .catch(error => {
136
+ finish(undefined, error)
135
137
 
136
- throw err
138
+ throw error
137
139
  })
138
140
  })
139
141
  }
@@ -141,9 +143,140 @@ addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0 <4'] }, expor
141
143
  return exports
142
144
  })
143
145
 
146
+ function addStreamedChunk (content, chunk) {
147
+ content.usage = chunk.usage // add usage if it was specified to be returned
148
+ for (const choice of chunk.choices) {
149
+ const choiceIdx = choice.index
150
+ const oldChoice = content.choices.find(choice => choice?.index === choiceIdx)
151
+ if (!oldChoice) {
152
+ // we don't know which choices arrive in which order
153
+ content.choices[choiceIdx] = choice
154
+ } else {
155
+ if (!oldChoice.finish_reason) {
156
+ oldChoice.finish_reason = choice.finish_reason
157
+ }
158
+
159
+ // delta exists on chat completions
160
+ const delta = choice.delta
161
+
162
+ if (delta) {
163
+ const content = delta.content
164
+ if (content) {
165
+ if (oldChoice.delta.content) { // we don't want to append to undefined
166
+ oldChoice.delta.content += content
167
+ } else {
168
+ oldChoice.delta.content = content
169
+ }
170
+ }
171
+ } else {
172
+ const text = choice.text
173
+ if (text) {
174
+ if (oldChoice.text) {
175
+ oldChoice.text += text
176
+ } else {
177
+ oldChoice.text = text
178
+ }
179
+ }
180
+ }
181
+
182
+ // tools only exist on chat completions
183
+ const tools = delta && choice.delta.tool_calls
184
+
185
+ if (tools) {
186
+ oldChoice.delta.tool_calls = tools.map((newTool, toolIdx) => {
187
+ const oldTool = oldChoice.delta.tool_calls[toolIdx]
188
+
189
+ if (oldTool) {
190
+ oldTool.function.arguments += newTool.function.arguments
191
+ }
192
+
193
+ return oldTool
194
+ })
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ function convertBufferstoObjects (chunks = []) {
201
+ return Buffer
202
+ .concat(chunks) // combine the buffers
203
+ .toString() // stringify
204
+ .split(/(?=data:)/) // split on "data:"
205
+ .map(chunk => chunk.split('\n').join('')) // remove newlines
206
+ .map(chunk => chunk.substring(6)) // remove 'data: ' from the front
207
+ .slice(0, -1) // remove the last [DONE] message
208
+ .map(JSON.parse) // parse all of the returned objects
209
+ }
210
+
211
+ /**
212
+ * For streamed responses, we need to accumulate all of the content in
213
+ * the chunks, and let the combined content be the final response.
214
+ * This way, spans look the same as when not streamed.
215
+ */
216
+ function wrapStreamIterator (response, options, n) {
217
+ let processChunksAsBuffers = false
218
+ let chunks = []
219
+ return function (itr) {
220
+ return function () {
221
+ const iterator = itr.apply(this, arguments)
222
+ shimmer.wrap(iterator, 'next', next => function () {
223
+ return next.apply(this, arguments)
224
+ .then(res => {
225
+ const { done, value: chunk } = res
226
+
227
+ if (chunk) {
228
+ chunks.push(chunk)
229
+ if (chunk instanceof Buffer) {
230
+ // this operation should be safe
231
+ // if one chunk is a buffer (versus a plain object), the rest should be as well
232
+ processChunksAsBuffers = true
233
+ }
234
+ }
235
+
236
+ if (done) {
237
+ let body = {}
238
+ chunks = chunks.filter(chunk => chunk != null) // filter null or undefined values
239
+
240
+ if (chunks) {
241
+ if (processChunksAsBuffers) {
242
+ chunks = convertBufferstoObjects(chunks)
243
+ }
244
+
245
+ if (chunks.length) {
246
+ // define the initial body having all the content outside of choices from the first chunk
247
+ // this will include import data like created, id, model, etc.
248
+ body = { ...chunks[0], choices: Array.from({ length: n }) }
249
+ // start from the first chunk, and add its choices into the body
250
+ for (let i = 0; i < chunks.length; i++) {
251
+ addStreamedChunk(body, chunks[i])
252
+ }
253
+ }
254
+ }
255
+
256
+ finish({
257
+ headers: response.headers,
258
+ body,
259
+ path: response.url,
260
+ method: options.method
261
+ })
262
+ }
263
+
264
+ return res
265
+ })
266
+ .catch(err => {
267
+ finish(undefined, err)
268
+
269
+ throw err
270
+ })
271
+ })
272
+ return iterator
273
+ }
274
+ }
275
+ }
276
+
144
277
  for (const shim of V4_PACKAGE_SHIMS) {
145
- const { file, targetClass, baseResource, methods } = shim
146
- addHook({ name: 'openai', file, versions: shim.versions || ['>=4'] }, exports => {
278
+ const { file, targetClass, baseResource, methods, versions, streamedResponse } = shim
279
+ addHook({ name: 'openai', file, versions: versions || ['>=4'] }, exports => {
147
280
  const targetPrototype = exports[targetClass].prototype
148
281
 
149
282
  for (const methodName of methods) {
@@ -152,6 +285,22 @@ for (const shim of V4_PACKAGE_SHIMS) {
152
285
  return methodFn.apply(this, arguments)
153
286
  }
154
287
 
288
+ // The OpenAI library lets you set `stream: true` on the options arg to any method
289
+ // However, we only want to handle streamed responses in specific cases
290
+ // chat.completions and completions
291
+ const stream = streamedResponse && getOption(arguments, 'stream', false)
292
+
293
+ // we need to compute how many prompts we are sending in streamed cases for completions
294
+ // not applicable for chat completiond
295
+ let n
296
+ if (stream) {
297
+ n = getOption(arguments, 'n', 1)
298
+ const prompt = getOption(arguments, 'prompt')
299
+ if (Array.isArray(prompt) && typeof prompt[0] !== 'number') {
300
+ n *= prompt.length
301
+ }
302
+ }
303
+
155
304
  const client = this._client || this.client
156
305
 
157
306
  startCh.publish({
@@ -170,19 +319,29 @@ for (const shim of V4_PACKAGE_SHIMS) {
170
319
  // the original response is wrapped in a promise, so we need to unwrap it
171
320
  .then(body => Promise.all([this.responsePromise, body]))
172
321
  .then(([{ response, options }, body]) => {
173
- finishCh.publish({
174
- headers: response.headers,
175
- body,
176
- path: response.url,
177
- method: options.method
178
- })
322
+ if (stream) {
323
+ if (body.iterator) {
324
+ shimmer.wrap(body, 'iterator', wrapStreamIterator(response, options, n))
325
+ } else {
326
+ shimmer.wrap(
327
+ body.response.body, Symbol.asyncIterator, wrapStreamIterator(response, options, n)
328
+ )
329
+ }
330
+ } else {
331
+ finish({
332
+ headers: response.headers,
333
+ body,
334
+ path: response.url,
335
+ method: options.method
336
+ })
337
+ }
179
338
 
180
339
  return body
181
340
  })
182
- .catch(err => {
183
- errorCh.publish({ err })
341
+ .catch(error => {
342
+ finish(undefined, error)
184
343
 
185
- throw err
344
+ throw error
186
345
  })
187
346
  .finally(() => {
188
347
  // maybe we don't want to unwrap here in case the promise is re-used?
@@ -197,3 +356,15 @@ for (const shim of V4_PACKAGE_SHIMS) {
197
356
  return exports
198
357
  })
199
358
  }
359
+
360
+ function finish (response, error) {
361
+ if (error) {
362
+ errorCh.publish({ error })
363
+ }
364
+
365
+ finishCh.publish(response)
366
+ }
367
+
368
+ function getOption (args, option, defaultValue) {
369
+ return args[args.length - 1]?.[option] || defaultValue
370
+ }
@@ -301,7 +301,7 @@ function dispatcherRunWrapperNew (run) {
301
301
  if (!this._allTests) {
302
302
  // Removed in https://github.com/microsoft/playwright/commit/1e52c37b254a441cccf332520f60225a5acc14c7
303
303
  // Not available from >=1.44.0
304
- this._allTests = testGroups.map(g => g.tests).flat()
304
+ this._ddAllTests = testGroups.map(g => g.tests).flat()
305
305
  }
306
306
  remainingTestsByFile = getTestsBySuiteFromTestGroups(arguments[0])
307
307
  return run.apply(this, arguments)
@@ -339,8 +339,9 @@ function getTestByTestId (dispatcher, testId) {
339
339
  if (dispatcher._testById) {
340
340
  return dispatcher._testById.get(testId)?.test
341
341
  }
342
- if (dispatcher._allTests) {
343
- return dispatcher._allTests.find(({ id }) => id === testId)
342
+ const allTests = dispatcher._allTests || dispatcher._ddAllTests
343
+ if (allTests) {
344
+ return allTests.find(({ id }) => id === testId)
344
345
  }
345
346
  }
346
347
 
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const METHODS = require('methods').concat('all')
3
+ const METHODS = require('http').METHODS.map(v => v.toLowerCase()).concat('all')
4
4
  const pathToRegExp = require('path-to-regexp')
5
5
  const shimmer = require('../../datadog-shimmer')
6
6
  const { addHook, channel } = require('./helpers/instrument')
@@ -23,6 +23,9 @@ addHook({
23
23
  }, (seleniumPackage, seleniumVersion) => {
24
24
  // TODO: do not turn this into async. Use promises
25
25
  shimmer.wrap(seleniumPackage.WebDriver.prototype, 'get', get => async function () {
26
+ if (!ciSeleniumDriverGetStartCh.hasSubscribers) {
27
+ return get.apply(this, arguments)
28
+ }
26
29
  let traceId
27
30
  const setTraceId = (inputTraceId) => {
28
31
  traceId = inputTraceId
@@ -40,15 +43,20 @@ addHook({
40
43
  isRumActive
41
44
  })
42
45
 
43
- await this.manage().addCookie({
44
- name: DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME,
45
- value: traceId
46
- })
46
+ if (traceId && isRumActive) {
47
+ await this.manage().addCookie({
48
+ name: DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME,
49
+ value: traceId
50
+ })
51
+ }
47
52
 
48
53
  return getResult
49
54
  })
50
55
 
51
56
  shimmer.wrap(seleniumPackage.WebDriver.prototype, 'quit', quit => async function () {
57
+ if (!ciSeleniumDriverGetStartCh.hasSubscribers) {
58
+ return quit.apply(this, arguments)
59
+ }
52
60
  const isRumActive = await this.executeScript(RUM_STOP_SESSION_SCRIPT)
53
61
 
54
62
  if (isRumActive) {
@@ -58,10 +66,9 @@ addHook({
58
66
  resolve()
59
67
  }, DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS)
60
68
  })
69
+ await this.manage().deleteCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME)
61
70
  }
62
71
 
63
- await this.manage().deleteCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME)
64
-
65
72
  return quit.apply(this, arguments)
66
73
  })
67
74
 
@@ -80,6 +80,10 @@ class GraphQLResolvePlugin extends TracingPlugin {
80
80
  // this will disable resolve subscribers if `config.depth` is set to 0
81
81
  super.configure(config.depth === 0 ? false : config)
82
82
  }
83
+
84
+ finish (finishTime) {
85
+ this.activeSpan.finish(finishTime)
86
+ }
83
87
  }
84
88
 
85
89
  // helpers
@@ -20,7 +20,14 @@ const {
20
20
  removeEfdStringFromTestName,
21
21
  TEST_IS_NEW,
22
22
  TEST_IS_RETRY,
23
- TEST_EARLY_FLAKE_ENABLED
23
+ TEST_EARLY_FLAKE_ENABLED,
24
+ TEST_SESSION_ID,
25
+ TEST_MODULE_ID,
26
+ TEST_MODULE,
27
+ TEST_SUITE_ID,
28
+ TEST_COMMAND,
29
+ TEST_SUITE,
30
+ MOCHA_IS_PARALLEL
24
31
  } = require('../../dd-trace/src/plugins/util/test')
25
32
  const { COMPONENT } = require('../../dd-trace/src/constants')
26
33
  const {
@@ -33,6 +40,22 @@ const {
33
40
  TELEMETRY_ITR_UNSKIPPABLE,
34
41
  TELEMETRY_CODE_COVERAGE_NUM_FILES
35
42
  } = require('../../dd-trace/src/ci-visibility/telemetry')
43
+ const id = require('../../dd-trace/src/id')
44
+ const log = require('../../dd-trace/src/log')
45
+
46
+ function getTestSuiteLevelVisibilityTags (testSuiteSpan) {
47
+ const testSuiteSpanContext = testSuiteSpan.context()
48
+ const suiteTags = {
49
+ [TEST_SUITE_ID]: testSuiteSpanContext.toSpanId(),
50
+ [TEST_SESSION_ID]: testSuiteSpanContext.toTraceId(),
51
+ [TEST_COMMAND]: testSuiteSpanContext._tags[TEST_COMMAND],
52
+ [TEST_MODULE]: 'mocha'
53
+ }
54
+ if (testSuiteSpanContext._parentId) {
55
+ suiteTags[TEST_MODULE_ID] = testSuiteSpanContext._parentId.toString(10)
56
+ }
57
+ return suiteTags
58
+ }
36
59
 
37
60
  class MochaPlugin extends CiPlugin {
38
61
  static get id () {
@@ -50,7 +73,8 @@ class MochaPlugin extends CiPlugin {
50
73
  if (!this.libraryConfig?.isCodeCoverageEnabled) {
51
74
  return
52
75
  }
53
- const testSuiteSpan = this._testSuites.get(suiteFile)
76
+ const testSuite = getTestSuitePath(suiteFile, this.sourceRoot)
77
+ const testSuiteSpan = this._testSuites.get(testSuite)
54
78
 
55
79
  if (!coverageFiles.length) {
56
80
  this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
@@ -73,16 +97,20 @@ class MochaPlugin extends CiPlugin {
73
97
  })
74
98
 
75
99
  this.addSub('ci:mocha:test-suite:start', ({
76
- testSuite,
100
+ testSuiteAbsolutePath,
77
101
  isUnskippable,
78
102
  isForcedToRun,
79
103
  itrCorrelationId
80
104
  }) => {
81
- const store = storage.getStore()
105
+ // If the test module span is undefined, the plugin has not been initialized correctly and we bail out
106
+ if (!this.testModuleSpan) {
107
+ return
108
+ }
109
+ const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
82
110
  const testSuiteMetadata = getTestSuiteCommonTags(
83
111
  this.command,
84
112
  this.frameworkVersion,
85
- getTestSuitePath(testSuite, this.sourceRoot),
113
+ testSuite,
86
114
  'mocha'
87
115
  )
88
116
  if (isUnskippable) {
@@ -109,6 +137,7 @@ class MochaPlugin extends CiPlugin {
109
137
  if (itrCorrelationId) {
110
138
  testSuiteSpan.setTag(ITR_CORRELATION_ID, itrCorrelationId)
111
139
  }
140
+ const store = storage.getStore()
112
141
  this.enter(testSuiteSpan, store)
113
142
  this._testSuites.set(testSuite, testSuiteSpan)
114
143
  })
@@ -142,6 +171,10 @@ class MochaPlugin extends CiPlugin {
142
171
  this.enter(span, store)
143
172
  })
144
173
 
174
+ this.addSub('ci:mocha:worker:finish', () => {
175
+ this.tracer._exporter.flush()
176
+ })
177
+
145
178
  this.addSub('ci:mocha:test:finish', (status) => {
146
179
  const store = storage.getStore()
147
180
  const span = store?.span
@@ -194,7 +227,8 @@ class MochaPlugin extends CiPlugin {
194
227
  hasForcedToRunSuites,
195
228
  hasUnskippableSuites,
196
229
  error,
197
- isEarlyFlakeDetectionEnabled
230
+ isEarlyFlakeDetectionEnabled,
231
+ isParallel
198
232
  }) => {
199
233
  if (this.testSessionSpan) {
200
234
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
@@ -206,6 +240,10 @@ class MochaPlugin extends CiPlugin {
206
240
  this.testModuleSpan.setTag('error', error)
207
241
  }
208
242
 
243
+ if (isParallel) {
244
+ this.testSessionSpan.setTag(MOCHA_IS_PARALLEL, 'true')
245
+ }
246
+
209
247
  addIntelligentTestRunnerSpanTags(
210
248
  this.testSessionSpan,
211
249
  this.testModuleSpan,
@@ -234,6 +272,37 @@ class MochaPlugin extends CiPlugin {
234
272
  this.libraryConfig = null
235
273
  this.tracer._exporter.flush()
236
274
  })
275
+
276
+ this.addSub('ci:mocha:worker-report:trace', (traces) => {
277
+ const formattedTraces = JSON.parse(traces).map(trace =>
278
+ trace.map(span => {
279
+ const formattedSpan = {
280
+ ...span,
281
+ span_id: id(span.span_id),
282
+ trace_id: id(span.trace_id),
283
+ parent_id: id(span.parent_id)
284
+ }
285
+ if (formattedSpan.name === 'mocha.test') {
286
+ const testSuite = span.meta[TEST_SUITE]
287
+ const testSuiteSpan = this._testSuites.get(testSuite)
288
+ if (!testSuiteSpan) {
289
+ log.warn(`Test suite span not found for test span with test suite ${testSuite}`)
290
+ return formattedSpan
291
+ }
292
+ const suiteTags = getTestSuiteLevelVisibilityTags(testSuiteSpan)
293
+ formattedSpan.meta = {
294
+ ...formattedSpan.meta,
295
+ ...suiteTags
296
+ }
297
+ }
298
+ return formattedSpan
299
+ })
300
+ )
301
+
302
+ formattedTraces.forEach(trace => {
303
+ this.tracer._exporter.export(trace)
304
+ })
305
+ })
237
306
  }
238
307
 
239
308
  startTestSpan (testInfo) {
@@ -242,7 +311,8 @@ class MochaPlugin extends CiPlugin {
242
311
  title,
243
312
  isNew,
244
313
  isEfdRetry,
245
- testStartLine
314
+ testStartLine,
315
+ isParallel
246
316
  } = testInfo
247
317
 
248
318
  const testName = removeEfdStringFromTestName(testInfo.testName)
@@ -257,8 +327,12 @@ class MochaPlugin extends CiPlugin {
257
327
  extraTags[TEST_SOURCE_START] = testStartLine
258
328
  }
259
329
 
330
+ if (isParallel) {
331
+ extraTags[MOCHA_IS_PARALLEL] = 'true'
332
+ }
333
+
260
334
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
261
- const testSuiteSpan = this._testSuites.get(testSuiteAbsolutePath)
335
+ const testSuiteSpan = this._testSuites.get(testSuite)
262
336
 
263
337
  if (this.repositoryRoot !== this.sourceRoot && !!this.repositoryRoot) {
264
338
  extraTags[TEST_SOURCE_FILE] = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
@@ -6,7 +6,7 @@ const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
6
  const { COMPONENT } = require('../../dd-trace/src/constants')
7
7
  const web = require('../../dd-trace/src/plugins/util/web')
8
8
 
9
- const errorPages = ['/404', '/500', '/_error', '/_not-found']
9
+ const errorPages = ['/404', '/500', '/_error', '/_not-found', '/_not-found/page']
10
10
 
11
11
  class NextPlugin extends ServerPlugin {
12
12
  static get id () {
@@ -120,7 +120,6 @@ class NextPlugin extends ServerPlugin {
120
120
  'resource.name': `${req.method} ${page}`.trim(),
121
121
  'next.page': page
122
122
  })
123
-
124
123
  web.setRoute(req, page)
125
124
  }
126
125