dd-trace 5.103.0 → 5.104.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 (90) hide show
  1. package/index.d.ts +25 -3
  2. package/package.json +4 -3
  3. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
  4. package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
  5. package/packages/datadog-instrumentations/src/cucumber.js +103 -30
  6. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  7. package/packages/datadog-instrumentations/src/graphql.js +0 -5
  8. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  9. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  10. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -5
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +14 -13
  14. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  15. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  16. package/packages/datadog-instrumentations/src/jest.js +33 -36
  17. package/packages/datadog-instrumentations/src/kafkajs.js +25 -6
  18. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  19. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  20. package/packages/datadog-instrumentations/src/mocha/main.js +272 -91
  21. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  22. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  23. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  24. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  25. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  26. package/packages/datadog-instrumentations/src/pg.js +1 -1
  27. package/packages/datadog-instrumentations/src/playwright.js +22 -5
  28. package/packages/datadog-instrumentations/src/router.js +4 -2
  29. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  30. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +26 -19
  31. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  32. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  33. package/packages/datadog-plugin-kafkajs/src/producer.js +32 -0
  34. package/packages/datadog-plugin-mongodb-core/src/index.js +54 -19
  35. package/packages/datadog-plugin-redis/src/index.js +37 -2
  36. package/packages/datadog-plugin-undici/src/index.js +19 -0
  37. package/packages/datadog-plugin-vitest/src/index.js +19 -7
  38. package/packages/datadog-shimmer/src/shimmer.js +35 -0
  39. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  40. package/packages/dd-trace/src/appsec/index.js +10 -3
  41. package/packages/dd-trace/src/appsec/reporter.js +19 -5
  42. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  43. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  44. package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -0
  45. package/packages/dd-trace/src/config/supported-configurations.json +9 -0
  46. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  47. package/packages/dd-trace/src/datastreams/context.js +4 -2
  48. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +26 -19
  49. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  50. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  51. package/packages/dd-trace/src/id.js +17 -4
  52. package/packages/dd-trace/src/lambda/handler.js +2 -4
  53. package/packages/dd-trace/src/llmobs/sdk.js +10 -0
  54. package/packages/dd-trace/src/log/writer.js +3 -1
  55. package/packages/dd-trace/src/noop/span.js +3 -1
  56. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  57. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  58. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  59. package/packages/dd-trace/src/plugins/ci_plugin.js +3 -13
  60. package/packages/dd-trace/src/plugins/log_plugin.js +3 -1
  61. package/packages/dd-trace/src/plugins/tracing.js +5 -3
  62. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  63. package/packages/dd-trace/src/plugins/util/test.js +82 -0
  64. package/packages/dd-trace/src/plugins/util/web.js +11 -0
  65. package/packages/dd-trace/src/scope.js +7 -5
  66. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  67. package/vendor/dist/opentracing/LICENSE +0 -201
  68. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  69. package/vendor/dist/opentracing/constants.d.ts +0 -61
  70. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  71. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  72. package/vendor/dist/opentracing/functions.d.ts +0 -20
  73. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  74. package/vendor/dist/opentracing/index.d.ts +0 -12
  75. package/vendor/dist/opentracing/index.js +0 -1
  76. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  77. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  78. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  79. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  80. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  81. package/vendor/dist/opentracing/noop.d.ts +0 -8
  82. package/vendor/dist/opentracing/reference.d.ts +0 -33
  83. package/vendor/dist/opentracing/span.d.ts +0 -147
  84. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  85. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  86. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  87. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  88. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  89. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  90. package/vendor/dist/opentracing/tracer.d.ts +0 -127
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ClientRequest, IncomingMessage, OutgoingMessage, ServerResponse } from "http";
2
2
  import { LookupFunction } from 'net';
3
- import * as opentracing from "./vendor/dist/opentracing";
3
+ import * as opentracing from "opentracing";
4
4
  import * as otel from "@opentelemetry/api";
5
5
 
6
6
  /**
@@ -2849,13 +2849,13 @@ declare namespace tracer {
2849
2849
  * [mocha](https://mochajs.org/) module.
2850
2850
  */
2851
2851
  interface mocha extends Integration {}
2852
-
2852
+
2853
2853
  /**
2854
2854
  * This plugin automatically instruments the
2855
2855
  * [modelcontextprotocol-sdk](https://github.com/npmjs/package/@modelcontextprotocol/sdk) library.
2856
2856
  */
2857
2857
  interface modelcontextprotocol_sdk extends Instrumentation {}
2858
-
2858
+
2859
2859
  /**
2860
2860
  * This plugin automatically instruments the
2861
2861
  * [moleculer](https://moleculer.services/) module.
@@ -2887,6 +2887,24 @@ declare namespace tracer {
2887
2887
  */
2888
2888
  heartbeatEnabled?: boolean;
2889
2889
 
2890
+ /**
2891
+ * How to mask primitive query values in the `mongodb.query` tag and the
2892
+ * resource name (when `queryInResourceName` is also enabled). Keys,
2893
+ * operator names, and array / pipeline shape are preserved so the masked
2894
+ * query is still a usable query signature.
2895
+ *
2896
+ * - `'types'`: replace each primitive leaf with its `typeof` name
2897
+ * (`'string'`, `'number'`, `'boolean'`, `'bigint'`, `'object'`,
2898
+ * `'null'`). Keeps the same redaction guarantee as `'redact'` but
2899
+ * preserves the value types so the rendered query can still be used
2900
+ * to design indexes.
2901
+ * - `'redact'`: replace each primitive leaf with `'?'`. Strictest masking.
2902
+ * - `'none'`: do not mask. Values land verbatim on the span.
2903
+ *
2904
+ * @default 'none'
2905
+ */
2906
+ obfuscateQuery?: 'none' | 'types' | 'redact';
2907
+
2890
2908
  /**
2891
2909
  * Whether to include the query contents in the resource name.
2892
2910
  */
@@ -3584,11 +3602,15 @@ declare namespace tracer {
3584
3602
 
3585
3603
  /**
3586
3604
  * Enable LLM Observability tracing.
3605
+ *
3606
+ * @deprecated Enabling LLM Observability via `llmobs.enable()` is deprecated and will be removed in dd-trace@7.0.0. Please instantiate LLM Observability via DD_LLMOBS_ENABLED or `tracer.init({ llmobs: ...options })`.
3587
3607
  */
3588
3608
  enable (options: LLMObsEnableOptions): void,
3589
3609
 
3590
3610
  /**
3591
3611
  * Disable LLM Observability tracing.
3612
+ *
3613
+ * @deprecated Disabling LLM Observability via `llmobs.disable()` is deprecated and will be removed in dd-trace@7.0.0. Set DD_LLMOBS_ENABLED=false to disable LLM Observability.
3592
3614
  */
3593
3615
  disable (): void,
3594
3616
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.103.0",
3
+ "version": "5.104.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -72,6 +72,7 @@
72
72
  "test:integration:aiguard:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/aiguard/*.spec.js\"",
73
73
  "test:integration:appsec": "mocha --timeout 60000 \"integration-tests/appsec/*.spec.js\"",
74
74
  "test:integration:appsec:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/appsec/*.spec.js\"",
75
+ "test:integration:crashtracking": "mocha --timeout 60000 \"integration-tests/crashtracking/*.spec.js\"",
75
76
  "test:integration:bun": "mocha --timeout 60000 \"integration-tests/bun/*.spec.js\"",
76
77
  "test:integration:cucumber": "mocha --timeout 60000 \"integration-tests/cucumber/*.spec.js\"",
77
78
  "test:integration:cucumber:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/cucumber/*.spec.js\"",
@@ -161,7 +162,8 @@
161
162
  ],
162
163
  "dependencies": {
163
164
  "dc-polyfill": "^0.1.11",
164
- "import-in-the-middle": "^3.0.1"
165
+ "import-in-the-middle": "^3.0.1",
166
+ "opentracing": ">=0.14.7"
165
167
  },
166
168
  "optionalDependencies": {
167
169
  "@datadog/libdatadog": "0.9.3",
@@ -218,7 +220,6 @@
218
220
  "node-preload": "^0.2.1",
219
221
  "nyc": "^18.0.0",
220
222
  "octokit": "^5.0.3",
221
- "opentracing": ">=0.14.7",
222
223
  "p-limit": "^7.2.0",
223
224
  "proxyquire": "^2.1.3",
224
225
  "retry": "^0.13.1",
@@ -198,7 +198,7 @@ function wrapSmithySend (send) {
198
198
  })
199
199
 
200
200
  if (typeof cb === 'function') {
201
- args[args.length - 1] = shimmer.wrapFunction(cb, cb => function (err, result) {
201
+ args[args.length - 1] = shimmer.wrapCallback(cb, cb => function (err, result) {
202
202
  addResponse(ctx, err, result)
203
203
 
204
204
  handleCompletion(result, ctx, channels)
@@ -270,7 +270,7 @@ function handleCompletion (result, ctx, channels) {
270
270
 
271
271
  function wrapCb (cb, channels, ctx) {
272
272
  // eslint-disable-next-line n/handle-callback-err
273
- return shimmer.wrapFunction(cb, cb => function wrappedCb (err, response) {
273
+ return shimmer.wrapCallback(cb, cb => function wrappedCb (err, response) {
274
274
  ctx = { request: ctx.request, response }
275
275
  return channels.responseStart.runStores(ctx, () => {
276
276
  try {
@@ -29,9 +29,12 @@ addHook({ name: 'cassandra-driver', versions: ['>=3.0.0'] }, cassandra => {
29
29
 
30
30
  try {
31
31
  const res = batch.apply(this, arguments)
32
- if (typeof res === 'function' || !res) {
32
+ if (typeof res === 'function') {
33
33
  return wrapCallback(finishCh, errorCh, startCtx, res)
34
34
  }
35
+ if (!res) {
36
+ return res
37
+ }
35
38
  return res.then(
36
39
  () => finish(finishCh, errorCh, startCtx),
37
40
  err => finish(finishCh, errorCh, startCtx, err)
@@ -162,7 +165,7 @@ function finish (finishCh, errorCh, ctx, error) {
162
165
  }
163
166
 
164
167
  function wrapCallback (finishCh, errorCh, ctx, callback) {
165
- return shimmer.wrapFunction(callback, callback => function (err) {
168
+ return shimmer.wrapCallback(callback, callback => function (err) {
166
169
  if (err) {
167
170
  ctx.error = err
168
171
  errorCh.publish(ctx)
@@ -15,10 +15,12 @@ const {
15
15
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
16
16
  getIsFaultyEarlyFlakeDetection,
17
17
  getEfdRetryCount,
18
+ getMaxEfdRetryCount,
18
19
  recordAttemptToFixExecution,
19
20
  collectAttemptToFixExecutionsFromTraces,
20
21
  logAttemptToFixTestExecution,
21
22
  logTestOptimizationSummary,
23
+ getTestOptimizationRequestResults,
22
24
  } = require('../../dd-trace/src/plugins/util/test')
23
25
  const satisfies = require('../../../vendor/dist/semifies')
24
26
  const { addHook, channel } = require('./helpers/instrument')
@@ -51,6 +53,8 @@ const itrSkippedSuitesCh = channel('ci:cucumber:itr:skipped-suites')
51
53
 
52
54
  const getCodeCoverageCh = channel('ci:nyc:get-coverage')
53
55
 
56
+ const DD_EFD_RETRY_COUNT_MESSAGE = '_ddEfdRetryCount'
57
+
54
58
  const isMarkedAsUnskippable = (pickle) => {
55
59
  return pickle.tags.some(tag => tag.name === '@datadog:unskippable')
56
60
  }
@@ -67,7 +71,7 @@ const atrStatusesByScenarioKey = new Map()
67
71
  const numRetriesByPickleId = new Map()
68
72
  const efdRetryCountByPickleId = new Map()
69
73
  const efdSlowAbortedPickleIds = new Set()
70
- const testCaseStartedTimesById = new Map()
74
+ const finishedParallelSuites = new Set()
71
75
  const numAttemptToCtx = new Map()
72
76
  const newTestsByTestFullname = new Map()
73
77
  const attemptToFixTestsByTestFullname = new Map()
@@ -117,6 +121,68 @@ function getSuiteStatusFromTestStatuses (testStatuses) {
117
121
  return 'pass'
118
122
  }
119
123
 
124
+ function getConfiguredEfdRetryCount () {
125
+ const maxSlowTestRetryCount = getMaxEfdRetryCount(earlyFlakeDetectionSlowTestRetries)
126
+ return maxSlowTestRetryCount || earlyFlakeDetectionNumRetries
127
+ }
128
+
129
+ function publishWorkerEfdRetryCount (pickle, retryCount) {
130
+ if (typeof process.send !== 'function') return
131
+
132
+ try {
133
+ process.send({
134
+ [DD_EFD_RETRY_COUNT_MESSAGE]: {
135
+ pickleId: pickle.id,
136
+ retryCount,
137
+ testFileAbsolutePath: pickle.uri,
138
+ testName: pickle.name,
139
+ },
140
+ })
141
+ } catch {
142
+ // ignore IPC errors
143
+ }
144
+ }
145
+
146
+ function finishParallelSuiteIfDone (testFileAbsolutePath) {
147
+ const finished = pickleResultByFile[testFileAbsolutePath]
148
+ const expectedPickles = pickleByFile[testFileAbsolutePath]
149
+
150
+ if (!finished || !expectedPickles || finished.length !== expectedPickles.length) return
151
+ if (finishedParallelSuites.has(testFileAbsolutePath)) return
152
+
153
+ finishedParallelSuites.add(testFileAbsolutePath)
154
+ testSuiteFinishCh.publish({
155
+ status: getSuiteStatusFromTestStatuses(finished),
156
+ testSuitePath: getTestSuitePath(testFileAbsolutePath, process.cwd()),
157
+ })
158
+ }
159
+
160
+ function maybeRecordFinalParallelEfdStatus ({ pickleId, testFileAbsolutePath, testFullname }) {
161
+ const efdRetryCount = efdRetryCountByPickleId.get(pickleId)
162
+ const testStatuses = newTestsByTestFullname.get(testFullname)
163
+ const finished = pickleResultByFile[testFileAbsolutePath]
164
+
165
+ if (efdRetryCount === undefined || !testStatuses || !finished) return
166
+ if (testStatuses.length !== efdRetryCount + 1) return
167
+
168
+ finished.push(getTestStatusFromRetries(testStatuses))
169
+ newTestsByTestFullname.delete(testFullname)
170
+ finishParallelSuiteIfDone(testFileAbsolutePath)
171
+ }
172
+
173
+ function handleEfdRetryCountMessage (message) {
174
+ const { pickleId, retryCount, testFileAbsolutePath, testName } = message
175
+
176
+ if (!pickleId || typeof retryCount !== 'number' || !testFileAbsolutePath || !testName) return
177
+
178
+ efdRetryCountByPickleId.set(pickleId, retryCount)
179
+ maybeRecordFinalParallelEfdStatus({
180
+ pickleId,
181
+ testFileAbsolutePath,
182
+ testFullname: `${testFileAbsolutePath}:${testName}`,
183
+ })
184
+ }
185
+
120
186
  function getStatusFromResult (result) {
121
187
  if (result.status === 1) {
122
188
  return { status: 'pass' }
@@ -639,18 +705,31 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
639
705
  testManagementAttemptToFixRetries = configurationResponse.libraryConfig?.testManagementAttemptToFixRetries
640
706
  isImpactedTestsEnabled = configurationResponse.libraryConfig?.isImpactedTestsEnabled
641
707
 
708
+ const {
709
+ knownTestsResponse,
710
+ testManagementTestsResponse,
711
+ skippableSuitesResponse,
712
+ } = await getTestOptimizationRequestResults({
713
+ isKnownTestsEnabled,
714
+ isTestManagementTestsEnabled,
715
+ isSuitesSkippingEnabled,
716
+ getKnownTests: () => getChannelPromise(knownTestsCh),
717
+ getTestManagementTests: () => getChannelPromise(testManagementTestsCh),
718
+ getSkippableSuites: () => getChannelPromise(skippableSuitesCh),
719
+ })
720
+
642
721
  if (isKnownTestsEnabled) {
643
- const knownTestsResponse = await getChannelPromise(knownTestsCh)
644
- if (knownTestsResponse.err) {
722
+ const currentKnownTestsResponse = knownTestsResponse || await getChannelPromise(knownTestsCh)
723
+ if (currentKnownTestsResponse.err) {
645
724
  isEarlyFlakeDetectionEnabled = false
646
725
  isKnownTestsEnabled = false
647
726
  } else {
648
- knownTests = knownTestsResponse.knownTests
727
+ knownTests = currentKnownTestsResponse.knownTests
649
728
  }
650
729
  }
651
730
 
652
731
  if (isSuitesSkippingEnabled) {
653
- const skippableResponse = await getChannelPromise(skippableSuitesCh)
732
+ const skippableResponse = skippableSuitesResponse || await getChannelPromise(skippableSuitesCh)
654
733
 
655
734
  errorSkippableRequest = skippableResponse.err
656
735
  skippableSuites = skippableResponse.skippableSuites ?? []
@@ -694,11 +773,12 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
694
773
  }
695
774
 
696
775
  if (isTestManagementTestsEnabled) {
697
- const testManagementTestsResponse = await getChannelPromise(testManagementTestsCh)
698
- if (testManagementTestsResponse.err) {
776
+ const currentTestManagementTestsResponse =
777
+ testManagementTestsResponse || await getChannelPromise(testManagementTestsCh)
778
+ if (currentTestManagementTestsResponse.err) {
699
779
  isTestManagementTestsEnabled = false
700
780
  } else {
701
- testManagementTests = testManagementTestsResponse.testManagementTests
781
+ testManagementTests = currentTestManagementTestsResponse.testManagementTests
702
782
  }
703
783
  }
704
784
 
@@ -720,7 +800,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
720
800
  attemptToFixTestsByTestFullname.clear()
721
801
  efdRetryCountByPickleId.clear()
722
802
  efdSlowAbortedPickleIds.clear()
723
- testCaseStartedTimesById.clear()
803
+ finishedParallelSuites.clear()
724
804
  newTestsByTestFullname.clear()
725
805
  sessionStartCh.publish({ command, frameworkVersion })
726
806
 
@@ -875,6 +955,9 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
875
955
  efdSlowAbortedPickleIds.add(pickle.id)
876
956
  }
877
957
  }
958
+ if (isWorker) {
959
+ publishWorkerEfdRetryCount(pickle, efdRetryCount)
960
+ }
878
961
  for (let retryIndex = 0; retryIndex < efdRetryCount; retryIndex++) {
879
962
  numRetriesByPickleId.set(pickle.id, retryIndex + 1)
880
963
  // eslint-disable-next-line no-await-in-loop
@@ -973,6 +1056,10 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion)
973
1056
  return
974
1057
  }
975
1058
  }
1059
+ if (message[DD_EFD_RETRY_COUNT_MESSAGE]) {
1060
+ handleEfdRetryCountMessage(message[DD_EFD_RETRY_COUNT_MESSAGE])
1061
+ return
1062
+ }
976
1063
 
977
1064
  const envelope = isNewVersion ? message.envelope : message.jsonEnvelope
978
1065
 
@@ -989,12 +1076,13 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion)
989
1076
  return parseWorkerMessageFunction.apply(this, arguments)
990
1077
  }
991
1078
  }
1079
+ if (parsed[DD_EFD_RETRY_COUNT_MESSAGE]) {
1080
+ handleEfdRetryCountMessage(parsed[DD_EFD_RETRY_COUNT_MESSAGE])
1081
+ return
1082
+ }
992
1083
  let pickle
993
1084
 
994
1085
  if (parsed.testCaseStarted) {
995
- if (parsed.testCaseStarted.id) {
996
- testCaseStartedTimesById.set(parsed.testCaseStarted.id, performance.now())
997
- }
998
1086
  if (isNewVersion) {
999
1087
  pickle = this.inProgress[worker.id].pickle
1000
1088
  } else {
@@ -1016,10 +1104,6 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion)
1016
1104
 
1017
1105
  // after calling `parseWorkerMessageFunction`, the test status can already be read
1018
1106
  if (parsed.testCaseFinished) {
1019
- const testCaseStartedId = parsed.testCaseFinished.testCaseStartedId
1020
- const testCaseStartedAt = testCaseStartedTimesById.get(testCaseStartedId)
1021
- testCaseStartedTimesById.delete(testCaseStartedId)
1022
-
1023
1107
  let worstTestStepResult
1024
1108
  if (isNewVersion && eventDataCollector) {
1025
1109
  pickle = this.inProgress[worker.id].pickle
@@ -1052,21 +1136,15 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion)
1052
1136
  }
1053
1137
  let efdRetryCount = efdRetryCountByPickleId.get(pickle.id)
1054
1138
  if (efdRetryCount === undefined) {
1055
- const firstExecutionDurationMs = testCaseStartedAt === undefined ? 0 : performance.now() - testCaseStartedAt
1056
1139
  efdRetryCount = status === 'skip'
1057
1140
  ? 0
1058
- : getEfdRetryCount(firstExecutionDurationMs, earlyFlakeDetectionSlowTestRetries)
1141
+ : getConfiguredEfdRetryCount()
1059
1142
  efdRetryCountByPickleId.set(pickle.id, efdRetryCount)
1060
1143
  if (efdRetryCount === 0 && status !== 'skip') {
1061
1144
  efdSlowAbortedPickleIds.add(pickle.id)
1062
1145
  }
1063
1146
  }
1064
- // We have finished all retries
1065
- if (testStatuses.length === efdRetryCount + 1) {
1066
- const newTestFinalStatus = getTestStatusFromRetries(testStatuses)
1067
- // we only push to `finished` if the retries have finished
1068
- finished.push(newTestFinalStatus)
1069
- }
1147
+ maybeRecordFinalParallelEfdStatus({ pickleId: pickle.id, testFileAbsolutePath, testFullname })
1070
1148
  } else if (
1071
1149
  isTestManagementTestsEnabled &&
1072
1150
  getTestProperties(getTestSuitePath(testFileAbsolutePath, process.cwd()), pickle.name).attemptToFix
@@ -1090,12 +1168,7 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion)
1090
1168
  finished.push(status)
1091
1169
  }
1092
1170
 
1093
- if (finished.length === pickleByFile[testFileAbsolutePath].length) {
1094
- testSuiteFinishCh.publish({
1095
- status: getSuiteStatusFromTestStatuses(finished),
1096
- testSuitePath: getTestSuitePath(testFileAbsolutePath, process.cwd()),
1097
- })
1098
- }
1171
+ finishParallelSuiteIfDone(testFileAbsolutePath)
1099
1172
  }
1100
1173
 
1101
1174
  return parseWorkerResponse
@@ -51,9 +51,9 @@ function createWrapSelect () {
51
51
  const connectCh = channel('apm:elasticsearch:query:connect')
52
52
  return function wrapRequest (request) {
53
53
  return function (...args) {
54
- if (args.length === 1) {
55
- const cb = args[0]
56
- args[0] = shimmer.wrapFunction(cb, cb => function (err, connection) {
54
+ const cb = args[0]
55
+ if (args.length === 1 && typeof cb === 'function') {
56
+ args[0] = shimmer.wrapCallback(cb, cb => function (err, connection) {
57
57
  if (connectCh.hasSubscribers && connection && connection.host) {
58
58
  connectCh.publish({ hostname: connection.host.host, port: connection.host.port })
59
59
  }
@@ -85,7 +85,7 @@ function createWrapRequest (name) {
85
85
  cb = arguments[lastIndex]
86
86
 
87
87
  if (typeof cb === 'function') {
88
- arguments[lastIndex] = shimmer.wrapFunction(cb, cb => function (error) {
88
+ arguments[lastIndex] = shimmer.wrapCallback(cb, cb => function (error) {
89
89
  if (error) {
90
90
  ctx.error = error
91
91
  errorCh.publish(ctx)
@@ -343,11 +343,6 @@ addHook({ name: '@graphql-tools/executor', versions: ['>=0.0.14'] }, executor =>
343
343
  return executor
344
344
  })
345
345
 
346
- addHook({ name: '@graphql-tools/executor', file: 'cjs/execution/execute.js', versions: ['>=0.0.14'] }, execute => {
347
- shimmer.wrap(execute, 'execute', wrapExecute(execute))
348
- return execute
349
- })
350
-
351
346
  addHook({ name: 'graphql', file: 'execution/execute.js', versions: ['>=0.10'] }, execute => {
352
347
  shimmer.wrap(execute, 'execute', wrapExecute(execute))
353
348
  return execute
@@ -14,11 +14,14 @@ const finishChannel = channel('apm:grpc:client:request:finish')
14
14
  const emitChannel = channel('apm:grpc:client:request:emit')
15
15
 
16
16
  function createWrapMakeRequest (type, hasPeer = false) {
17
+ const metadataIndex = type === types.client_stream || type === types.bidi ? 3 : 4
18
+
17
19
  return function wrapMakeRequest (makeRequest) {
18
20
  return function (path) {
19
- const args = ensureMetadata(this, arguments, 4)
21
+ if (!startChannel.hasSubscribers) return makeRequest.apply(this, arguments)
20
22
 
21
- return callMethod(this, makeRequest, args, path, args[4], type, hasPeer)
23
+ const { metadata, args } = resolveMetadata(this, arguments, metadataIndex)
24
+ return callMethod(this, makeRequest, args, path, metadata, type, hasPeer)
22
25
  }
23
26
  }
24
27
  }
@@ -82,9 +85,13 @@ function wrapMethod (method, path, type, hasPeer) {
82
85
  return method
83
86
  }
84
87
 
88
+ const metadataIndex = type === types.client_stream || type === types.bidi ? 0 : 1
89
+
85
90
  const wrapped = shimmer.wrapFunction(method, method => function () {
86
- const args = ensureMetadata(this, arguments, 1)
87
- return callMethod(this, method, args, path, args[1], type, hasPeer)
91
+ if (!startChannel.hasSubscribers) return method.apply(this, arguments)
92
+
93
+ const { metadata, args } = resolveMetadata(this, arguments, metadataIndex)
94
+ return callMethod(this, method, args, path, metadata, type, hasPeer)
88
95
  })
89
96
 
90
97
  patched.add(wrapped)
@@ -140,24 +147,25 @@ function createWrapEmit (ctx, hasPeer = false) {
140
147
  }
141
148
 
142
149
  function callMethod (client, method, args, path, metadata, type, hasPeer = false) {
143
- if (!startChannel.hasSubscribers) return method.apply(client, args)
144
-
145
- const length = args.length
146
- const callback = args[length - 1]
147
-
148
150
  const ctx = { metadata, path, type }
149
151
 
150
152
  return startChannel.runStores(ctx, () => {
151
153
  try {
154
+ let callArgs = args
155
+
152
156
  if (type === types.unary || type === types.client_stream) {
157
+ if (!Array.isArray(callArgs)) callArgs = [...callArgs]
158
+
159
+ const length = callArgs.length
160
+ const callback = callArgs[length - 1]
153
161
  if (typeof callback === 'function') {
154
- args[length - 1] = wrapCallback(ctx, callback)
162
+ callArgs[length - 1] = wrapCallback(ctx, callback)
155
163
  } else {
156
- args[length] = wrapCallback(ctx)
164
+ callArgs[length] = wrapCallback(ctx)
157
165
  }
158
166
  }
159
167
 
160
- const call = method.apply(client, args)
168
+ const call = method.apply(client, callArgs)
161
169
 
162
170
  if (call && typeof call.emit === 'function') {
163
171
  shimmer.wrap(call, 'emit', createWrapEmit(ctx, hasPeer))
@@ -167,36 +175,44 @@ function callMethod (client, method, args, path, metadata, type, hasPeer = false
167
175
  } catch (e) {
168
176
  ctx.error = e
169
177
  errorChannel.publish(ctx)
178
+ throw e
170
179
  }
171
180
  // No end channel needed
172
181
  })
173
182
  }
174
183
 
175
- function ensureMetadata (client, args, index) {
176
- const grpc = getGrpc(client)
177
-
178
- if (!client || !grpc) return args
179
-
180
- const meta = args[index]
181
- const normalized = []
182
-
183
- for (let i = 0; i < index; i++) {
184
- normalized.push(args[i])
184
+ /**
185
+ * Returns the `Metadata` for a gRPC client invocation, splicing or replacing
186
+ * one at `index` when the user did not pass their own.
187
+ *
188
+ * @param {object} client
189
+ * @param {ArrayLike<unknown>} args
190
+ * @param {number} index
191
+ * @returns {{ metadata: object | undefined, args: ArrayLike<unknown> }}
192
+ */
193
+ function resolveMetadata (client, args, index) {
194
+ const grpc = client && getGrpc(client)
195
+ if (!grpc) return { metadata: undefined, args }
196
+
197
+ const slot = args[index]
198
+
199
+ if (slot instanceof grpc.Metadata || slot?.constructor?.name === 'Metadata') {
200
+ return { metadata: slot, args }
185
201
  }
186
202
 
187
- if (!meta || !meta.constructor || meta.constructor.name !== 'Metadata') {
188
- normalized.push(new grpc.Metadata())
189
- }
190
-
191
- if (meta) {
192
- normalized.push(meta)
193
- }
203
+ const metadata = new grpc.Metadata()
194
204
 
195
- for (let i = index + 1; i < args.length; i++) {
196
- normalized.push(args[i])
205
+ if (slot == null) {
206
+ const out = [...args]
207
+ out[index] = metadata
208
+ return { metadata, args: out }
197
209
  }
198
210
 
199
- return normalized
211
+ const out = new Array(args.length + 1)
212
+ for (let i = 0; i < index; i++) out[i] = args[i]
213
+ out[index] = metadata
214
+ for (let i = index; i < args.length; i++) out[i + 1] = args[i]
215
+ return { metadata, args: out }
200
216
  }
201
217
 
202
218
  function getType (definition) {
@@ -45,7 +45,7 @@ function createCallbackInstrumentor (prefix, { captureResult = false } = {}) {
45
45
  }
46
46
 
47
47
  return startCh.runStores(ctx, () => {
48
- args[lastIndex] = shimmer.wrapFunction(cb, cb => function (error, ...rest) {
48
+ args[lastIndex] = shimmer.wrapCallback(cb, cb => function (error, ...rest) {
49
49
  if (error) {
50
50
  ctx.error = error
51
51
  errorCh.publish(ctx)
@@ -1,5 +1,10 @@
1
1
  'use strict'
2
2
 
3
+ // Produce API key 0; v0–v2 use the legacy MessageSet format with no header
4
+ // field, so trace headers can only be carried on v3+ (Kafka >=0.11).
5
+ const PRODUCE_API_KEY = 0
6
+ const PRODUCE_VERSION_WITH_HEADERS = 3
7
+
3
8
  // Side-table mapping a kafkajs producer/consumer to the cluster captured at
4
9
  // creation time. The boundary uses it to read `cluster.brokerPool` lazily on
5
10
  // first send/consume instead of opening a parallel admin connection. A
@@ -35,7 +40,19 @@ function cloneMessages (messages, ensureHeaders) {
35
40
  return result
36
41
  }
37
42
 
43
+ /**
44
+ * @param {{ versions?: Record<number, { minVersion: number, maxVersion: number }> } | undefined} brokerPool
45
+ * kafkajs's `cluster.brokerPool`. `versions` is populated once the seed
46
+ * broker handshakes; before that, the answer is unknown and we return
47
+ * `true` so the caller defaults to injection.
48
+ */
49
+ function brokerSupportsMessageHeaders (brokerPool) {
50
+ const produce = brokerPool?.versions?.[PRODUCE_API_KEY]
51
+ return !produce || produce.maxVersion >= PRODUCE_VERSION_WITH_HEADERS
52
+ }
53
+
38
54
  module.exports = {
55
+ brokerSupportsMessageHeaders,
39
56
  clientToCluster,
40
57
  cloneMessages,
41
58
  }
@@ -19,9 +19,10 @@ const compiler = {
19
19
  // TODO: Figure out ESBuild `createRequire` issue and remove this hack.
20
20
  const oxc = runtimeRequire(['oxc', 'parser'].join('-'))
21
21
 
22
- compiler.parse = (sourceText, options) => {
22
+ compiler.parse = (sourceText, { range, isModule } = {}) => {
23
23
  const { program, errors } = oxc.parseSync('index.js', sourceText, {
24
- ...options,
24
+ range,
25
+ sourceType: isModule ? 'module' : 'script',
25
26
  preserveParens: false,
26
27
  })
27
28
 
@@ -2,15 +2,25 @@
2
2
 
3
3
  const { readFileSync } = require('fs')
4
4
  const { join } = require('path')
5
+ const { pathToFileURL } = require('url')
5
6
  const log = require('../../../../dd-trace/src/log')
6
7
  const { create } = require('../../../../../vendor/dist/@apm-js-collab/code-transformer')
7
8
  const { traceAsyncIterator, traceIterator } = require('./transforms')
8
9
  const instrumentations = require('./instrumentations')
9
10
 
10
- let dcPolyfill
11
+ // `dc-polyfill` is referenced from injected `require()` (CJS) and `import`
12
+ // (ESM) statements that the transformer splices into the rewritten module.
13
+ // `require()` accepts an absolute filesystem path; the ESM resolver rejects it
14
+ // with `ERR_INVALID_MODULE_SPECIFIER` and needs a `file://` URL instead. We
15
+ // pre-compute both forms here so each matcher hands the transformer a
16
+ // specifier that is valid for the module type it is rewriting.
17
+ let dcPolyfillCjs
18
+ let dcPolyfillEsm
11
19
 
12
20
  try {
13
- dcPolyfill = require.resolve('dc-polyfill').replaceAll('\\', '/')
21
+ const resolved = require.resolve('dc-polyfill')
22
+ dcPolyfillCjs = resolved.replaceAll('\\', '/')
23
+ dcPolyfillEsm = pathToFileURL(resolved).href
14
24
  } catch {
15
25
  // The `dc-polyfill` module is unavailable for some reason (like bundling).
16
26
  // Let's just keep the default of using `diagnostics-channel` as a fallback
@@ -20,10 +30,13 @@ try {
20
30
  /** @type {Record<string, string>} map of module base name to version */
21
31
  const moduleVersions = {}
22
32
  const disabled = new Set()
23
- const matcher = create(instrumentations, dcPolyfill)
33
+ const matcherCjs = create(instrumentations, dcPolyfillCjs)
34
+ const matcherEsm = create(instrumentations, dcPolyfillEsm)
24
35
 
25
- matcher.addTransform('traceIterator', traceIterator)
26
- matcher.addTransform('traceAsyncIterator', traceAsyncIterator)
36
+ for (const matcher of [matcherCjs, matcherEsm]) {
37
+ matcher.addTransform('traceIterator', traceIterator)
38
+ matcher.addTransform('traceAsyncIterator', traceAsyncIterator)
39
+ }
27
40
 
28
41
  function rewrite (content, filename, format) {
29
42
  if (!content) return content
@@ -41,6 +54,7 @@ function rewrite (content, filename, format) {
41
54
 
42
55
  if (disabled.has(moduleName)) return content
43
56
 
57
+ const matcher = moduleType === 'esm' ? matcherEsm : matcherCjs
44
58
  const transformer = matcher.getTransformer(moduleName, version, filePath)
45
59
 
46
60
  if (!transformer) return content