dd-trace 5.52.0 → 5.53.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 (55) hide show
  1. package/README.md +5 -0
  2. package/index.d.ts +54 -6
  3. package/package.json +1 -1
  4. package/packages/datadog-instrumentations/src/amqplib.js +8 -5
  5. package/packages/datadog-instrumentations/src/child_process.js +2 -1
  6. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +16 -1
  7. package/packages/datadog-instrumentations/src/couchbase.js +2 -1
  8. package/packages/datadog-instrumentations/src/cucumber.js +41 -46
  9. package/packages/datadog-instrumentations/src/express.js +2 -6
  10. package/packages/datadog-instrumentations/src/fs.js +6 -5
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  12. package/packages/datadog-instrumentations/src/helpers/register.js +17 -12
  13. package/packages/datadog-instrumentations/src/http/client.js +2 -1
  14. package/packages/datadog-instrumentations/src/iovalkey.js +51 -0
  15. package/packages/datadog-instrumentations/src/jest.js +49 -41
  16. package/packages/datadog-instrumentations/src/kafkajs.js +21 -8
  17. package/packages/datadog-instrumentations/src/mocha/main.js +33 -46
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +72 -75
  19. package/packages/datadog-instrumentations/src/mysql2.js +3 -1
  20. package/packages/datadog-instrumentations/src/net.js +3 -1
  21. package/packages/datadog-instrumentations/src/next.js +6 -14
  22. package/packages/datadog-instrumentations/src/pg.js +5 -11
  23. package/packages/datadog-instrumentations/src/playwright.js +60 -69
  24. package/packages/datadog-instrumentations/src/url.js +9 -17
  25. package/packages/datadog-instrumentations/src/vitest.js +55 -75
  26. package/packages/datadog-plugin-cucumber/src/index.js +29 -18
  27. package/packages/datadog-plugin-iovalkey/src/index.js +18 -0
  28. package/packages/datadog-plugin-jest/src/index.js +14 -8
  29. package/packages/datadog-plugin-kafkajs/src/producer.js +8 -5
  30. package/packages/datadog-plugin-mocha/src/index.js +55 -35
  31. package/packages/datadog-plugin-playwright/src/index.js +26 -20
  32. package/packages/datadog-plugin-redis/src/index.js +8 -3
  33. package/packages/datadog-plugin-vitest/src/index.js +53 -42
  34. package/packages/datadog-shimmer/src/shimmer.js +164 -33
  35. package/packages/dd-trace/src/appsec/graphql.js +2 -2
  36. package/packages/dd-trace/src/appsec/index.js +14 -11
  37. package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
  38. package/packages/dd-trace/src/appsec/rasp/utils.js +11 -6
  39. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
  40. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -2
  41. package/packages/dd-trace/src/appsec/telemetry/rasp.js +0 -9
  42. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -6
  43. package/packages/dd-trace/src/config.js +1 -1
  44. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +59 -7
  45. package/packages/dd-trace/src/debugger/devtools_client/index.js +10 -26
  46. package/packages/dd-trace/src/debugger/devtools_client/send.js +8 -7
  47. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +15 -7
  48. package/packages/dd-trace/src/debugger/devtools_client/state.js +21 -1
  49. package/packages/dd-trace/src/dogstatsd.js +2 -0
  50. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  51. package/packages/dd-trace/src/plugins/index.js +1 -0
  52. package/packages/dd-trace/src/proxy.js +0 -4
  53. package/packages/dd-trace/src/serverless.js +0 -48
  54. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +8 -0
  55. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
@@ -41,6 +41,7 @@ const testStartCh = channel('ci:jest:test:start')
41
41
  const testSkippedCh = channel('ci:jest:test:skip')
42
42
  const testFinishCh = channel('ci:jest:test:finish')
43
43
  const testErrCh = channel('ci:jest:test:err')
44
+ const testFnCh = channel('ci:jest:test:fn')
44
45
 
45
46
  const skippableSuitesCh = channel('ci:jest:test-suite:skippable')
46
47
  const libraryConfigurationCh = channel('ci:jest:library-configuration')
@@ -79,7 +80,7 @@ let testManagementAttemptToFixRetries = 0
79
80
 
80
81
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
81
82
 
82
- const asyncResources = new WeakMap()
83
+ const testContexts = new WeakMap()
83
84
  const originalTestFns = new WeakMap()
84
85
  const originalHookFns = new WeakMap()
85
86
  const retriedTestsToNumAttempts = new Map()
@@ -307,8 +308,6 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
307
308
  const testParameters = getTestParametersString(this.nameToParams, event.test.name)
308
309
  // Async resource for this test is created here
309
310
  // It is used later on by the test_done handler
310
- const asyncResource = new AsyncResource('bound-anonymous-fn')
311
- asyncResources.set(event.test, asyncResource)
312
311
  const testName = getJestTestName(event.test)
313
312
  const originalTestName = removeEfdStringFromTestName(removeAttemptToFixStringFromTestName(testName))
314
313
 
@@ -336,22 +335,24 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
336
335
  }
337
336
 
338
337
  const isJestRetry = event.test?.invocations > 1
339
- asyncResource.runInAsyncScope(() => {
340
- testStartCh.publish({
341
- name: originalTestName,
342
- suite: this.testSuite,
343
- testSourceFile: this.testSourceFile,
344
- displayName: this.displayName,
345
- testParameters,
346
- frameworkVersion: jestVersion,
347
- isNew: isNewTest,
348
- isEfdRetry: numEfdRetry > 0,
349
- isAttemptToFix,
350
- isAttemptToFixRetry: numOfAttemptsToFixRetries > 0,
351
- isJestRetry,
352
- isDisabled,
353
- isQuarantined
354
- })
338
+ const ctx = {
339
+ name: originalTestName,
340
+ suite: this.testSuite,
341
+ testSourceFile: this.testSourceFile,
342
+ displayName: this.displayName,
343
+ testParameters,
344
+ frameworkVersion: jestVersion,
345
+ isNew: isNewTest,
346
+ isEfdRetry: numEfdRetry > 0,
347
+ isAttemptToFix,
348
+ isAttemptToFixRetry: numOfAttemptsToFixRetries > 0,
349
+ isJestRetry,
350
+ isDisabled,
351
+ isQuarantined
352
+ }
353
+ testContexts.set(event.test, ctx)
354
+
355
+ testStartCh.runStores(ctx, () => {
355
356
  for (const hook of event.test.parent.hooks) {
356
357
  let hookFn = hook.fn
357
358
  if (!originalHookFns.has(hook)) {
@@ -359,10 +360,21 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
359
360
  } else {
360
361
  hookFn = originalHookFns.get(hook)
361
362
  }
362
- hook.fn = asyncResource.bind(hookFn)
363
+ const wrapperHook = function () {
364
+ return testFnCh.runStores(ctx, () => hookFn.apply(this, arguments))
365
+ }
366
+ // If we don't do this, the timeout will be not be triggered
367
+ Object.defineProperty(wrapperHook, 'length', { value: hookFn.length })
368
+ hook.fn = wrapperHook
363
369
  }
364
- originalTestFns.set(event.test, event.test.fn)
365
- event.test.fn = asyncResource.bind(event.test.fn)
370
+ const originalFn = event.test.fn
371
+ originalTestFns.set(event.test, originalFn)
372
+ const wrapper = function () {
373
+ return testFnCh.runStores(ctx, () => originalFn.apply(this, arguments))
374
+ }
375
+ // If we don't do this, the timeout will be not be triggered
376
+ Object.defineProperty(wrapper, 'length', { value: originalFn.length })
377
+ event.test.fn = wrapper
366
378
  })
367
379
  }
368
380
 
@@ -460,16 +472,15 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
460
472
  const willBeRetried = numRetries > 0 && numTestExecutions - 1 < numRetries
461
473
  const mightHitBreakpoint = this.isDiEnabled && numTestExecutions >= 2
462
474
 
463
- const asyncResource = asyncResources.get(event.test)
475
+ const ctx = testContexts.get(event.test)
464
476
 
465
477
  if (status === 'fail') {
466
478
  const shouldSetProbe = this.isDiEnabled && willBeRetried && numTestExecutions === 1
467
- asyncResource.runInAsyncScope(() => {
468
- testErrCh.publish({
469
- error: formatJestError(event.test.errors[0]),
470
- shouldSetProbe,
471
- promises
472
- })
479
+ testErrCh.publish({
480
+ ...ctx.currentStore,
481
+ error: formatJestError(event.test.errors[0]),
482
+ shouldSetProbe,
483
+ promises
473
484
  })
474
485
  }
475
486
 
@@ -488,15 +499,14 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
488
499
  isAtrRetry = true
489
500
  }
490
501
 
491
- asyncResource.runInAsyncScope(() => {
492
- testFinishCh.publish({
493
- status,
494
- testStartLine: getTestLineStart(event.test.asyncError, this.testSuite),
495
- attemptToFixPassed,
496
- failedAllTests,
497
- attemptToFixFailed,
498
- isAtrRetry
499
- })
502
+ testFinishCh.publish({
503
+ ...ctx.currentStore,
504
+ status,
505
+ testStartLine: getTestLineStart(event.test.asyncError, this.testSuite),
506
+ attemptToFixPassed,
507
+ failedAllTests,
508
+ attemptToFixFailed,
509
+ isAtrRetry
500
510
  })
501
511
 
502
512
  if (promises.isProbeReady) {
@@ -625,7 +635,7 @@ addHook({
625
635
  })
626
636
 
627
637
  function cliWrapper (cli, jestVersion) {
628
- const wrapped = shimmer.wrap(cli, 'runCLI', runCLI => async function () {
638
+ shimmer.wrap(cli, 'runCLI', runCLI => async function () {
629
639
  let onDone
630
640
  const configurationPromise = new Promise((resolve) => {
631
641
  onDone = resolve
@@ -864,8 +874,6 @@ function cliWrapper (cli, jestVersion) {
864
874
  return result
865
875
  })
866
876
 
867
- cli.runCLI = wrapped.runCLI
868
-
869
877
  return cli
870
878
  }
871
879
 
@@ -7,6 +7,8 @@ const {
7
7
  } = require('./helpers/instrument')
8
8
  const shimmer = require('../../datadog-shimmer')
9
9
 
10
+ const log = require('../../dd-trace/src/log')
11
+
10
12
  const producerStartCh = channel('apm:kafkajs:produce:start')
11
13
  const producerCommitCh = channel('apm:kafkajs:produce:commit')
12
14
  const producerFinishCh = channel('apm:kafkajs:produce:finish')
@@ -21,6 +23,8 @@ const batchConsumerStartCh = channel('apm:kafkajs:consume-batch:start')
21
23
  const batchConsumerFinishCh = channel('apm:kafkajs:consume-batch:finish')
22
24
  const batchConsumerErrorCh = channel('apm:kafkajs:consume-batch:error')
23
25
 
26
+ const disabledHeaderWeakSet = new WeakSet()
27
+
24
28
  function commitsFromEvent (event) {
25
29
  const { payload: { groupId, topics } } = event
26
30
  const commitList = []
@@ -65,15 +69,14 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
65
69
 
66
70
  try {
67
71
  const { topic, messages = [] } = arguments[0]
68
- for (const message of messages) {
69
- if (message !== null && typeof message === 'object') {
70
- message.headers = message.headers || {}
71
- }
72
- }
73
- producerStartCh.publish({ topic, messages, bootstrapServers, clusterId })
74
-
72
+ producerStartCh.publish({
73
+ topic,
74
+ messages,
75
+ bootstrapServers,
76
+ clusterId,
77
+ disableHeaderInjection: disabledHeaderWeakSet.has(producer)
78
+ })
75
79
  const result = send.apply(this, arguments)
76
-
77
80
  result.then(
78
81
  innerAsyncResource.bind(res => {
79
82
  producerFinishCh.publish(undefined)
@@ -81,6 +84,16 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
81
84
  }),
82
85
  innerAsyncResource.bind(err => {
83
86
  if (err) {
87
+ // Fixes bug where we would inject message headers for kafka brokers that don't support headers
88
+ // (version <0.11). On the error, we disable header injection.
89
+ // Tnfortunately the error name / type is not more specific.
90
+ // This approach is implemented by other tracers as well.
91
+ if (err.name === 'KafkaJSProtocolError' && err.type === 'UNKNOWN') {
92
+ disabledHeaderWeakSet.add(producer)
93
+ log.error('Kafka Broker responded with UNKNOWN_SERVER_ERROR (-1). ' +
94
+ 'Please look at broker logs for more information. ' +
95
+ 'Tracer message header injection for Kafka is disabled.')
96
+ }
84
97
  producerErrorCh.publish(err)
85
98
  }
86
99
  producerFinishCh.publish(undefined)
@@ -26,7 +26,7 @@ const {
26
26
  getOnHookEndHandler,
27
27
  getOnFailHandler,
28
28
  getOnPendingHandler,
29
- testFileToSuiteAr,
29
+ testFileToSuiteCtx,
30
30
  newTests,
31
31
  testsQuarantined,
32
32
  getTestFullName,
@@ -53,7 +53,7 @@ const originalCoverageMap = createCoverageMap()
53
53
  let untestedCoverage
54
54
 
55
55
  // test channels
56
- const testStartCh = channel('ci:mocha:test:start')
56
+ const testFinishCh = channel('ci:mocha:test:finish')
57
57
 
58
58
  // test suite channels
59
59
  const testSuiteStartCh = channel('ci:mocha:test-suite:start')
@@ -166,7 +166,7 @@ function getOnEndHandler (isParallel) {
166
166
  error = new Error(`Failed tests: ${this.failures}.`)
167
167
  }
168
168
 
169
- testFileToSuiteAr.clear()
169
+ testFileToSuiteCtx.clear()
170
170
 
171
171
  let testCodeCoverageLinesTotal
172
172
  if (global.__coverage__) {
@@ -312,7 +312,7 @@ addHook({
312
312
  }, (Mocha) => {
313
313
  shimmer.wrap(Mocha.prototype, 'run', run => function () {
314
314
  // Workers do not need to request any data, just run the tests
315
- if (!testStartCh.hasSubscribers || process.env.MOCHA_WORKER_ID || this.options.parallel) {
315
+ if (!testFinishCh.hasSubscribers || process.env.MOCHA_WORKER_ID || this.options.parallel) {
316
316
  return run.apply(this, arguments)
317
317
  }
318
318
 
@@ -367,7 +367,7 @@ addHook({
367
367
  }, (run) => {
368
368
  // `runMocha` is an async function
369
369
  shimmer.wrap(run, 'runMocha', runMocha => function () {
370
- if (!testStartCh.hasSubscribers) {
370
+ if (!testFinishCh.hasSubscribers) {
371
371
  return runMocha.apply(this, arguments)
372
372
  }
373
373
  const mocha = arguments[0]
@@ -403,7 +403,7 @@ addHook({
403
403
  shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
404
404
 
405
405
  shimmer.wrap(Runner.prototype, 'run', run => function () {
406
- if (!testStartCh.hasSubscribers) {
406
+ if (!testFinishCh.hasSubscribers) {
407
407
  return run.apply(this, arguments)
408
408
  }
409
409
 
@@ -430,20 +430,18 @@ addHook({
430
430
  if (suite.root || !suite.tests.length) {
431
431
  return
432
432
  }
433
- let asyncResource = testFileToSuiteAr.get(suite.file)
434
- if (!asyncResource) {
435
- asyncResource = new AsyncResource('bound-anonymous-fn')
436
- testFileToSuiteAr.set(suite.file, asyncResource)
433
+ let ctx = testFileToSuiteCtx.get(suite.file)
434
+ if (!ctx) {
437
435
  const isUnskippable = unskippableSuites.includes(suite.file)
438
436
  isForcedToRun = isUnskippable && suitesToSkip.includes(getTestSuitePath(suite.file, process.cwd()))
439
- asyncResource.runInAsyncScope(() => {
440
- testSuiteStartCh.publish({
441
- testSuiteAbsolutePath: suite.file,
442
- isUnskippable,
443
- isForcedToRun,
444
- itrCorrelationId
445
- })
446
- })
437
+ ctx = {
438
+ testSuiteAbsolutePath: suite.file,
439
+ isUnskippable,
440
+ isForcedToRun,
441
+ itrCorrelationId
442
+ }
443
+ testFileToSuiteCtx.set(suite.file, ctx)
444
+ testSuiteStartCh.runStores(ctx, () => { })
447
445
  }
448
446
  })
449
447
 
@@ -485,11 +483,9 @@ addHook({
485
483
  resetCoverage(global.__coverage__)
486
484
  }
487
485
 
488
- const asyncResource = testFileToSuiteAr.get(suite.file)
489
- if (asyncResource) {
490
- asyncResource.runInAsyncScope(() => {
491
- testSuiteFinishCh.publish(status)
492
- })
486
+ const ctx = testFileToSuiteCtx.get(suite.file)
487
+ if (ctx) {
488
+ testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => { })
493
489
  } else {
494
490
  log.warn(() => `No AsyncResource found for suite ${suite.file}`)
495
491
  }
@@ -520,58 +516,49 @@ addHook({
520
516
  file: 'src/WorkerHandler.js'
521
517
  }, (workerHandlerPackage) => {
522
518
  shimmer.wrap(workerHandlerPackage.prototype, 'exec', exec => function (_, path) {
523
- if (!testStartCh.hasSubscribers) {
519
+ if (!testFinishCh.hasSubscribers) {
524
520
  return exec.apply(this, arguments)
525
521
  }
526
522
  if (!path?.length) {
527
523
  return exec.apply(this, arguments)
528
524
  }
529
525
  const [testSuiteAbsolutePath] = path
530
- const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
526
+ const testSuiteContext = { }
531
527
 
532
528
  function onMessage (message) {
533
529
  if (Array.isArray(message)) {
534
530
  const [messageCode, payload] = message
535
531
  if (messageCode === MOCHA_WORKER_TRACE_PAYLOAD_CODE) {
536
- testSuiteAsyncResource.runInAsyncScope(() => {
537
- workerReportTraceCh.publish(payload)
538
- })
532
+ workerReportTraceCh.publish(payload)
539
533
  }
540
534
  }
541
535
  }
542
536
 
543
537
  this.worker.on('message', onMessage)
544
538
 
545
- testSuiteAsyncResource.runInAsyncScope(() => {
546
- testSuiteStartCh.publish({
547
- testSuiteAbsolutePath
548
- })
549
- })
539
+ testSuiteContext.testSuiteAbsolutePath = testSuiteAbsolutePath
540
+ testSuiteStartCh.runStores(testSuiteContext, () => { })
550
541
 
551
542
  try {
552
543
  const promise = exec.apply(this, arguments)
553
544
  promise.then(
554
545
  (result) => {
555
546
  const status = result.failureCount === 0 ? 'pass' : 'fail'
556
- testSuiteAsyncResource.runInAsyncScope(() => {
557
- testSuiteFinishCh.publish(status)
558
- })
547
+ testSuiteFinishCh.publish({ status, ...testSuiteContext.currentStore }, () => { })
559
548
  this.worker.off('message', onMessage)
560
549
  },
561
550
  (err) => {
562
- testSuiteAsyncResource.runInAsyncScope(() => {
563
- testSuiteErrorCh.publish(err)
564
- testSuiteFinishCh.publish('fail')
565
- })
551
+ testSuiteContext.error = err
552
+ testSuiteErrorCh.runStores(testSuiteContext, () => { })
553
+ testSuiteFinishCh.publish({ status: 'fail', ...testSuiteContext.currentStore }, () => { })
566
554
  this.worker.off('message', onMessage)
567
555
  }
568
556
  )
569
557
  return promise
570
558
  } catch (err) {
571
- testSuiteAsyncResource.runInAsyncScope(() => {
572
- testSuiteErrorCh.publish(err)
573
- testSuiteFinishCh.publish('fail')
574
- })
559
+ testSuiteContext.error = err
560
+ testSuiteErrorCh.runStores(testSuiteContext, () => { })
561
+ testSuiteFinishCh.publish({ status: 'fail', ...testSuiteContext.currentStore }, () => { })
575
562
  this.worker.off('message', onMessage)
576
563
  throw err
577
564
  }
@@ -588,7 +575,7 @@ addHook({
588
575
  file: 'lib/nodejs/parallel-buffered-runner.js'
589
576
  }, (ParallelBufferedRunner, frameworkVersion) => {
590
577
  shimmer.wrap(ParallelBufferedRunner.prototype, 'run', run => function (cb, { files }) {
591
- if (!testStartCh.hasSubscribers) {
578
+ if (!testFinishCh.hasSubscribers) {
592
579
  return run.apply(this, arguments)
593
580
  }
594
581
 
@@ -629,7 +616,7 @@ addHook({
629
616
  const { BufferedWorkerPool } = BufferedWorkerPoolPackage
630
617
 
631
618
  shimmer.wrap(BufferedWorkerPool.prototype, 'run', run => async function (testSuiteAbsolutePath, workerArgs) {
632
- if (!testStartCh.hasSubscribers || (!config.isKnownTestsEnabled && !config.isTestManagementTestsEnabled)) {
619
+ if (!testFinishCh.hasSubscribers || (!config.isKnownTestsEnabled && !config.isTestManagementTestsEnabled)) {
633
620
  return run.apply(this, arguments)
634
621
  }
635
622