dd-trace 6.4.0 → 6.6.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 (185) hide show
  1. package/README.electron.md +7 -0
  2. package/README.md +17 -0
  3. package/ci/diagnose.js +2100 -0
  4. package/ci/init.js +23 -2
  5. package/ci/runbook.md +198 -0
  6. package/ci/test-optimization-validation/approval-artifacts.js +143 -0
  7. package/ci/test-optimization-validation/approval.js +299 -0
  8. package/ci/test-optimization-validation/artifact-id.js +20 -0
  9. package/ci/test-optimization-validation/bounded-json.js +121 -0
  10. package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
  11. package/ci/test-optimization-validation/ci-discovery.js +181 -0
  12. package/ci/test-optimization-validation/ci-remediation.js +210 -0
  13. package/ci/test-optimization-validation/cli.js +903 -0
  14. package/ci/test-optimization-validation/command-blocker.js +81 -0
  15. package/ci/test-optimization-validation/command-output-policy.js +206 -0
  16. package/ci/test-optimization-validation/command-runner.js +707 -0
  17. package/ci/test-optimization-validation/command-suitability.js +471 -0
  18. package/ci/test-optimization-validation/executable-approval.js +48 -0
  19. package/ci/test-optimization-validation/executable.js +480 -0
  20. package/ci/test-optimization-validation/generated-file-policy.js +63 -0
  21. package/ci/test-optimization-validation/generated-files.js +351 -0
  22. package/ci/test-optimization-validation/generated-verifier.js +196 -0
  23. package/ci/test-optimization-validation/init-probe-preload.js +163 -0
  24. package/ci/test-optimization-validation/init-probe.js +246 -0
  25. package/ci/test-optimization-validation/late-initialization.js +63 -0
  26. package/ci/test-optimization-validation/local-command.js +163 -0
  27. package/ci/test-optimization-validation/manifest-loader.js +133 -0
  28. package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
  29. package/ci/test-optimization-validation/manifest-schema.js +862 -0
  30. package/ci/test-optimization-validation/offline-fixtures.js +327 -0
  31. package/ci/test-optimization-validation/offline-output.js +406 -0
  32. package/ci/test-optimization-validation/payload-normalizer.js +72 -0
  33. package/ci/test-optimization-validation/plan-writer.js +1120 -0
  34. package/ci/test-optimization-validation/preflight-runner.js +114 -0
  35. package/ci/test-optimization-validation/redaction.js +331 -0
  36. package/ci/test-optimization-validation/report-writer.js +1508 -0
  37. package/ci/test-optimization-validation/safe-files.js +203 -0
  38. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
  39. package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
  40. package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
  41. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
  42. package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
  43. package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
  44. package/ci/test-optimization-validation/setup-runner.js +97 -0
  45. package/ci/test-optimization-validation/static-diagnosis.js +159 -0
  46. package/ci/test-optimization-validation/test-output.js +129 -0
  47. package/ci/test-optimization-validation-manifest.schema.json +1 -0
  48. package/ci/validate-test-optimization.js +3 -0
  49. package/ext/exporters.js +1 -0
  50. package/index.d.ts +121 -8
  51. package/init.js +18 -0
  52. package/initialize.mjs +1 -1
  53. package/loader-hook.mjs +28 -18
  54. package/openfeature.d.ts +1 -0
  55. package/openfeature.js +4 -0
  56. package/package.json +11 -8
  57. package/packages/datadog-instrumentations/src/ai.js +8 -2
  58. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  60. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  61. package/packages/datadog-instrumentations/src/cucumber.js +31 -19
  62. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  63. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  64. package/packages/datadog-instrumentations/src/helpers/channel.js +74 -0
  65. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  66. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  67. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  68. package/packages/datadog-instrumentations/src/jest.js +44 -17
  69. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  70. package/packages/datadog-instrumentations/src/mocha/main.js +79 -32
  71. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  72. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  73. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  74. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  75. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  76. package/packages/datadog-instrumentations/src/next.js +24 -0
  77. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  78. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  79. package/packages/datadog-instrumentations/src/pg.js +2 -2
  80. package/packages/datadog-instrumentations/src/playwright.js +162 -46
  81. package/packages/datadog-instrumentations/src/process.js +3 -0
  82. package/packages/datadog-instrumentations/src/router.js +18 -8
  83. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  84. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +3 -3
  85. package/packages/datadog-instrumentations/src/vitest-main.js +14 -28
  86. package/packages/datadog-instrumentations/src/vitest-util.js +0 -7
  87. package/packages/datadog-instrumentations/src/vitest-worker.js +5 -8
  88. package/packages/datadog-instrumentations/src/ws.js +2 -1
  89. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  90. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  91. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  92. package/packages/datadog-plugin-cucumber/src/index.js +3 -2
  93. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  94. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  95. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  96. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  97. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  98. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  99. package/packages/datadog-plugin-http2/src/client.js +1 -1
  100. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  101. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  102. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  103. package/packages/datadog-plugin-mocha/src/index.js +2 -1
  104. package/packages/datadog-plugin-playwright/src/index.js +181 -65
  105. package/packages/datadog-plugin-router/src/index.js +11 -2
  106. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  107. package/packages/datadog-plugin-vitest/src/index.js +5 -5
  108. package/packages/datadog-plugin-ws/src/close.js +2 -1
  109. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  110. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  111. package/packages/datadog-plugin-ws/src/server.js +2 -1
  112. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  113. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  114. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  115. package/packages/dd-trace/src/azure_metadata.js +2 -1
  116. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  117. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  122. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  124. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  125. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  127. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  128. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  129. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  130. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  131. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  132. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  133. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  134. package/packages/dd-trace/src/config/helper.js +1 -0
  135. package/packages/dd-trace/src/config/index.js +10 -0
  136. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  137. package/packages/dd-trace/src/exporter.js +2 -0
  138. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  140. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  141. package/packages/dd-trace/src/feature-registry.js +10 -3
  142. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  143. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  144. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  145. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  146. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  147. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  148. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  149. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  150. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  151. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  152. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  153. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  154. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  155. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  156. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  157. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  158. package/packages/dd-trace/src/llmobs/util.js +32 -0
  159. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  160. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  161. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  162. package/packages/dd-trace/src/openfeature/index.js +0 -2
  163. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  164. package/packages/dd-trace/src/openfeature/register.js +16 -27
  165. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  166. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  167. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  168. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  169. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  170. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  171. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  172. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  173. package/packages/dd-trace/src/profiling/profilers/space.js +1 -1
  174. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
  175. package/packages/dd-trace/src/proxy.js +84 -7
  176. package/packages/dd-trace/src/span_processor.js +5 -0
  177. package/packages/dd-trace/src/standalone/index.js +0 -14
  178. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  179. package/packages/dd-trace/src/util.js +26 -0
  180. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  181. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  182. package/vendor/dist/protobufjs/index.js +1 -1
  183. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  184. package/vendor/dist/shell-quote/index.js +1 -1
  185. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -0,0 +1,90 @@
1
+ 'use strict'
2
+
3
+ const { isLoopbackHost } = require('../exporters/common/url')
4
+ const log = require('../log')
5
+
6
+ const DEFAULT_AGENTLESS_PATH = '/api/v2/feature-flagging/config/rules-based/server'
7
+ const MAX_POLL_INTERVAL_SECONDS = 60 * 60
8
+
9
+ /**
10
+ * @typedef {import('@datadog/openfeature-node-server').UniversalFlagConfigurationV1} UniversalFlagConfiguration
11
+ */
12
+
13
+ /**
14
+ * @param {import('../config/config-base')} config
15
+ * @param {(configuration: UniversalFlagConfiguration) => void} applyConfiguration
16
+ */
17
+ function create (config, applyConfiguration) {
18
+ const {
19
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: source,
20
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL: baseUrl,
21
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS: pollIntervalSeconds,
22
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS: requestTimeoutSeconds,
23
+ DD_FEATURE_FLAGS_ENABLED: enabled,
24
+ } = config.featureFlags
25
+
26
+ if (!enabled || source !== 'agentless') {
27
+ return
28
+ }
29
+
30
+ try {
31
+ if (!config.DD_API_KEY) {
32
+ throw new Error('DD_API_KEY is required for Feature Flagging agentless delivery')
33
+ }
34
+
35
+ const AgentlessConfigurationSource = require('./agentless_configuration_source')
36
+ return new AgentlessConfigurationSource({
37
+ endpoint: endpoint(config, baseUrl),
38
+ pollIntervalMs: Math.min(pollIntervalSeconds, MAX_POLL_INTERVAL_SECONDS) * 1000,
39
+ requestTimeoutMs: requestTimeoutSeconds * 1000,
40
+ apiKey: config.DD_API_KEY,
41
+ }, applyConfiguration)
42
+ } catch (error) {
43
+ log.error('Unable to configure Feature Flagging configuration source', error)
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Builds the agentless rules-based server endpoint.
49
+ *
50
+ * A configured URL with a non-root path is treated as the exact endpoint. A
51
+ * configured origin (or root URL) receives the standard rules-based server
52
+ * path.
53
+ *
54
+ * @param {import('../config/config-base')} config - Tracer configuration.
55
+ * @param {string | undefined} configuredBaseUrl - Optional endpoint or origin override.
56
+ * @returns {URL} Agentless endpoint.
57
+ */
58
+ function endpoint (config, configuredBaseUrl) {
59
+ const configured = configuredBaseUrl?.trim()
60
+
61
+ if (!configured) {
62
+ const url = new URL(`https://ufc-server.ff-cdn.${config.site.toLowerCase()}${DEFAULT_AGENTLESS_PATH}`)
63
+ if (config.env) url.searchParams.set('dd_env', config.env)
64
+ return url
65
+ }
66
+
67
+ let url
68
+ try {
69
+ url = new URL(configured)
70
+ } catch {
71
+ throw new Error('Invalid Feature Flagging agentless URL')
72
+ }
73
+
74
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') {
75
+ throw new Error('Feature Flagging agentless URL must use HTTP or HTTPS')
76
+ }
77
+ if (url.protocol === 'http:' && !isLoopbackHost(url.hostname)) {
78
+ throw new Error('Feature Flagging agentless URL must use HTTPS unless it targets loopback')
79
+ }
80
+
81
+ if (url.pathname === '' || url.pathname === '/') {
82
+ url.pathname = DEFAULT_AGENTLESS_PATH
83
+ }
84
+
85
+ return url
86
+ }
87
+
88
+ module.exports = {
89
+ create,
90
+ }
@@ -1,40 +1,40 @@
1
1
  'use strict'
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
- const requireOptionalPeer = require('../../../datadog-instrumentations/src/helpers/require-optional-peer')
5
4
  const log = require('../log')
5
+ const configurationSource = require('./configuration_source')
6
6
  const { EXPOSURE_CHANNEL } = require('./constants/constants')
7
7
  const EvalMetricsHook = require('./eval-metrics-hook')
8
8
  const SpanEnrichmentHook = require('./span-enrichment-hook')
9
9
 
10
- const { DatadogNodeServerProvider } = requireOptionalPeer('@datadog/openfeature-node-server')
10
+ const { DatadogNodeServerProvider } = require('./require-provider')
11
11
 
12
12
  /**
13
13
  * OpenFeature provider that integrates with Datadog's feature flagging system.
14
14
  * Extends DatadogNodeServerProvider to add tracer integration and configuration management.
15
15
  */
16
16
  class FlaggingProvider extends DatadogNodeServerProvider {
17
- /** @type {SpanEnrichmentHook?} */
17
+ /** @type {SpanEnrichmentHook | undefined} */
18
18
  #spanEnrichmentHook
19
19
 
20
+ /** @type {{ start: Function, stop: Function } | undefined} */
21
+ #configurationSource
22
+
20
23
  /**
21
24
  * @param {import('../tracer')} tracer - Datadog tracer instance
22
- * @param {import('../config')} config - Tracer configuration object
25
+ * @param {import('../config/config-base')} config - Tracer configuration object
23
26
  */
24
27
  constructor (tracer, config) {
25
- // Call parent constructor with required options and timeout
26
28
  super({
27
29
  exposureChannel: channel(EXPOSURE_CHANNEL),
28
30
  initializationTimeoutMs: config.experimental.flaggingProvider.initializationTimeoutMs,
29
31
  })
30
32
 
31
- this._tracer = tracer
32
- this._config = config
33
-
34
33
  this.hooks.push(new EvalMetricsHook(config))
35
34
 
36
35
  if (config.experimental.flaggingProvider.spanEnrichment?.enabled) {
37
36
  this.#spanEnrichmentHook = new SpanEnrichmentHook(tracer)
37
+ // @ts-expect-error The upstream constructor always initializes its optional hooks property.
38
38
  this.hooks.push(this.#spanEnrichmentHook)
39
39
  log.info('%s span enrichment enabled', this.constructor.name)
40
40
  } else {
@@ -43,6 +43,9 @@ class FlaggingProvider extends DatadogNodeServerProvider {
43
43
 
44
44
  log.debug('%s created with timeout: %dms', this.constructor.name,
45
45
  config.experimental.flaggingProvider.initializationTimeoutMs)
46
+
47
+ this.#configurationSource = configurationSource.create(config, this.setConfiguration.bind(this))
48
+ this.#configurationSource?.start()
46
49
  }
47
50
 
48
51
  /**
@@ -50,22 +53,10 @@ class FlaggingProvider extends DatadogNodeServerProvider {
50
53
  * Cleans up resources including channel subscriptions.
51
54
  */
52
55
  onClose () {
56
+ this.#configurationSource?.stop()
57
+ this.#configurationSource = undefined
53
58
  this.#spanEnrichmentHook?.destroy()
54
- }
55
-
56
- /**
57
- * Internal method to update flag configuration from Remote Config.
58
- * This method is called automatically when Remote Config delivers UFC updates.
59
- *
60
- * @internal
61
- * @param {import('@datadog/openfeature-node-server').UniversalFlagConfigurationV1} ufc
62
- * - Universal Flag Configuration object
63
- */
64
- _setConfiguration (ufc) {
65
- if (typeof this.setConfiguration === 'function') {
66
- this.setConfiguration(ufc)
67
- }
68
- log.debug('%s provider configuration updated', this.constructor.name)
59
+ this.#spanEnrichmentHook = undefined
69
60
  }
70
61
  }
71
62
 
@@ -47,8 +47,6 @@ function enable (config) {
47
47
  setAgentStrategy(config, hasAgent => {
48
48
  exposuresWriter?.setEnabled(hasAgent)
49
49
  })
50
-
51
- log.debug('OpenFeature module enabled')
52
50
  }
53
51
 
54
52
  /**
@@ -20,12 +20,7 @@ function resolveDefault (defaultValue) {
20
20
  * https://openfeature.dev/docs/reference/concepts/provider/
21
21
  */
22
22
  class NoopFlaggingProvider {
23
- /**
24
- * @param {object} [noopTracer] - Optional noop tracer instance
25
- */
26
- constructor (noopTracer) {
27
- this._tracer = noopTracer
28
- this._config = {}
23
+ constructor () {
29
24
  this.metadata = { name: 'NoopFlaggingProvider' }
30
25
  this.status = 'NOT_READY'
31
26
  this.runsOn = 'server'
@@ -78,28 +73,6 @@ class NoopFlaggingProvider {
78
73
  resolveObjectEvaluation (flagKey, defaultValue, context, logger) {
79
74
  return resolveDefault(defaultValue)
80
75
  }
81
-
82
- /**
83
- * @returns {object} Current configuration
84
- */
85
- getConfiguration () {
86
- return this._config
87
- }
88
-
89
- /**
90
- * @param {object} config - Configuration to set
91
- */
92
- setConfiguration (config) {
93
- this._config = config
94
- }
95
-
96
- /**
97
- * @internal
98
- * @param {object} ufc - Universal Flag Configuration object
99
- */
100
- _setConfiguration (ufc) {
101
- this.setConfiguration(ufc)
102
- }
103
76
  }
104
77
 
105
78
  module.exports = NoopFlaggingProvider
@@ -4,43 +4,32 @@ const { registerFeature } = require('../feature-registry')
4
4
 
5
5
  const noop = new (require('./noop'))()
6
6
 
7
- /**
8
- * @param {import('../proxy')} proxy
9
- * @returns {boolean}
10
- */
11
- function hasFlaggingProvider (proxy) {
12
- const descriptor = Reflect.getOwnPropertyDescriptor(proxy, 'openfeature')
13
-
14
- return descriptor?.value !== undefined && descriptor.value !== noop
15
- }
7
+ /** @typedef {import('../proxy') & { openfeature: object }} OpenFeatureProxy */
16
8
 
17
9
  registerFeature({
18
10
  name: 'openfeature',
19
11
  noop,
20
12
  factory: () => require('./index'),
13
+ provider: () => require('./flagging_provider'),
21
14
 
22
- /**
23
- * @param {object} rc - RemoteConfig instance
24
- * @param {import('../config/config-base')} config
25
- * @param {import('../proxy')} proxy
26
- */
27
- remoteConfig (rc, config, proxy) {
28
- const openfeatureRemoteConfig = require('./remote_config')
29
- openfeatureRemoteConfig.enable(rc, config, () => proxy.openfeature)
15
+ /** @param {import('../config/config-base')} config */
16
+ isEnabled (config) {
17
+ return config.featureFlags.DD_FEATURE_FLAGS_ENABLED
30
18
  },
31
19
 
32
20
  /**
21
+ * @param {import('../remote_config')} rc - RemoteConfig instance
33
22
  * @param {import('../config/config-base')} config
34
- * @param {import('../tracer')} tracer
35
- * @param {import('../proxy')} proxy
36
- * @param {Function} lazyProxy
23
+ * @param {OpenFeatureProxy} proxy
37
24
  */
38
- enable (config, tracer, proxy, lazyProxy) {
39
- if (config.experimental.flaggingProvider.enabled) {
40
- proxy._modules.openfeature.enable(config)
41
- if (!hasFlaggingProvider(proxy)) {
42
- lazyProxy(proxy, 'openfeature', () => require('./flagging_provider'), tracer, config)
43
- }
44
- }
25
+ remoteConfig (rc, config, proxy) {
26
+ const openfeatureRemoteConfig = require('./remote_config')
27
+ const subscribe = config.featureFlags.DD_FEATURE_FLAGS_ENABLED &&
28
+ config.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE === 'remote_config'
29
+ openfeatureRemoteConfig.enable(
30
+ rc,
31
+ () => proxy.openfeature,
32
+ subscribe
33
+ )
45
34
  },
46
35
  })
@@ -5,30 +5,27 @@ const RemoteConfigCapabilities = require('../remote_config/capabilities')
5
5
  /**
6
6
  * Configures remote config handlers for openfeature feature flagging
7
7
  *
8
- * @param {object} rc - RemoteConfig instance
9
- * @param {object} config - Tracer config
10
- * @param {Function} getOpenfeatureProxy - Function that returns the OpenFeature proxy from tracer
8
+ * @param {import('../remote_config')} rc - RemoteConfig instance
9
+ * @param {() => import('./flagging_provider')} getOpenfeatureProxy
10
+ * @param {boolean} subscribe - Whether Agent Remote Config owns UFC delivery
11
11
  */
12
- function enable (rc, config, getOpenfeatureProxy) {
13
- // Always enable capability for feature flag configuration
14
- // This indicates the library supports this capability via remote config
15
- rc.updateCapabilities(RemoteConfigCapabilities.FFE_FLAG_CONFIGURATION_RULES, true)
12
+ function enable (rc, getOpenfeatureProxy, subscribe) {
13
+ if (!subscribe) return
16
14
 
17
- // Only register product handler if the experimental feature is enabled
18
- if (!config.experimental.flaggingProvider.enabled) return
15
+ rc.updateCapabilities(RemoteConfigCapabilities.FFE_FLAG_CONFIGURATION_RULES, true)
19
16
 
20
- // Set product handler for FFE_FLAGS
21
- rc.setProductHandler('FFE_FLAGS', (action, conf) => {
17
+ /**
18
+ * @param {string} action
19
+ * @param {import('@datadog/openfeature-node-server').UniversalFlagConfigurationV1} conf
20
+ */
21
+ const updateConfiguration = (action, conf) => {
22
22
  if (action === 'apply' || action === 'modify') {
23
- // Feed UFC config directly to OpenFeature provider
24
- getOpenfeatureProxy()._setConfiguration(conf)
23
+ getOpenfeatureProxy().setConfiguration(conf)
25
24
  } else if (action === 'unapply') {
26
- // Clear the configuration so evaluations return PROVIDER_NOT_READY,
27
- // consistent with Go and Python which also set config to null on RC deletion.
28
- // The evaluator returns PROVIDER_NOT_READY when config is null/undefined.
29
- getOpenfeatureProxy()._setConfiguration(null)
25
+ getOpenfeatureProxy().setConfiguration(undefined)
30
26
  }
31
- })
27
+ }
28
+ rc.setProductHandler('FFE_FLAGS', updateConfiguration)
32
29
  }
33
30
 
34
31
  module.exports = {
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ /** @type {(request: string) => typeof import('@datadog/openfeature-node-server')} */
4
+ let requireOptionalPeer
5
+
6
+ // @ts-expect-error webpack exposes this escape hatch as a free variable.
7
+ // eslint-disable-next-line camelcase
8
+ if (typeof __non_webpack_require__ === 'function') {
9
+ // eslint-disable-next-line camelcase, no-undef
10
+ requireOptionalPeer = __non_webpack_require__
11
+ } else {
12
+ // nft recognizes createRequire through a binding named `module`.
13
+ const module = require('node:module')
14
+ const runtimeRequire = module.createRequire(__filename)
15
+ requireOptionalPeer = runtimeRequire
16
+ }
17
+
18
+ module.exports = requireOptionalPeer('@datadog/openfeature-node-server')
@@ -0,0 +1,10 @@
1
+ 'use strict'
2
+
3
+ // Pre-finish hook for OTel-bridge spans. `Span.end()` runs it before `finish()` formats and
4
+ // (synchronously, for the last span in a trace) exports the trace, so a framework instrumentation
5
+ // can rewrite the operation name / resource while the DD span is still unfinished; `onEnd` only sees
6
+ // the already-built payload. Its own dependency-free module so an instrumentation can register
7
+ // without loading the OTel bridge, and a plain holder so the caller gates on `hook` existing.
8
+
9
+ /** @type {{ hook: ((ddSpan: import('../opentracing/span')) => void) | undefined }} */
10
+ module.exports = { hook: undefined }
@@ -15,6 +15,7 @@ const kinds = require('../../../../ext/kinds')
15
15
  const id = require('../id')
16
16
  const BridgeSpanBase = require('./bridge-span-base')
17
17
  const SpanContext = require('./span_context')
18
+ const spanEndingHook = require('./span-ending-hook')
18
19
  const { setOtelOperationName, setOtelResource } = require('./span-helpers')
19
20
 
20
21
  const spanKindNames = {
@@ -255,6 +256,10 @@ class Span extends BridgeSpanBase {
255
256
  const hrEndTime = timeInputToHrTime(timeInput || (performance.now() + timeOrigin))
256
257
  const endTime = hrTimeToMilliseconds(hrEndTime)
257
258
 
259
+ // Must run before `finish()`, while the DD span is still unfinished. See span-ending-hook.js.
260
+ if (spanEndingHook.hook !== undefined) {
261
+ spanEndingHook.hook(this._ddSpan)
262
+ }
258
263
  this._ddSpan.finish(endTime)
259
264
  this._spanProcessor.onEnd(this)
260
265
  }
@@ -181,6 +181,9 @@ module.exports = class CiPlugin extends Plugin {
181
181
  }
182
182
  this.tracer._exporter.getLibraryConfiguration(this.testConfiguration, (err, libraryConfig) => {
183
183
  if (err) {
184
+ this.libraryConfig = undefined
185
+ this.itrCorrelationId = undefined
186
+ this.skippableSuitesCoverage = undefined
184
187
  log.error('Library configuration could not be fetched. %s', err.message)
185
188
  this._addRequestErrorTag(DD_CI_LIBRARY_CONFIGURATION_ERROR_SETTINGS, err)
186
189
  } else {
@@ -955,17 +958,28 @@ module.exports = class CiPlugin extends Plugin {
955
958
  }
956
959
  log.debug('Removing all Dynamic Instrumentation probes')
957
960
  const promises = []
958
- for (const fileLine of this.fileLineToProbeId.keys()) {
959
- const [file, line] = fileLine.split(':')
960
- promises.push(this.removeDiProbe({ file, line }))
961
+ for (const [activeProbeKey, probeId] of this.fileLineToProbeId) {
962
+ promises.push(this.#removeDiProbe(activeProbeKey, probeId))
961
963
  }
962
964
  return Promise.all(promises)
963
965
  }
964
966
 
967
+ /**
968
+ * @param {{ file: string, line: number }} location
969
+ */
965
970
  removeDiProbe ({ file, line }) {
966
- const probeId = this.fileLineToProbeId.get(`${file}:${line}`)
967
- log.warn('Removing probe from %s:%s, with id: %s', file, line, probeId)
968
- this.fileLineToProbeId.delete(probeId)
971
+ const activeProbeKey = `${file}:${line}`
972
+ const probeId = this.fileLineToProbeId.get(activeProbeKey)
973
+ return this.#removeDiProbe(activeProbeKey, probeId)
974
+ }
975
+
976
+ /**
977
+ * @param {string} activeProbeKey
978
+ * @param {string|undefined} probeId
979
+ */
980
+ #removeDiProbe (activeProbeKey, probeId) {
981
+ log.warn('Removing probe from %s, with id: %s', activeProbeKey, probeId)
982
+ this.fileLineToProbeId.delete(activeProbeKey)
969
983
  return this.di.removeProbe(probeId)
970
984
  }
971
985
 
@@ -13,6 +13,7 @@ const {
13
13
  TELEMETRY_GIT_COMMAND_ERRORS,
14
14
  } = require('../../ci-visibility/telemetry')
15
15
  const { storage } = require('../../../../datadog-core')
16
+ const { getSegment } = require('../../util')
16
17
  const {
17
18
  GIT_COMMIT_SHA,
18
19
  GIT_BRANCH,
@@ -251,7 +252,7 @@ function getGitRemoteName () {
251
252
  )
252
253
 
253
254
  if (upstreamRemote) {
254
- return upstreamRemote.split('/')[0]
255
+ return getSegment(upstreamRemote, '/', 0)
255
256
  }
256
257
 
257
258
  const remotes = sanitizedExec(
@@ -263,7 +264,7 @@ function getGitRemoteName () {
263
264
  false
264
265
  )
265
266
 
266
- return remotes.split('\n')[0] || 'origin'
267
+ return getSegment(remotes, '\n', 0) || 'origin'
267
268
  }
268
269
 
269
270
  function getSourceBranch () {
@@ -26,14 +26,27 @@ const supportedProxies = {
26
26
  'aws-apigateway': {
27
27
  spanName: 'aws.apigateway',
28
28
  component: 'aws-apigateway',
29
+ providesTimestamp: true,
29
30
  },
30
31
  'aws-httpapi': {
31
32
  spanName: 'aws.httpapi',
32
33
  component: 'aws-httpapi',
34
+ providesTimestamp: true,
33
35
  },
34
36
  'azure-apim': {
35
37
  spanName: 'azure.apim',
36
38
  component: 'azure-apim',
39
+ providesTimestamp: true,
40
+ },
41
+ 'azure-gw': {
42
+ spanName: 'azure.app-gateway',
43
+ component: 'azure-gw',
44
+ providesTimestamp: false,
45
+ },
46
+ 'azure-fd': {
47
+ spanName: 'azure.frontdoor',
48
+ component: 'azure-fd',
49
+ providesTimestamp: false,
37
50
  },
38
51
  }
39
52
 
@@ -109,21 +122,32 @@ function setInferredProxySpanTags (span, proxyContext) {
109
122
  }
110
123
 
111
124
  function extractInferredProxyContext (headers) {
112
- if (!(PROXY_HEADER_START_TIME_MS in headers)) {
125
+ if (!(PROXY_HEADER_SYSTEM in headers)) {
126
+ return null
127
+ }
128
+ if (!Object.hasOwn(supportedProxies, headers[PROXY_HEADER_SYSTEM])) {
129
+ log.debug('Received headers to create inferred proxy span but headers include an unsupported proxy type: %s',
130
+ headers[PROXY_HEADER_SYSTEM])
131
+
113
132
  return null
114
133
  }
115
134
 
116
- if (!(PROXY_HEADER_SYSTEM in headers && headers[PROXY_HEADER_SYSTEM] in supportedProxies)) {
117
- log.debug('Received headers to create inferred proxy span but headers include an unsupported proxy type', headers)
135
+ const detectedProxy = supportedProxies[headers[PROXY_HEADER_SYSTEM]]
136
+
137
+ if (detectedProxy.providesTimestamp && !headers[PROXY_HEADER_START_TIME_MS]) {
118
138
  return null
119
139
  }
120
140
 
121
141
  return {
122
142
  requestTime: headers[PROXY_HEADER_START_TIME_MS]
123
143
  ? Number.parseInt(headers[PROXY_HEADER_START_TIME_MS], 10)
124
- : null,
144
+ : Date.now(),
125
145
  method: headers[PROXY_HEADER_HTTPMETHOD],
126
- path: headers[PROXY_HEADER_PATH],
146
+ path: headers[PROXY_HEADER_PATH]
147
+ ? (headers[PROXY_HEADER_PATH].startsWith('/')
148
+ ? headers[PROXY_HEADER_PATH]
149
+ : '/' + headers[PROXY_HEADER_PATH])
150
+ : headers[PROXY_HEADER_PATH],
127
151
  stage: headers[PROXY_HEADER_STAGE],
128
152
  domainName: headers[PROXY_HEADER_DOMAIN],
129
153
  proxySystemName: headers[PROXY_HEADER_SYSTEM],
@@ -6,6 +6,7 @@ const { URL } = require('url')
6
6
  const { getLageTestSessionName } = require('../../ci-visibility/lage')
7
7
  const log = require('../../log')
8
8
  const { getEnvironmentVariable } = require('../../config/helper')
9
+ const { getSegment } = require('../../util')
9
10
  const satisfies = require('../../../../../vendor/dist/semifies')
10
11
 
11
12
  const istanbul = require('../../../../../vendor/dist/istanbul-lib-coverage')
@@ -77,6 +78,11 @@ const {
77
78
  *
78
79
  * @typedef {{ service?: string, isServiceUserProvided?: boolean }} TestEnvironmentConfig
79
80
  * @typedef {Record<string, string|number|undefined>} TestEnvironmentMetadata
81
+ * @typedef {{
82
+ * isRumActive?: boolean,
83
+ * browserVersion?: string,
84
+ * testExecutionId?: string
85
+ * }} RumTestCorrelationContext
80
86
  */
81
87
 
82
88
  // session tags
@@ -130,7 +136,7 @@ const CI_APP_ORIGIN = 'ciapp-test'
130
136
  // - Math.random() float values (10+ decimal digits after 0.)
131
137
  const DYNAMIC_NAME_RE = new RegExp(
132
138
  String.raw`\b1[6-9]\d{8,11}\b|` +
133
- String.raw`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|` +
139
+ '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|' +
134
140
  String.raw`\b\d{4}-\d{2}-\d{2}|` +
135
141
  String.raw`(?:localhost|127\.0\.0\.1|0\.0\.0\.0):\d{4,5}\b|` +
136
142
  String.raw`\b0\.\d{10,}`,
@@ -434,6 +440,8 @@ module.exports = {
434
440
  TEST_PARAMETERS,
435
441
  TEST_SKIP_REASON,
436
442
  TEST_IS_RUM_ACTIVE,
443
+ setRumTestCorrelation,
444
+ setRumTestTags,
437
445
  TEST_SOURCE_FILE,
438
446
  TEST_FAILURE_SCREENSHOT_UPLOADED,
439
447
  TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR,
@@ -567,7 +575,7 @@ module.exports = {
567
575
  // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
568
576
  function getPkgManager () {
569
577
  try {
570
- return getEnvironmentVariable('npm_config_user_agent').split(' ')[0].replace('/', '-')
578
+ return getSegment(getEnvironmentVariable('npm_config_user_agent'), ' ', 0).replace('/', '-')
571
579
  } catch {
572
580
  return ''
573
581
  }
@@ -798,6 +806,9 @@ function getTestTypeFromFramework (testFramework) {
798
806
  return 'test'
799
807
  }
800
808
 
809
+ /**
810
+ * @param {import('../../opentracing/span')} span
811
+ */
801
812
  function finishAllTraceSpans (span) {
802
813
  for (const traceSpan of span.context()._trace.started) {
803
814
  if (traceSpan !== span) {
@@ -806,6 +817,49 @@ function finishAllTraceSpans (span) {
806
817
  }
807
818
  }
808
819
 
820
+ /**
821
+ * @param {import('../../opentracing/span')} testSpan
822
+ * @param {boolean|undefined} isRumActive
823
+ * @param {string} [browserVersion]
824
+ * @returns {void}
825
+ */
826
+ function setRumTestTags (testSpan, isRumActive, browserVersion) {
827
+ if (isRumActive) {
828
+ testSpan.setTag(TEST_IS_RUM_ACTIVE, 'true')
829
+ }
830
+ if (browserVersion) {
831
+ testSpan.setTag(TEST_BROWSER_VERSION, browserVersion)
832
+ }
833
+ }
834
+
835
+ /**
836
+ * @param {RumTestCorrelationContext} context
837
+ * @param {import('../../opentracing/span')|undefined} activeSpan
838
+ * @returns {import('../../opentracing/span')|undefined}
839
+ */
840
+ function setRumTestCorrelation (context, activeSpan) {
841
+ if (!activeSpan) return
842
+
843
+ const activeContext = activeSpan.context()
844
+ let testSpan
845
+ if (activeContext.getTag(SPAN_TYPE) === 'test') {
846
+ testSpan = activeSpan
847
+ } else {
848
+ for (const traceSpan of activeContext._trace.started) {
849
+ if (traceSpan.context().getTag(SPAN_TYPE) === 'test') {
850
+ testSpan = traceSpan
851
+ break
852
+ }
853
+ }
854
+ }
855
+
856
+ if (!testSpan) return
857
+
858
+ context.testExecutionId = testSpan.context().toTraceId()
859
+ setRumTestTags(testSpan, context.isRumActive, context.browserVersion)
860
+ return testSpan
861
+ }
862
+
809
863
  function getTestParentSpan (tracer) {
810
864
  return tracer.extract('text_map', {
811
865
  'x-datadog-trace-id': id().toString(10),
@@ -1360,7 +1414,6 @@ function resetCoverage (coverage) {
1360
1414
 
1361
1415
  return coverageMap
1362
1416
  .files()
1363
- // eslint-disable-next-line unicorn/no-array-for-each
1364
1417
  .forEach(filename => {
1365
1418
  const fileCoverage = coverageMap.fileCoverageFor(filename)
1366
1419
  fileCoverage.resetHits()
@@ -1371,7 +1424,6 @@ function mergeCoverage (coverage, targetCoverage) {
1371
1424
  const coverageMap = istanbul.createCoverageMap(coverage)
1372
1425
  return coverageMap
1373
1426
  .files()
1374
- // eslint-disable-next-line unicorn/no-array-for-each
1375
1427
  .forEach(filename => {
1376
1428
  const fileCoverage = coverageMap.fileCoverageFor(filename)
1377
1429
 
@@ -59,7 +59,7 @@ class NativeSpaceProfiler {
59
59
  }
60
60
 
61
61
  profile (restart) {
62
- const profile = this.#pprof.heap.profile(undefined, this.#mapper, getThreadLabels)
62
+ const profile = this.#pprof.heap.profile(undefined, this.#mapper, getThreadLabels, 'pack')
63
63
  if (!restart) {
64
64
  this.stop()
65
65
  }
@@ -187,6 +187,7 @@ class NativeWallProfiler {
187
187
 
188
188
  this.#pprof.time.start({
189
189
  collectCpuTime: this.#cpuProfilingEnabled,
190
+ columnNumbers: 'pack',
190
191
  durationMillis: this.#flushIntervalMillis,
191
192
  intervalMicros: this.#samplingIntervalMicros,
192
193
  lineNumbers: false,