dd-trace 5.110.0 → 5.111.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 (115) hide show
  1. package/README.md +13 -8
  2. package/ci/init.js +21 -2
  3. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +13 -0
  6. package/initialize.mjs +5 -6
  7. package/loader-hook.mjs +76 -55
  8. package/package.json +9 -9
  9. package/packages/datadog-instrumentations/src/fs.js +8 -6
  10. package/packages/datadog-instrumentations/src/graphql.js +26 -495
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  13. package/packages/datadog-instrumentations/src/mongoose.js +14 -2
  14. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1293 -0
  15. package/packages/datadog-instrumentations/src/vitest-main.js +1567 -0
  16. package/packages/datadog-instrumentations/src/vitest-util.js +249 -0
  17. package/packages/datadog-instrumentations/src/vitest-worker.js +753 -0
  18. package/packages/datadog-instrumentations/src/vitest.js +11 -1855
  19. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  20. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +18 -10
  21. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  22. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  23. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +10 -4
  24. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  25. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  26. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  27. package/packages/datadog-plugin-graphql/src/execute.js +627 -11
  28. package/packages/datadog-plugin-graphql/src/index.js +20 -8
  29. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  30. package/packages/datadog-plugin-graphql/src/validate.js +16 -5
  31. package/packages/datadog-plugin-http/src/index.js +6 -8
  32. package/packages/datadog-plugin-jest/src/index.js +1 -1
  33. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  34. package/packages/datadog-plugin-openai/src/services.js +2 -2
  35. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  36. package/packages/datadog-plugin-vitest/src/index.js +69 -63
  37. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  38. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  39. package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
  40. package/packages/dd-trace/src/appsec/channels.js +3 -1
  41. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  42. package/packages/dd-trace/src/appsec/graphql.js +5 -5
  43. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +26 -19
  44. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  45. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  46. package/packages/dd-trace/src/appsec/index.js +10 -1
  47. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  48. package/packages/dd-trace/src/appsec/reporter.js +49 -10
  49. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
  50. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  51. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  52. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  53. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -2
  54. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  55. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  56. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -2
  57. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  58. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -13
  59. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  60. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  61. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  62. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  63. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  64. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  65. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  66. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  67. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  68. package/packages/dd-trace/src/config/defaults.js +3 -2
  69. package/packages/dd-trace/src/config/generated-config-types.d.ts +61 -57
  70. package/packages/dd-trace/src/config/helper.js +1 -0
  71. package/packages/dd-trace/src/config/index.js +35 -22
  72. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  73. package/packages/dd-trace/src/config/remote_config.js +1 -1
  74. package/packages/dd-trace/src/config/supported-configurations.json +90 -56
  75. package/packages/dd-trace/src/constants.js +7 -0
  76. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  77. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  78. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  79. package/packages/dd-trace/src/llmobs/index.js +1 -1
  80. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  81. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  82. package/packages/dd-trace/src/llmobs/sdk.js +3 -3
  83. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  84. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  85. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  86. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  87. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  88. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  89. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
  90. package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
  91. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  92. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  93. package/packages/dd-trace/src/plugin_manager.js +7 -7
  94. package/packages/dd-trace/src/plugins/ci_plugin.js +12 -3
  95. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  96. package/packages/dd-trace/src/plugins/util/test.js +11 -9
  97. package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
  98. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
  99. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  100. package/packages/dd-trace/src/proxy.js +8 -8
  101. package/packages/dd-trace/src/span_processor.js +5 -6
  102. package/packages/dd-trace/src/span_stats.js +96 -78
  103. package/packages/dd-trace/src/startup-log.js +2 -1
  104. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  105. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  106. package/packages/dd-trace/src/telemetry/index.js +2 -2
  107. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  108. package/packages/dd-trace/src/telemetry/send-data.js +8 -8
  109. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  110. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  111. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  112. package/vendor/dist/protobufjs/index.js +1 -1
  113. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  114. package/vendor/dist/shell-quote/index.js +1 -1
  115. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
@@ -1,12 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const pick = require('../../datadog-core/src/utils/src/pick')
4
+ const { DD_MAJOR } = require('../../../version')
4
5
  const CompositePlugin = require('../../dd-trace/src/plugins/composite')
5
6
  const log = require('../../dd-trace/src/log')
6
7
  const GraphQLExecutePlugin = require('./execute')
7
8
  const GraphQLParsePlugin = require('./parse')
8
9
  const GraphQLValidatePlugin = require('./validate')
9
- const GraphQLResolvePlugin = require('./resolve')
10
10
 
11
11
  class GraphQLPlugin extends CompositePlugin {
12
12
  static id = 'graphql'
@@ -15,7 +15,9 @@ class GraphQLPlugin extends CompositePlugin {
15
15
  execute: GraphQLExecutePlugin,
16
16
  parse: GraphQLParsePlugin,
17
17
  validate: GraphQLValidatePlugin,
18
- resolve: GraphQLResolvePlugin,
18
+ // resolve plugin is absorbed into execute: per-field data is recorded
19
+ // synchronously in wrapResolve, and all graphql.resolve spans are
20
+ // materialized at execute end.
19
21
  }
20
22
  }
21
23
 
@@ -30,11 +32,15 @@ class GraphQLPlugin extends CompositePlugin {
30
32
  // config validator helpers
31
33
 
32
34
  function validateConfig (config) {
35
+ const collapse = config.collapse === undefined || !!config.collapse
33
36
  return {
34
37
  ...config,
35
38
  depth: getDepth(config),
36
39
  variables: getVariablesFilter(config),
37
- collapse: config.collapse === undefined || !!config.collapse,
40
+ collapse,
41
+ // v5 counted collapsed list indices toward `depth`, so the same query reached a
42
+ // different depth depending on `collapse`. v6 counts selection-set depth only.
43
+ countListIndices: DD_MAJOR < 6 && collapse,
38
44
  hooks: getHooks(config),
39
45
  }
40
46
  }
@@ -60,13 +66,19 @@ function getVariablesFilter (config) {
60
66
  }
61
67
 
62
68
  const noop = () => {}
69
+ const noopHooks = { execute: noop, parse: noop, validate: noop, resolve: undefined }
63
70
 
64
71
  function getHooks ({ hooks }) {
65
- const execute = hooks?.execute ?? noop
66
- const parse = hooks?.parse ?? noop
67
- const validate = hooks?.validate ?? noop
68
-
69
- return { execute, parse, validate }
72
+ if (!hooks) return noopHooks
73
+ return {
74
+ execute: hooks.execute ?? noop,
75
+ parse: hooks.parse ?? noop,
76
+ validate: hooks.validate ?? noop,
77
+ // No noop fallback: `resolve` runs per-field (hot path); the plugin
78
+ // gates with `if (this.config.hooks.resolve)` so the absent-hook case
79
+ // skips both the call and the payload-object allocation.
80
+ resolve: hooks.resolve,
81
+ }
70
82
  }
71
83
 
72
84
  module.exports = GraphQLPlugin
@@ -2,34 +2,54 @@
2
2
 
3
3
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
4
 
5
+ const documentSources = new WeakMap()
6
+
5
7
  class GraphQLParsePlugin extends TracingPlugin {
6
8
  static id = 'graphql'
7
9
  static operation = 'parser'
10
+ static prefix = 'tracing:orchestrion:graphql:apm:graphql:parser'
8
11
 
9
12
  bindStart (ctx) {
13
+ const source = ctx.arguments?.[0]
14
+
10
15
  this.startSpan('graphql.parse', {
11
16
  service: this.config.service,
12
17
  type: 'graphql',
13
18
  meta: {},
14
19
  }, ctx)
15
20
 
21
+ ctx.ddSource = source
22
+
16
23
  return ctx.currentStore
17
24
  }
18
25
 
19
- finish (ctx) {
20
- const { source, document, docSource } = ctx
26
+ end (ctx) {
27
+ const source = ctx.ddSource
28
+ const document = ctx.result
21
29
  const span = ctx?.currentStore?.span || this.activeSpan
22
30
 
23
- if (this.config.source && document) {
31
+ let docSource
32
+ if (document) {
33
+ if (source) {
34
+ docSource = source.body || source
35
+ documentSources.set(document, docSource)
36
+ } else {
37
+ docSource = documentSources.get(document)
38
+ }
39
+ }
40
+
41
+ if (this.config.source && docSource) {
24
42
  span.setTag('graphql.source', docSource)
25
43
  }
26
44
 
27
45
  this.config.hooks.parse(span, source, document)
28
46
 
29
- super.finish(ctx)
47
+ span.finish()
30
48
 
31
49
  return ctx.parentStore
32
50
  }
33
51
  }
34
52
 
53
+ GraphQLParsePlugin.documentSources = documentSources
54
+
35
55
  module.exports = GraphQLParsePlugin
@@ -1,14 +1,18 @@
1
1
  'use strict'
2
2
 
3
3
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+ const GraphQLParsePlugin = require('./parse')
4
5
  const { extractErrorIntoSpanEvent } = require('./utils')
5
6
 
6
7
  class GraphQLValidatePlugin extends TracingPlugin {
7
8
  static id = 'graphql'
8
9
  static operation = 'validate'
10
+ static prefix = 'tracing:orchestrion:graphql:apm:graphql:validate'
9
11
 
10
12
  bindStart (ctx) {
11
- const { docSource, document } = ctx
13
+ // validate(schema, documentAST, rules, options, typeInfo)
14
+ const document = ctx.arguments?.[1]
15
+ const docSource = document ? GraphQLParsePlugin.documentSources.get(document) : undefined
12
16
  const source = this.config.source && document && docSource
13
17
 
14
18
  this.startSpan('graphql.validate', {
@@ -19,19 +23,26 @@ class GraphQLValidatePlugin extends TracingPlugin {
19
23
  },
20
24
  }, ctx)
21
25
 
26
+ ctx.ddDocument = document
27
+
22
28
  return ctx.currentStore
23
29
  }
24
30
 
25
- finish (ctx) {
26
- const { document, errors } = ctx
31
+ end (ctx) {
32
+ const document = ctx.ddDocument
33
+ const errors = ctx.result
27
34
  const span = ctx?.currentStore?.span || this.activeSpan
35
+
28
36
  this.config.hooks.validate(span, document, errors)
29
- if (errors) {
37
+
38
+ if (errors?.length) {
39
+ span.setTag('error', errors[0])
30
40
  for (const err of errors) {
31
41
  extractErrorIntoSpanEvent(this._tracerConfig, span, err)
32
42
  }
33
43
  }
34
- super.finish(ctx)
44
+
45
+ span.finish()
35
46
 
36
47
  return ctx.parentStore
37
48
  }
@@ -1,6 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const PushSubscriptionPlugin = require('../../datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription')
4
3
  const CompositePlugin = require('../../dd-trace/src/plugins/composite')
5
4
  const { enableGCPPubSubPushSubscription } = require('../../dd-trace/src/serverless')
6
5
  const log = require('../../dd-trace/src/log')
@@ -17,14 +16,13 @@ class HttpPlugin extends CompositePlugin {
17
16
  static get plugins () {
18
17
  const plugins = {}
19
18
 
20
- // Load push subscription plugin first (if enabled) for GCP Cloud Run
19
+ // Load the push subscription plugin first (if enabled) for GCP Cloud Run.
20
+ // The require stays inside the gate so the pubsub plugin graph is not pulled
21
+ // into every process that instruments http — only GCP Cloud Run reaches it.
21
22
  if (enableGCPPubSubPushSubscription()) {
22
- try {
23
- plugins['pubsub-push-subscription'] = PushSubscriptionPlugin
24
- log.debug('Loaded GCP Pub/Sub Push Subscription plugin for HTTP requests')
25
- } catch (e) {
26
- log.debug('Failed to load GCP Pub/Sub Push Subscription plugin:', e)
27
- }
23
+ plugins['pubsub-push-subscription'] =
24
+ require('../../datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription')
25
+ log.debug('Loaded GCP Pub/Sub Push Subscription plugin for HTTP requests')
28
26
  }
29
27
 
30
28
  plugins.server = HttpServerPlugin
@@ -177,7 +177,7 @@ class JestPlugin extends CiPlugin {
177
177
 
178
178
  this.telemetry.count(TELEMETRY_TEST_SESSION, {
179
179
  provider: this.ciProviderName,
180
- autoInjected: !!this._tracerConfig.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
180
+ autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
181
181
  })
182
182
 
183
183
  appClosingTelemetry()
@@ -427,7 +427,7 @@ class MochaPlugin extends CiPlugin {
427
427
  finishAllTraceSpans(this.testSessionSpan)
428
428
  this.telemetry.count(TELEMETRY_TEST_SESSION, {
429
429
  provider: this.ciProviderName,
430
- autoInjected: !!this._tracerConfig.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
430
+ autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
431
431
  })
432
432
  }
433
433
  this.libraryConfig = null
@@ -24,12 +24,12 @@ module.exports.init = function (tracerConfig) {
24
24
  })
25
25
  : new NoopDogStatsDClient()
26
26
 
27
- logger = tracerConfig && tracerConfig.apiKey
27
+ logger = tracerConfig && tracerConfig.DD_API_KEY
28
28
  ? new ExternalLogger({
29
29
  ddsource: 'openai',
30
30
  hostname: tracerConfig.hostname,
31
31
  service: tracerConfig.service,
32
- apiKey: tracerConfig.apiKey,
32
+ apiKey: tracerConfig.DD_API_KEY,
33
33
  interval: FLUSH_INTERVAL,
34
34
  })
35
35
  : new NoopExternalLogger()
@@ -108,7 +108,7 @@ class PlaywrightPlugin extends CiPlugin {
108
108
  finishAllTraceSpans(this.testSessionSpan)
109
109
  this.telemetry.count(TELEMETRY_TEST_SESSION, {
110
110
  provider: this.ciProviderName,
111
- autoInjected: !!this._tracerConfig.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
111
+ autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
112
112
  })
113
113
  appClosingTelemetry()
114
114
  this.tracer._exporter.flush(onDone)
@@ -30,12 +30,12 @@ const {
30
30
  TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
31
31
  TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
32
32
  TEST_HAS_FAILED_ALL_RETRIES,
33
- getLibraryCapabilitiesTags,
33
+ getLibraryCapabilitiesTags: getDefaultLibraryCapabilitiesTags,
34
34
  TEST_RETRY_REASON_TYPES,
35
- isModifiedTest,
36
35
  TEST_IS_MODIFIED,
37
36
  TEST_HAS_DYNAMIC_NAME,
38
37
  TEST_FINAL_STATUS,
38
+ TEST_IS_TEST_FRAMEWORK_WORKER,
39
39
  } = require('../../dd-trace/src/plugins/util/test')
40
40
  const { COMPONENT } = require('../../dd-trace/src/constants')
41
41
  const {
@@ -70,49 +70,6 @@ class VitestPlugin extends CiPlugin {
70
70
  })
71
71
  })
72
72
 
73
- this.addSub('ci:vitest:test:is-new', ({ knownTests, testSuiteAbsolutePath, testName, onDone }) => {
74
- // if for whatever reason the worker does not receive valid known tests, we don't consider it as new
75
- if (!knownTests.vitest) {
76
- return onDone(false)
77
- }
78
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
79
- const testsForThisTestSuite = knownTests.vitest[testSuite] || []
80
- onDone(!testsForThisTestSuite.includes(testName))
81
- })
82
-
83
- this.addSub('ci:vitest:test:is-attempt-to-fix', ({
84
- testManagementTests,
85
- testSuiteAbsolutePath,
86
- testName,
87
- onDone,
88
- }) => {
89
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
90
- const { isAttemptToFix } = this.getTestProperties(testManagementTests, testSuite, testName)
91
-
92
- onDone(isAttemptToFix ?? false)
93
- })
94
-
95
- this.addSub('ci:vitest:test:is-disabled', ({ testManagementTests, testSuiteAbsolutePath, testName, onDone }) => {
96
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
97
- const { isDisabled } = this.getTestProperties(testManagementTests, testSuite, testName)
98
-
99
- onDone(isDisabled)
100
- })
101
-
102
- this.addSub('ci:vitest:test:is-quarantined', ({ testManagementTests, testSuiteAbsolutePath, testName, onDone }) => {
103
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
104
- const { isQuarantined } = this.getTestProperties(testManagementTests, testSuite, testName)
105
-
106
- onDone(isQuarantined)
107
- })
108
-
109
- this.addSub('ci:vitest:test:is-modified', ({ modifiedFiles, testSuiteAbsolutePath, onDone }) => {
110
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
111
- const isModified = isModifiedTest(testSuite, 0, 0, modifiedFiles, this.constructor.id)
112
-
113
- onDone(isModified)
114
- })
115
-
116
73
  this.addSub('ci:vitest:is-early-flake-detection-faulty', ({
117
74
  knownTests,
118
75
  testFilepaths,
@@ -141,12 +98,16 @@ class VitestPlugin extends CiPlugin {
141
98
  isRetryReasonAttemptToFix,
142
99
  isRetryReasonAtr,
143
100
  isModified,
101
+ isTestFrameworkWorker,
102
+ requestErrorTags,
144
103
  } = ctx
145
104
 
146
105
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
147
- const store = storage('legacy').getStore()
106
+ const store = ctx.currentStore || storage('legacy').getStore()
107
+ const testSuiteSpan = store?.testSuiteSpan || this.testSuiteSpan
148
108
 
149
109
  const extraTags = {
110
+ ...requestErrorTags,
150
111
  [TEST_SOURCE_FILE]: testSuite,
151
112
  }
152
113
  if (isRetry) {
@@ -179,11 +140,14 @@ class VitestPlugin extends CiPlugin {
179
140
  if (isModified) {
180
141
  extraTags[TEST_IS_MODIFIED] = 'true'
181
142
  }
143
+ if (isTestFrameworkWorker) {
144
+ extraTags[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
145
+ }
182
146
 
183
147
  const span = this.startTestSpan(
184
148
  testName,
185
149
  testSuite,
186
- this.testSuiteSpan,
150
+ testSuiteSpan,
187
151
  extraTags
188
152
  )
189
153
 
@@ -204,7 +168,7 @@ class VitestPlugin extends CiPlugin {
204
168
  })
205
169
 
206
170
  this.addBind('ci:vitest:test:finish-time', (ctx) => {
207
- const { status, task, attemptToFixPassed, attemptToFixFailed } = ctx
171
+ const { status, task, attemptToFixPassed, attemptToFixFailed, duration } = ctx
208
172
  const span = ctx.currentStore?.span
209
173
 
210
174
  // we store the finish time to finish at a later hook
@@ -218,7 +182,8 @@ class VitestPlugin extends CiPlugin {
218
182
  span.setTag(TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED, 'false')
219
183
  }
220
184
 
221
- this.taskToFinishTime.set(task, span._getTime())
185
+ const finishTime = typeof duration === 'number' ? span._startTime + duration : span._getTime()
186
+ this.taskToFinishTime.set(task, finishTime)
222
187
 
223
188
  ctx.parentStore = ctx.currentStore
224
189
  ctx.currentStore = { ...ctx.currentStore, span }
@@ -291,19 +256,29 @@ class VitestPlugin extends CiPlugin {
291
256
  finishAllTraceSpans(span)
292
257
  })
293
258
 
294
- this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath, isNew, isDisabled }) => {
259
+ this.addSub('ci:vitest:test:skip', ({
260
+ testName,
261
+ testSuiteAbsolutePath,
262
+ isNew,
263
+ isDisabled,
264
+ isTestFrameworkWorker,
265
+ requestErrorTags,
266
+ testSuiteSpan,
267
+ }) => {
295
268
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
296
269
  const testSpan = this.startTestSpan(
297
270
  testName,
298
271
  testSuite,
299
- this.testSuiteSpan,
272
+ testSuiteSpan || this.testSuiteSpan,
300
273
  {
274
+ ...requestErrorTags,
301
275
  [TEST_SOURCE_FILE]: testSuite,
302
276
  [TEST_SOURCE_START]: 1, // we can't get the proper start line in vitest
303
277
  [TEST_STATUS]: 'skip',
304
278
  [TEST_FINAL_STATUS]: 'skip',
305
279
  ...(isDisabled ? { [TEST_MANAGEMENT_IS_DISABLED]: 'true' } : {}),
306
280
  ...(isNew ? { [TEST_IS_NEW]: 'true' } : {}),
281
+ ...(isTestFrameworkWorker ? { [TEST_IS_TEST_FRAMEWORK_WORKER]: 'true' } : {}),
307
282
  }
308
283
  )
309
284
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(testSpan))
@@ -311,7 +286,15 @@ class VitestPlugin extends CiPlugin {
311
286
  })
312
287
 
313
288
  this.addBind('ci:vitest:test-suite:start', (ctx) => {
314
- const { codeOwnersEntries, repositoryRoot, testSuiteAbsolutePath, frameworkVersion } = ctx
289
+ const {
290
+ codeOwnersEntries,
291
+ repositoryRoot,
292
+ requestErrorTags,
293
+ testSuiteAbsolutePath,
294
+ frameworkVersion,
295
+ isTestFrameworkWorker,
296
+ isVitestNoWorkerInitActive,
297
+ } = ctx
315
298
 
316
299
  const testCommand = ctx.testCommand || 'vitest run'
317
300
  const { testSessionId, testModuleId } = ctx
@@ -329,13 +312,16 @@ class VitestPlugin extends CiPlugin {
329
312
  // test suites run in a different process, so they also need to init the metadata dictionary
330
313
  const testSessionName = getTestSessionName(this.config, trimmedCommand, this.testEnvironmentMetadata)
331
314
  if (this.tracer._exporter.addMetadataTags) {
315
+ const libraryCapabilitiesTags = this.getLibraryCapabilitiesTags(frameworkVersion, {
316
+ isVitestNoWorkerInitActive,
317
+ })
332
318
  this.tracer._exporter.addMetadataTags({
333
319
  [TEST_LEVELS_METADATA]: {
334
320
  [TEST_COMMAND]: testCommand,
335
321
  [TEST_SESSION_NAME]: testSessionName,
336
322
  ...getTestLevelsMetadataTags(this.testEnvironmentMetadata),
337
323
  },
338
- test: getLibraryCapabilitiesTags(this.constructor.id),
324
+ test: libraryCapabilitiesTags,
339
325
  })
340
326
  }
341
327
 
@@ -348,9 +334,13 @@ class VitestPlugin extends CiPlugin {
348
334
  testSuite,
349
335
  'vitest'
350
336
  ),
337
+ ...requestErrorTags,
351
338
  [TEST_SOURCE_FILE]: testSuite,
352
339
  [TEST_SOURCE_START]: 1,
353
340
  }
341
+ if (isTestFrameworkWorker) {
342
+ testSuiteMetadata[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
343
+ }
354
344
 
355
345
  const codeOwners = this.getCodeOwners(testSuiteMetadata)
356
346
  if (codeOwners) {
@@ -374,13 +364,17 @@ class VitestPlugin extends CiPlugin {
374
364
  return ctx.currentStore
375
365
  })
376
366
 
377
- this.addSub('ci:vitest:test-suite:finish', ({ testSuiteSpan, status, onFinish }) => {
367
+ this.addSub('ci:vitest:test-suite:finish', ({ testSuiteSpan, status, deferFlush, onFinish }) => {
378
368
  if (testSuiteSpan) {
379
369
  testSuiteSpan.setTag(TEST_STATUS, status)
380
370
  testSuiteSpan.finish()
381
371
  finishAllTraceSpans(testSuiteSpan)
382
372
  }
383
373
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
374
+ if (deferFlush) {
375
+ onFinish()
376
+ return
377
+ }
384
378
  this.tracer._exporter.flush(onFinish)
385
379
  if (this.runningTestProbe) {
386
380
  this.removeDiProbe(this.runningTestProbe)
@@ -409,9 +403,15 @@ class VitestPlugin extends CiPlugin {
409
403
  isEarlyFlakeDetectionEnabled,
410
404
  isEarlyFlakeDetectionFaulty,
411
405
  isTestManagementTestsEnabled,
406
+ requestErrorTags,
412
407
  vitestPool,
408
+ isVitestNoWorkerInitActive,
413
409
  onFinish,
414
410
  }) => {
411
+ for (const [tag, value] of Object.entries(requestErrorTags || {})) {
412
+ this.testSessionSpan.setTag(tag, value)
413
+ this.testModuleSpan.setTag(tag, value)
414
+ }
415
415
  this.testSessionSpan.setTag(TEST_STATUS, status)
416
416
  this.testModuleSpan.setTag(TEST_STATUS, status)
417
417
  if (error) {
@@ -438,12 +438,12 @@ class VitestPlugin extends CiPlugin {
438
438
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
439
439
  this.testSessionSpan.finish()
440
440
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session', {
441
- hasFailedTestReplay: this.libraryConfig?.isDiEnabled || undefined,
441
+ hasFailedTestReplay: this.libraryConfig?.isDiEnabled && !isVitestNoWorkerInitActive ? true : undefined,
442
442
  })
443
443
  finishAllTraceSpans(this.testSessionSpan)
444
444
  this.telemetry.count(TELEMETRY_TEST_SESSION, {
445
445
  provider: this.ciProviderName,
446
- autoInjected: !!this._tracerConfig.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
446
+ autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
447
447
  })
448
448
  this.tracer._exporter.flush(onFinish)
449
449
  })
@@ -453,6 +453,19 @@ class VitestPlugin extends CiPlugin {
453
453
  })
454
454
  }
455
455
 
456
+ /**
457
+ * Returns Vitest library capability metadata tags.
458
+ * @param {string} frameworkVersion - The Vitest version.
459
+ * @param {object} [ctx] - Diagnostic channel context.
460
+ * @param {boolean} [ctx.isVitestNoWorkerInitActive] - Whether no-worker init is active for this run.
461
+ * @returns {Record<string, string|undefined>}
462
+ */
463
+ getLibraryCapabilitiesTags (frameworkVersion, ctx = {}) {
464
+ return getDefaultLibraryCapabilitiesTags(this.constructor.id, frameworkVersion, {
465
+ omitFailedTestReplay: ctx.isVitestNoWorkerInitActive,
466
+ })
467
+ }
468
+
456
469
  /**
457
470
  * Handles the coverage report by discovering and uploading it if enabled.
458
471
  * @param {string} rootDir - The root directory where coverage reports are located.
@@ -469,13 +482,6 @@ class VitestPlugin extends CiPlugin {
469
482
  onDone,
470
483
  })
471
484
  }
472
-
473
- getTestProperties (testManagementTests, testSuite, testName) {
474
- const { attempt_to_fix: isAttemptToFix, disabled: isDisabled, quarantined: isQuarantined } =
475
- testManagementTests?.vitest?.suites?.[testSuite]?.tests?.[testName]?.properties || {}
476
-
477
- return { isAttemptToFix, isDisabled, isQuarantined }
478
- }
479
485
  }
480
486
 
481
487
  module.exports = VitestPlugin
@@ -119,11 +119,14 @@ function wrapCallback (original, wrapper) {
119
119
  * object.
120
120
  * @param {string | symbol} name - The property key of the method to wrap.
121
121
  * @param {(original: Function) => (...args: unknown[]) => unknown} wrapper - The wrapper function.
122
- * @param {{ replaceGetter?: boolean }} [options] - If `replaceGetter` is set to
123
- * true, the getter is accessed and the getter is replaced with one that just
124
- * returns the earlier retrieved value. Use with care! This may only be done in
125
- * case the getter absolutely has no side effect and no setter is defined for the
126
- * property.
122
+ * @param {{ replaceGetter?: boolean }} [options] - By default the getter is
123
+ * wrapped in place, so each property access runs the wrapper. A getter+setter
124
+ * pair keeps its setter; a setter-only property throws. If `replaceGetter` is
125
+ * true, the getter is instead accessed once and replaced with one returning the
126
+ * resolved wrapped value — for a lazy getter+setter pair (e.g. Node 20's
127
+ * `fs.opendir`) the setter is rebuilt to materialize a writable data property on
128
+ * assignment, keeping the descriptor observationally identical for downstream
129
+ * consumers. Use with care! This may only be done when the getter has no side effect.
127
130
  * @returns {Record<string | symbol, unknown> | Function | undefined} The target object with
128
131
  * the wrapped method.
129
132
  */
@@ -151,17 +154,13 @@ function wrap (target, name, wrapper, options) {
151
154
  enumerable: false,
152
155
  }
153
156
 
154
- if (descriptor.set && (!descriptor.get || options?.replaceGetter)) {
155
- // It is possible to support these cases by instrumenting both the getter
156
- // and setter (or only the setter, in case that is a use case).
157
- // For now, this is not supported due to the complexity and the fact that
158
- // this is not a common use case.
159
- throw new Error(options?.replaceGetter
160
- ? 'Replacing a getter/setter pair is not supported. Implement if required.'
161
- : 'Replacing setters is not supported. Implement if required.')
157
+ // A setter-only property has nothing to wrap. Instrumenting the setter is not
158
+ // implemented; a getter+setter pair is handled below.
159
+ if (descriptor.set && !descriptor.get) {
160
+ throw new Error('Replacing setters is not supported. Implement if required.')
162
161
  }
163
162
 
164
- const original = descriptor.value ?? options?.replaceGetter ? target[name] : descriptor.get
163
+ const original = (descriptor.value ?? options?.replaceGetter) ? target[name] : descriptor.get
165
164
 
166
165
  assertMethod(target, name, original)
167
166
 
@@ -176,8 +175,30 @@ function wrap (target, name, wrapper, options) {
176
175
  }
177
176
  descriptor.value = wrapped
178
177
  } else {
179
- if (descriptor.get) {
180
- // `replaceGetter` may only be used when the getter has no side effect.
178
+ if (descriptor.set && options?.replaceGetter) {
179
+ // A lazy accessor pair (e.g. Node 20's `fs.opendir`). `original` already
180
+ // resolved the value through the getter. Keep the property an accessor pair
181
+ // so the shape stays observationally identical — a downstream consumer may
182
+ // read the descriptor or assign to it on a specific Node.js version. The
183
+ // getter returns the wrapped value; the setter mirrors the native lazy
184
+ // contract (assignment self-replaces the property with a writable data
185
+ // property holding the assigned value), so a caller that overwrites the
186
+ // method gets exactly what they set, unwrapped, as before. The descriptor
187
+ // is the original accessor descriptor (no `value`/`writable` slots), so
188
+ // reassigning `get`/`set` keeps it a valid accessor descriptor.
189
+ descriptor.get = () => wrapped
190
+ descriptor.set = function (value) {
191
+ Object.defineProperty(this, name, {
192
+ configurable: descriptor.configurable,
193
+ enumerable: descriptor.enumerable,
194
+ writable: true,
195
+ value,
196
+ })
197
+ }
198
+ } else if (descriptor.get) {
199
+ // Wrap the getter in place; for a getter+setter pair the original setter
200
+ // stays untouched. `replaceGetter` (no side effect on read) instead returns
201
+ // the value resolved once into `wrapped`.
181
202
  descriptor.get = options?.replaceGetter ? () => wrapped : wrapped
182
203
  } else {
183
204
  descriptor.value = wrapped
@@ -93,14 +93,14 @@ class AIGuard extends NoopAIGuard {
93
93
  constructor (tracer, config) {
94
94
  super()
95
95
 
96
- if (!config.apiKey || !config.DD_APP_KEY) {
96
+ if (!config.DD_API_KEY || !config.DD_APP_KEY) {
97
97
  log.error('AIGuard: missing api and/or app keys, use env DD_API_KEY and DD_APP_KEY')
98
98
  this.#initialized = false
99
99
  return
100
100
  }
101
101
  this.#tracer = tracer
102
102
  this.#headers = {
103
- 'DD-API-KEY': config.apiKey,
103
+ 'DD-API-KEY': config.DD_API_KEY,
104
104
  'DD-APPLICATION-KEY': config.DD_APP_KEY,
105
105
  'DD-AI-GUARD-VERSION': tracerVersion,
106
106
  'DD-AI-GUARD-SOURCE': 'SDK',
@@ -31,11 +31,11 @@ class NoopTTLCache {
31
31
  }
32
32
 
33
33
  function configure ({ appsec, apmTracingEnabled }) {
34
- enabled = appsec.apiSecurity.enabled
34
+ enabled = appsec.DD_API_SECURITY_ENABLED
35
35
  asmStandaloneEnabled = apmTracingEnabled === false
36
- sampledRequests = appsec.apiSecurity.sampleDelay === 0
36
+ sampledRequests = appsec.DD_API_SECURITY_SAMPLE_DELAY === 0
37
37
  ? new NoopTTLCache()
38
- : new TTLCache({ max: MAX_SIZE, ttl: appsec.apiSecurity.sampleDelay * 1000 })
38
+ : new TTLCache({ max: MAX_SIZE, ttl: appsec.DD_API_SECURITY_SAMPLE_DELAY * 1000 })
39
39
  }
40
40
 
41
41
  function disable () {
@@ -27,6 +27,8 @@ module.exports = {
27
27
  httpClientResponseFinish: dc.channel('apm:http:client:response:finish'),
28
28
  incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
29
29
  incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
30
+ lambdaStartInvocation: dc.channel('datadog:lambda:start-invocation'),
31
+ lambdaEndInvocation: dc.channel('datadog:lambda:end-invocation'),
30
32
  multerParser: dc.channel('datadog:multer:read:finish'),
31
33
  mysql2OuterQueryStart: dc.channel('datadog:mysql2:outerquery:start'),
32
34
  nextBodyParsed: dc.channel('apm:next:body-parsed'),
@@ -43,7 +45,7 @@ module.exports = {
43
45
  routerParam: dc.channel('datadog:router:param:start'),
44
46
  setCookieChannel: dc.channel('datadog:iast:set-cookie'),
45
47
  setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start'),
46
- startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
48
+ startGraphqlResolver: dc.channel('datadog:graphql:resolver:start'),
47
49
  stripeCheckoutSessionCreate: dc.channel('datadog:stripe:checkoutSession:create:finish'),
48
50
  stripeConstructEvent: dc.channel('datadog:stripe:constructEvent:finish'),
49
51
  stripePaymentIntentCreate: dc.channel('datadog:stripe:paymentIntent:create:finish'),