dd-trace 5.110.0 → 5.112.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 (164) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/README.md +13 -8
  3. package/ci/init.js +21 -2
  4. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  5. package/ext/tags.js +2 -0
  6. package/index.d.ts +34 -1
  7. package/initialize.mjs +5 -6
  8. package/loader-hook.mjs +76 -55
  9. package/package.json +37 -34
  10. package/packages/datadog-instrumentations/src/ai.js +45 -0
  11. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  12. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  13. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  14. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  15. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  16. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  17. package/packages/datadog-instrumentations/src/fs.js +8 -6
  18. package/packages/datadog-instrumentations/src/graphql.js +26 -495
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  25. package/packages/datadog-instrumentations/src/jest.js +713 -63
  26. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  27. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  28. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  29. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  30. package/packages/datadog-instrumentations/src/mongoose.js +13 -2
  31. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  32. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1297 -0
  33. package/packages/datadog-instrumentations/src/vitest-main.js +1594 -0
  34. package/packages/datadog-instrumentations/src/vitest-util.js +254 -0
  35. package/packages/datadog-instrumentations/src/vitest-worker.js +823 -0
  36. package/packages/datadog-instrumentations/src/vitest.js +11 -1855
  37. package/packages/datadog-plugin-ai/src/index.js +1 -1
  38. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  39. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  40. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +19 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  43. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  44. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  45. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  46. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  47. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  48. package/packages/datadog-plugin-cucumber/src/index.js +15 -24
  49. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +360 -14
  50. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  51. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  52. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  53. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  54. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  55. package/packages/datadog-plugin-graphql/src/execute.js +639 -12
  56. package/packages/datadog-plugin-graphql/src/index.js +37 -9
  57. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  58. package/packages/datadog-plugin-graphql/src/utils.js +9 -2
  59. package/packages/datadog-plugin-graphql/src/validate.js +17 -6
  60. package/packages/datadog-plugin-http/src/index.js +6 -8
  61. package/packages/datadog-plugin-jest/src/index.js +31 -15
  62. package/packages/datadog-plugin-mocha/src/index.js +40 -15
  63. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  64. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  65. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  66. package/packages/datadog-plugin-openai/src/services.js +2 -2
  67. package/packages/datadog-plugin-playwright/src/index.js +4 -3
  68. package/packages/datadog-plugin-vitest/src/index.js +120 -72
  69. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  70. package/packages/dd-trace/src/aiguard/index.js +9 -14
  71. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  72. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  73. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  74. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  75. package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
  76. package/packages/dd-trace/src/appsec/channels.js +3 -1
  77. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  78. package/packages/dd-trace/src/appsec/graphql.js +14 -11
  79. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +23 -23
  80. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  81. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  82. package/packages/dd-trace/src/appsec/index.js +10 -1
  83. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  84. package/packages/dd-trace/src/appsec/reporter.js +49 -10
  85. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
  86. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  87. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  88. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  89. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  90. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  91. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +5 -2
  92. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  93. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  94. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +6 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  96. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +130 -20
  97. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  98. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  99. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  100. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  101. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  102. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  103. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  104. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  105. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  106. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  107. package/packages/dd-trace/src/config/defaults.js +3 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +71 -57
  109. package/packages/dd-trace/src/config/helper.js +1 -0
  110. package/packages/dd-trace/src/config/index.js +35 -22
  111. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  112. package/packages/dd-trace/src/config/remote_config.js +1 -1
  113. package/packages/dd-trace/src/config/supported-configurations.json +127 -56
  114. package/packages/dd-trace/src/constants.js +7 -0
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  116. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  117. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  118. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  119. package/packages/dd-trace/src/llmobs/index.js +1 -1
  120. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  121. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  122. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  123. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  124. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  125. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  126. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  127. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  128. package/packages/dd-trace/src/llmobs/sdk.js +3 -3
  129. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  130. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  131. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  132. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  133. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  135. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
  136. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  137. package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
  138. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  139. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  140. package/packages/dd-trace/src/plugin_manager.js +23 -7
  141. package/packages/dd-trace/src/plugins/ci_plugin.js +159 -10
  142. package/packages/dd-trace/src/plugins/index.js +2 -0
  143. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  144. package/packages/dd-trace/src/plugins/util/test.js +15 -9
  145. package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
  146. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  147. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
  148. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  149. package/packages/dd-trace/src/proxy.js +8 -8
  150. package/packages/dd-trace/src/span_processor.js +5 -6
  151. package/packages/dd-trace/src/span_stats.js +96 -78
  152. package/packages/dd-trace/src/startup-log.js +2 -1
  153. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  154. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  155. package/packages/dd-trace/src/telemetry/index.js +2 -2
  156. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  157. package/packages/dd-trace/src/telemetry/send-data.js +8 -8
  158. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  159. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  160. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  161. package/vendor/dist/protobufjs/index.js +1 -1
  162. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  163. package/vendor/dist/shell-quote/index.js +1 -1
  164. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
@@ -1,5 +1,11 @@
1
1
  'use strict'
2
2
 
3
+ // Capture real timers at module load time, before any test can install fake timers.
4
+ const realSetTimeout = setTimeout
5
+ const realClearTimeout = clearTimeout
6
+
7
+ const { threadId } = require('node:worker_threads')
8
+
3
9
  const { storage } = require('../../../datadog-core')
4
10
  const { COMPONENT } = require('../constants')
5
11
  const log = require('../log')
@@ -12,6 +18,7 @@ const {
12
18
  } = require('../ci-visibility/telemetry')
13
19
  const getDiClient = require('../ci-visibility/dynamic-instrumentation')
14
20
  const { DD_MAJOR } = require('../../../../version')
21
+ const { version: tracerVersion } = require('../../../../package.json')
15
22
  const id = require('../id')
16
23
  const { OS_VERSION, OS_PLATFORM, OS_ARCHITECTURE, RUNTIME_NAME, RUNTIME_VERSION } = require('./util/env')
17
24
  const {
@@ -61,7 +68,7 @@ const {
61
68
  DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX,
62
69
  DI_DEBUG_ERROR_FILE_SUFFIX,
63
70
  DI_DEBUG_ERROR_LINE_SUFFIX,
64
- getLibraryCapabilitiesTags,
71
+ getLibraryCapabilitiesTags: getDefaultLibraryCapabilitiesTags,
65
72
  getPullRequestDiff,
66
73
  getModifiedFilesFromDiff,
67
74
  getPullRequestBaseBranch,
@@ -84,6 +91,9 @@ const {
84
91
  } = require('./util/test')
85
92
 
86
93
  const legacyStorage = storage('legacy')
94
+ const DI_OPERATION_TIMEOUT_MS = 2000
95
+ const DI_LOGGER_THREAD_ID = threadId === 0 ? `pid:${process.pid}` : `pid:${process.pid};tid:${threadId}`
96
+ const DI_LOGGER_THREAD_NAME = threadId === 0 ? 'MainThread' : `WorkerThread:${threadId}`
87
97
 
88
98
  const FRAMEWORK_TO_TRIMMED_COMMAND = {
89
99
  vitest: 'vitest run',
@@ -108,8 +118,20 @@ const TEST_FRAMEWORKS_TO_SKIP_GIT_METADATA_EXTRACTION = new Set([
108
118
  'cucumber',
109
119
  ])
110
120
 
121
+ function withTimeout (promise, timeoutMs) {
122
+ return new Promise(resolve => {
123
+ const timeoutId = realSetTimeout(resolve, timeoutMs)
124
+
125
+ const done = () => {
126
+ realClearTimeout(timeoutId)
127
+ resolve()
128
+ }
129
+ promise.then(done, done)
130
+ })
131
+ }
132
+
111
133
  function setItrSkippingEnabledTagFromLibraryConfig (plugin, frameworkVersion) {
112
- const libraryCapabilitiesTags = getLibraryCapabilitiesTags(plugin.constructor.id, frameworkVersion)
134
+ const libraryCapabilitiesTags = getDefaultLibraryCapabilitiesTags(plugin.constructor.id, frameworkVersion)
113
135
 
114
136
  if (!libraryCapabilitiesTags[DD_CAPABILITIES_TEST_IMPACT_ANALYSIS] ||
115
137
  !plugin.libraryConfig ||
@@ -143,6 +165,8 @@ module.exports = class CiPlugin extends Plugin {
143
165
  super(...args)
144
166
 
145
167
  this.fileLineToProbeId = new Map()
168
+ this.diBreakpointHitPromise = undefined
169
+ this.diBreakpointHitResolvers = []
146
170
  this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
147
171
  this._testSuiteSpansByTestSuite = new Map()
148
172
  this._pendingWorkerTracesByTestSuite = new Map()
@@ -164,18 +188,20 @@ module.exports = class CiPlugin extends Plugin {
164
188
  setItrSkippingEnabledTagFromLibraryConfig(this, frameworkVersion)
165
189
  }
166
190
 
167
- const requestErrorTags = this.testSessionSpan
168
- ? getSessionRequestErrorTags(this.testSessionSpan)
169
- : Object.fromEntries(this._pendingRequestErrorTags.map(({ tag, value }) => [tag, value]))
170
-
171
- const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, frameworkVersion)
191
+ const libraryCapabilitiesTags = this.getLibraryCapabilitiesTags(frameworkVersion, ctx)
172
192
  const metadataTags = {
173
193
  test: {
174
194
  ...libraryCapabilitiesTags,
175
195
  },
176
196
  }
177
197
  this.tracer._exporter.addMetadataTags(metadataTags)
178
- onDone({ err, libraryConfig, repositoryRoot: this.repositoryRoot, requestErrorTags })
198
+ onDone({
199
+ err,
200
+ isTestDynamicInstrumentationEnabled: this.config.isTestDynamicInstrumentationEnabled,
201
+ libraryConfig,
202
+ repositoryRoot: this.repositoryRoot,
203
+ requestErrorTags: this._getCurrentRequestErrorTags(),
204
+ })
179
205
  })
180
206
  })
181
207
 
@@ -307,7 +333,7 @@ module.exports = class CiPlugin extends Plugin {
307
333
  this.libraryConfig.isKnownTestsEnabled = false
308
334
  }
309
335
  }
310
- onDone({ err, knownTests })
336
+ onDone({ err, knownTests, requestErrorTags: this._getCurrentRequestErrorTags() })
311
337
  })
312
338
  })
313
339
 
@@ -327,7 +353,7 @@ module.exports = class CiPlugin extends Plugin {
327
353
  this.libraryConfig.isTestManagementEnabled = false
328
354
  }
329
355
  }
330
- onDone({ err, testManagementTests })
356
+ onDone({ err, testManagementTests, requestErrorTags: this._getCurrentRequestErrorTags() })
331
357
  })
332
358
  })
333
359
 
@@ -431,6 +457,15 @@ module.exports = class CiPlugin extends Plugin {
431
457
  }
432
458
  }
433
459
 
460
+ /**
461
+ * Returns library capability metadata tags for this test framework.
462
+ * @param {string} frameworkVersion - The test framework version.
463
+ * @returns {Record<string, string|undefined>}
464
+ */
465
+ getLibraryCapabilitiesTags (frameworkVersion) {
466
+ return getDefaultLibraryCapabilitiesTags(this.constructor.id, frameworkVersion)
467
+ }
468
+
434
469
  /**
435
470
  * Adds a hidden _dd tag to the test session span when a test-optimization request fails.
436
471
  * If the session span does not exist yet (e.g. library-configuration failed before session:start),
@@ -450,6 +485,18 @@ module.exports = class CiPlugin extends Plugin {
450
485
  }
451
486
  }
452
487
 
488
+ /**
489
+ * Returns the current request error tags, including tags queued before session creation.
490
+ *
491
+ * @returns {Record<string, string>}
492
+ */
493
+ _getCurrentRequestErrorTags () {
494
+ if (this.testSessionSpan) {
495
+ return getSessionRequestErrorTags(this.testSessionSpan)
496
+ }
497
+ return Object.fromEntries(this._pendingRequestErrorTags.map(({ tag, value }) => [tag, value]))
498
+ }
499
+
453
500
  /**
454
501
  * Updates repository-root-dependent state when a worker receives the root from
455
502
  * the coordinator process after plugin configuration.
@@ -767,6 +814,11 @@ module.exports = class CiPlugin extends Plugin {
767
814
  }
768
815
 
769
816
  onDiBreakpointHit ({ snapshot }) {
817
+ for (const resolve of this.diBreakpointHitResolvers) {
818
+ resolve()
819
+ }
820
+ this.diBreakpointHitResolvers.length = 0
821
+
770
822
  if (!this.activeTestSpan || this.activeTestSpan.context()._isFinished) {
771
823
  // This is unexpected and is caused by a race condition.
772
824
  log.warn('Breakpoint snapshot could not be attached to the active test span')
@@ -790,8 +842,17 @@ module.exports = class CiPlugin extends Plugin {
790
842
  )
791
843
 
792
844
  const activeTestSpanContext = this.activeTestSpan.context()
845
+ const topStackFrame = snapshot.stack?.[0]
793
846
 
794
847
  this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, {
848
+ message: '',
849
+ logger: {
850
+ name: snapshot.probe.location.file,
851
+ method: topStackFrame?.function || '',
852
+ version: tracerVersion,
853
+ thread_id: DI_LOGGER_THREAD_ID,
854
+ thread_name: DI_LOGGER_THREAD_NAME,
855
+ },
795
856
  debugger: { snapshot },
796
857
  dd: {
797
858
  trace_id: activeTestSpanContext.toTraceId(),
@@ -800,6 +861,94 @@ module.exports = class CiPlugin extends Plugin {
800
861
  })
801
862
  }
802
863
 
864
+ /**
865
+ * Wait for a Dynamic Instrumentation operation without blocking test framework progress forever.
866
+ *
867
+ * @param {Promise<void>} promise - Dynamic Instrumentation operation promise.
868
+ * @returns {Promise<void>}
869
+ */
870
+ waitForDiOperation (promise) {
871
+ return withTimeout(promise, DI_OPERATION_TIMEOUT_MS)
872
+ }
873
+
874
+ /**
875
+ * Resolve any prepared breakpoint-hit wait when no caller still needs it.
876
+ */
877
+ cancelDiBreakpointHitWait () {
878
+ for (const resolve of this.diBreakpointHitResolvers) {
879
+ resolve()
880
+ }
881
+ this.diBreakpointHitResolvers.length = 0
882
+ }
883
+
884
+ /**
885
+ * Wait for a prepared breakpoint hit before resolving any unused waiters.
886
+ *
887
+ * @returns {Promise<void>}
888
+ */
889
+ waitForPreparedDiBreakpointHit () {
890
+ if (!this.diBreakpointHitPromise) {
891
+ this.cancelDiBreakpointHitWait()
892
+ return Promise.resolve()
893
+ }
894
+
895
+ return this.waitForDiOperation(this.diBreakpointHitPromise).then(
896
+ () => this.cancelDiBreakpointHitWait(),
897
+ () => this.cancelDiBreakpointHitWait()
898
+ )
899
+ }
900
+
901
+ /**
902
+ * Prepare a wait for the next breakpoint hit before the retried test starts.
903
+ *
904
+ * @returns {Promise<void>}
905
+ */
906
+ prepareDiBreakpointHitWait () {
907
+ if (!this.di) return Promise.resolve()
908
+
909
+ let resolveHit
910
+ const hitPromise = new Promise(resolve => {
911
+ resolveHit = resolve
912
+ this.diBreakpointHitResolvers.push(resolve)
913
+ })
914
+
915
+ const preparedPromise = hitPromise.finally(() => {
916
+ const resolverIndex = this.diBreakpointHitResolvers.indexOf(resolveHit)
917
+ if (resolverIndex !== -1) {
918
+ this.diBreakpointHitResolvers.splice(resolverIndex, 1)
919
+ }
920
+ if (this.diBreakpointHitPromise === preparedPromise) {
921
+ this.diBreakpointHitPromise = undefined
922
+ }
923
+ })
924
+
925
+ this.diBreakpointHitPromise = preparedPromise
926
+ return this.diBreakpointHitPromise
927
+ }
928
+
929
+ /**
930
+ * Wait until the DI worker has posted any breakpoint hits it was already processing.
931
+ *
932
+ * @returns {Promise<void>}
933
+ */
934
+ waitForDiBreakpointHits () {
935
+ if (!this.di) return Promise.resolve()
936
+ if (this.diBreakpointHitPromise) return this.waitForDiOperation(this.diBreakpointHitPromise)
937
+
938
+ return this.waitForInFlightDiBreakpointHits()
939
+ }
940
+
941
+ /**
942
+ * Wait until the DI worker has posted breakpoint hits it was already processing.
943
+ *
944
+ * @returns {Promise<void>}
945
+ */
946
+ waitForInFlightDiBreakpointHits () {
947
+ if (!this.di) return Promise.resolve()
948
+
949
+ return this.waitForDiOperation(this.di.waitForInFlightBreakpointHits())
950
+ }
951
+
803
952
  removeAllDiProbes () {
804
953
  if (this.fileLineToProbeId.size === 0) {
805
954
  return Promise.resolve()
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const plugins = {
4
+ get '@anthropic-ai/claude-agent-sdk' () { return require('../../../datadog-plugin-claude-agent-sdk/src') },
4
5
  get '@anthropic-ai/sdk' () { return require('../../../datadog-plugin-anthropic/src') },
5
6
  get '@apollo/gateway' () { return require('../../../datadog-plugin-apollo/src') },
6
7
  get '@aws-sdk/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
@@ -46,6 +47,7 @@ const plugins = {
46
47
  get bunyan () { return require('../../../datadog-plugin-bunyan/src') },
47
48
  get 'cassandra-driver' () { return require('../../../datadog-plugin-cassandra-driver/src') },
48
49
  get child_process () { return require('../../../datadog-plugin-child_process/src') },
50
+ get 'claude-agent-sdk' () { return require('../../../datadog-plugin-claude-agent-sdk/src') },
49
51
  get connect () { return require('../../../datadog-plugin-connect/src') },
50
52
  get couchbase () { return require('../../../datadog-plugin-couchbase/src') },
51
53
  get cypress () { return require('../../../datadog-plugin-cypress/src') },
@@ -37,7 +37,12 @@ function isPromptCompletionSampled (sampler, span) {
37
37
 
38
38
  module.exports = function makeUtilities (integrationName, tracerConfig) {
39
39
  const integrationConfig = tracerConfig[integrationName] || {}
40
- const { spanCharLimit, spanPromptCompletionSampleRate } = integrationConfig
40
+ // The per-integration config is keyed by the canonical environment variable
41
+ // names (e.g. `DD_LANGCHAIN_SPAN_CHAR_LIMIT`), nested under the integration's
42
+ // namespace. Derive those leaf names from the integration name.
43
+ const prefix = `DD_${integrationName.toUpperCase()}_`
44
+ const spanCharLimit = integrationConfig[`${prefix}SPAN_CHAR_LIMIT`]
45
+ const spanPromptCompletionSampleRate = integrationConfig[`${prefix}SPAN_PROMPT_COMPLETION_SAMPLE_RATE`]
41
46
 
42
47
  const sampler = new Sampler(spanPromptCompletionSampleRate ?? 1)
43
48
 
@@ -95,6 +95,8 @@ const TEST_IS_RUM_ACTIVE = 'test.is_rum_active'
95
95
  const TEST_CODE_OWNERS = 'test.codeowners'
96
96
  const TEST_SOURCE_FILE = 'test.source.file'
97
97
  const TEST_SOURCE_START = 'test.source.start'
98
+ const TEST_FAILURE_SCREENSHOT_UPLOADED = 'test.failure_screenshot.uploaded'
99
+ const TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR = 'test.failure_screenshot.upload_error'
98
100
  const LIBRARY_VERSION = 'library_version'
99
101
  const TEST_COMMAND = 'test.command'
100
102
  const TEST_MODULE = 'test.module'
@@ -112,6 +114,12 @@ const TEST_RETRY_REASON = 'test.retry_reason'
112
114
  const TEST_HAS_FAILED_ALL_RETRIES = 'test.has_failed_all_retries'
113
115
  const TEST_IS_MODIFIED = 'test.is_modified'
114
116
  const TEST_HAS_DYNAMIC_NAME = '_dd.has_dynamic_name'
117
+ const EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS = [
118
+ { limitMs: 5 * 1000, key: '5s' },
119
+ { limitMs: 10 * 1000, key: '10s' },
120
+ { limitMs: 30 * 1000, key: '30s' },
121
+ { limitMs: 5 * 60 * 1000, key: '5m' },
122
+ ]
115
123
  const CI_APP_ORIGIN = 'ciapp-test'
116
124
 
117
125
  // Matches patterns that are almost certainly runtime-generated values in test names:
@@ -427,6 +435,8 @@ module.exports = {
427
435
  TEST_SKIP_REASON,
428
436
  TEST_IS_RUM_ACTIVE,
429
437
  TEST_SOURCE_FILE,
438
+ TEST_FAILURE_SCREENSHOT_UPLOADED,
439
+ TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR,
430
440
  CI_APP_ORIGIN,
431
441
  LIBRARY_VERSION,
432
442
  JEST_WORKER_TRACE_PAYLOAD_CODE,
@@ -493,6 +503,7 @@ module.exports = {
493
503
  removeInvalidMetadata,
494
504
  parseAnnotations,
495
505
  getIsFaultyEarlyFlakeDetection,
506
+ EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS,
496
507
  getEfdRetryCount,
497
508
  getMaxEfdRetryCount,
498
509
  TEST_BROWSER_DRIVER,
@@ -1447,13 +1458,7 @@ function parseAnnotations (annotations) {
1447
1458
  * @returns {number}
1448
1459
  */
1449
1460
  function getEfdRetryCount (durationMs, slowTestRetries) {
1450
- const thresholds = [
1451
- { limitMs: 5 * 1000, key: '5s' },
1452
- { limitMs: 10 * 1000, key: '10s' },
1453
- { limitMs: 30 * 1000, key: '30s' },
1454
- { limitMs: 5 * 60 * 1000, key: '5m' },
1455
- ]
1456
- for (const { limitMs, key } of thresholds) {
1461
+ for (const { limitMs, key } of EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS) {
1457
1462
  if (durationMs < limitMs) {
1458
1463
  return slowTestRetries[key] ?? 0
1459
1464
  }
@@ -1616,7 +1621,7 @@ function isFailedTestReplaySupported (testFramework, frameworkVersion) {
1616
1621
  : true
1617
1622
  }
1618
1623
 
1619
- function getLibraryCapabilitiesTags (testFramework, frameworkVersion) {
1624
+ function getLibraryCapabilitiesTags (testFramework, frameworkVersion, options = {}) {
1620
1625
  return {
1621
1626
  [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework)
1622
1627
  ? '1'
@@ -1638,7 +1643,8 @@ function getLibraryCapabilitiesTags (testFramework, frameworkVersion) {
1638
1643
  isAttemptToFixSupported(testFramework, frameworkVersion)
1639
1644
  ? '5'
1640
1645
  : undefined,
1641
- [DD_CAPABILITIES_FAILED_TEST_REPLAY]: isFailedTestReplaySupported(testFramework, frameworkVersion)
1646
+ [DD_CAPABILITIES_FAILED_TEST_REPLAY]: !options.omitFailedTestReplay &&
1647
+ isFailedTestReplaySupported(testFramework, frameworkVersion)
1642
1648
  ? '1'
1643
1649
  : undefined,
1644
1650
  }
@@ -18,7 +18,7 @@ function exporterFromURL (url) {
18
18
  return new FileExporter({}, fileURLToPath(url))
19
19
  }
20
20
  // The subprocess has no tracer config, so mirror the canonical config property
21
- // names the exporters read. Normalize the raw env to the parsed profiling.enabled
21
+ // names the exporters read. Normalize the raw env to the parsed profiling.DD_PROFILING_ENABLED
22
22
  // values ('true' | 'false' | 'auto') the main process would have produced.
23
23
  const profilingEnabled = (getValueFromEnvSources('DD_PROFILING_ENABLED') ?? '').toLowerCase()
24
24
  const enabled = profilingEnabled === 'auto'
@@ -28,7 +28,7 @@ function exporterFromURL (url) {
28
28
  url,
29
29
  DD_PROFILING_UPLOAD_TIMEOUT: timeoutMs,
30
30
  DD_INJECTION_ENABLED: getValueFromEnvSources('DD_INJECTION_ENABLED'),
31
- profiling: { enabled },
31
+ profiling: { DD_PROFILING_ENABLED: enabled },
32
32
  })
33
33
  }
34
34
 
@@ -42,6 +42,19 @@ function sendRequest (options, form, callback) {
42
42
  storage('legacy').run({ noop: true }, () => {
43
43
  requestCounter.inc()
44
44
  const start = perf.now()
45
+
46
+ // Ensure the callback runs at most once per request. The 'timeout' handler
47
+ // below can fire after a response was already received (e.g. on a lingering
48
+ // keep-alive socket, once it goes idle); without this guard that would
49
+ // invoke the callback a second time and corrupt the retry accounting in
50
+ // AgentExporter.export.
51
+ let settled = false
52
+ const done = (err, res) => {
53
+ if (settled) return
54
+ settled = true
55
+ callback(err, res)
56
+ }
57
+
45
58
  const req = request(options, res => {
46
59
  durationDistribution.track(perf.now() - start)
47
60
  countStatusCode(res.statusCode)
@@ -49,16 +62,28 @@ function sendRequest (options, form, callback) {
49
62
  statusCodeErrorCounter.inc()
50
63
  const error = new Error(`HTTP Error ${res.statusCode}`)
51
64
  error.status = res.statusCode
52
- callback(error)
65
+ done(error)
53
66
  } else {
54
- callback(null, res)
67
+ done(null, res)
55
68
  }
56
69
  })
57
70
 
58
71
  req.on('error', (err) => {
59
72
  networkErrorCounter.inc()
60
- callback(err)
73
+ done(err)
61
74
  })
75
+
76
+ // `options.timeout` sets the socket's idle timeout, which only emits a
77
+ // 'timeout' event — per the Node docs it does NOT abort the request. Without
78
+ // destroying the socket here, a stalled upload hangs indefinitely: no
79
+ // 'error' fires, so the retry/backoff in AgentExporter.export never runs and
80
+ // the export promise never settles. Destroying the request makes the
81
+ // 'error' handler above run, which drives the retry (see the trace exporter
82
+ // in ../common/request.js, which likewise aborts the request on timeout).
83
+ req.on('timeout', () => {
84
+ req.destroy(new Error(`Profiling agent export timed out after ${options.timeout}ms`))
85
+ })
86
+
62
87
  if (form) {
63
88
  sizeDistribution.track(form.size())
64
89
  form.pipe(req)
@@ -11,10 +11,10 @@ const processTags = require('../../process-tags')
11
11
  /** @typedef {import('../../config/config-base')} TracerConfig */
12
12
 
13
13
  /**
14
- * Maps the canonical profiling.enabled value to the activation reported in the
14
+ * Maps the canonical profiling.DD_PROFILING_ENABLED value to the activation reported in the
15
15
  * profile event.
16
16
  *
17
- * @param {string} [enabled] - config.profiling.enabled ('true' | 'false' | 'auto')
17
+ * @param {string} [enabled] - config.profiling.DD_PROFILING_ENABLED ('true' | 'false' | 'auto')
18
18
  * @returns {string}
19
19
  */
20
20
  function getActivation (enabled) {
@@ -38,7 +38,7 @@ class EventSerializer {
38
38
  this.#service = config.service
39
39
  this.#appVersion = config.version
40
40
  this.#libraryInjected = !!config.DD_INJECTION_ENABLED
41
- this.#activation = getActivation(config.profiling?.enabled)
41
+ this.#activation = getActivation(config.profiling?.DD_PROFILING_ENABLED)
42
42
  }
43
43
 
44
44
  /**
@@ -148,7 +148,7 @@ class NativeWallProfiler {
148
148
  this.#endpointCollectionEnabled = config.DD_PROFILING_ENDPOINT_COLLECTION_ENABLED
149
149
  this.#flushIntervalMillis = flushInterval
150
150
  this.#samplingIntervalMicros = SAMPLING_INTERVAL * 1000
151
- this.#telemetryHeartbeatIntervalMillis = config.telemetry.heartbeatInterval
151
+ this.#telemetryHeartbeatIntervalMillis = config.telemetry.DD_TELEMETRY_HEARTBEAT_INTERVAL
152
152
  this.#timelineEnabled = config.DD_PROFILING_TIMELINE_ENABLED
153
153
  this.#v8ProfilerBugWorkaroundEnabled = config.DD_PROFILING_V8_PROFILER_BUG_WORKAROUND
154
154
 
@@ -138,7 +138,7 @@ class Tracer extends NoopProxy {
138
138
  spanleak.startScrubber()
139
139
  }
140
140
 
141
- if (config.remoteConfig.enabled && !config.isCiVisibility) {
141
+ if (config.remoteConfig.DD_REMOTE_CONFIGURATION_ENABLED && !config.isCiVisibility) {
142
142
  const RemoteConfig = require('./remote_config')
143
143
  const rc = new RemoteConfig(config)
144
144
 
@@ -180,11 +180,11 @@ class Tracer extends NoopProxy {
180
180
  openfeatureRemoteConfig.enable(rc, config, () => this.openfeature)
181
181
  }
182
182
 
183
- if (config.profiling.enabled === 'true') {
183
+ if (config.profiling.DD_PROFILING_ENABLED === 'true') {
184
184
  this._profilerStarted = this._startProfiler(config)
185
185
  } else {
186
186
  this._profilerStarted = false
187
- if (config.profiling.enabled === 'auto') {
187
+ if (config.profiling.DD_PROFILING_ENABLED === 'auto') {
188
188
  const { SSIHeuristics } = require('./profiling/ssi-heuristics')
189
189
  const ssiHeuristics = new SSIHeuristics(config)
190
190
  ssiHeuristics.start()
@@ -216,7 +216,7 @@ class Tracer extends NoopProxy {
216
216
 
217
217
  this._modules.rewriter.enable(config)
218
218
 
219
- if (config.tracing && config.DD_CIVISIBILITY_MANUAL_API_ENABLED) {
219
+ if (config.DD_TRACE_ENABLED && config.testOptimization.DD_CIVISIBILITY_MANUAL_API_ENABLED) {
220
220
  const TestApiManualPlugin = require('./ci-visibility/test-api-manual/test-api-manual-plugin')
221
221
  this._testApiManualPlugin = new TestApiManualPlugin(this)
222
222
  // `shouldGetEnvironmentData` is passed as false so that we only lazily calculate it
@@ -225,7 +225,7 @@ class Tracer extends NoopProxy {
225
225
  this._testApiManualPlugin.configure({ ...config, enabled: true }, false)
226
226
  }
227
227
  if (config.DD_AGENTLESS_LOG_SUBMISSION_ENABLED) {
228
- if (config.apiKey) {
228
+ if (config.DD_API_KEY) {
229
229
  const LogSubmissionPlugin = require('./ci-visibility/log-submission/log-submission-plugin')
230
230
  const automaticLogPlugin = new LogSubmissionPlugin(this)
231
231
  automaticLogPlugin.configure({ ...config, enabled: true })
@@ -237,7 +237,7 @@ class Tracer extends NoopProxy {
237
237
  }
238
238
  }
239
239
 
240
- if (config.isTestDynamicInstrumentationEnabled) {
240
+ if (config.testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED) {
241
241
  const getDynamicInstrumentationClient = require('./ci-visibility/dynamic-instrumentation')
242
242
  // We instantiate the client but do not start the Worker here. The worker is started lazily
243
243
  getDynamicInstrumentationClient(config)
@@ -270,11 +270,11 @@ class Tracer extends NoopProxy {
270
270
  * @param {import('./config/config-base')} config - Tracer configuration
271
271
  */
272
272
  #updateTracing (config) {
273
- if (config.tracing !== false) {
273
+ if (config.DD_TRACE_ENABLED !== false) {
274
274
  if (config.appsec.enabled) {
275
275
  this._modules.appsec.enable(config)
276
276
  }
277
- if (config.llmobs.enabled) {
277
+ if (config.llmobs.DD_LLMOBS_ENABLED) {
278
278
  this._modules.llmobs.enable(config)
279
279
  }
280
280
  if (!this._tracingInitialized) {
@@ -11,16 +11,15 @@ const startedSpans = new WeakSet()
11
11
  const finishedSpans = new WeakSet()
12
12
 
13
13
  class SpanProcessor {
14
- constructor (exporter, prioritySampler, config) {
14
+ constructor (exporter, prioritySampler, config, otlpStatsExporter) {
15
15
  this._exporter = exporter
16
16
  this._prioritySampler = prioritySampler
17
17
  this._config = config
18
18
  this._killAll = false
19
19
 
20
- // TODO: This should already have been calculated in `config.js`.
21
- if (config.stats?.enabled && !config.appsec?.standalone?.enabled) {
20
+ if (config.stats?.DD_TRACE_STATS_COMPUTATION_ENABLED && !config.appsec?.standalone?.enabled) {
22
21
  const { SpanStatsProcessor } = require('./span_stats')
23
- this._stats = new SpanStatsProcessor(config)
22
+ this._stats = new SpanStatsProcessor(config, otlpStatsExporter)
24
23
  }
25
24
 
26
25
  this._spanSampler = new SpanSampler(config.sampler)
@@ -42,11 +41,11 @@ class SpanProcessor {
42
41
  const active = []
43
42
  const formatted = []
44
43
  const trace = spanContext._trace
45
- const { flushMinSpans, tracing } = this._config
44
+ const { flushMinSpans, DD_TRACE_ENABLED } = this._config
46
45
  const { started, finished } = trace
47
46
 
48
47
  if (trace.record === false) return
49
- if (tracing === false) {
48
+ if (DD_TRACE_ENABLED === false) {
50
49
  this._erase(trace, active)
51
50
  return
52
51
  }