dd-trace 6.10.0 → 6.11.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 (137) hide show
  1. package/README.md +0 -17
  2. package/index.d.ts +108 -3
  3. package/initialize.mjs +28 -17
  4. package/openfeature.js +2 -2
  5. package/package.json +11 -8
  6. package/packages/datadog-esbuild/index.js +0 -34
  7. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  8. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  9. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  10. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  11. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  12. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  13. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  14. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  15. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  16. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  17. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  18. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
  19. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  20. package/packages/datadog-instrumentations/src/jest.js +25 -17
  21. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  22. package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
  23. package/packages/datadog-instrumentations/src/mocha/utils.js +130 -17
  24. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  25. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  26. package/packages/datadog-instrumentations/src/router.js +2 -0
  27. package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
  28. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  29. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  30. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
  31. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  32. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  33. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  34. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  35. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  36. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  37. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
  38. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  39. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  40. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  41. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  42. package/packages/datadog-plugin-electron/src/index.js +2 -0
  43. package/packages/datadog-plugin-fs/src/index.js +1 -1
  44. package/packages/datadog-plugin-jest/src/index.js +6 -0
  45. package/packages/datadog-plugin-mocha/src/index.js +56 -1
  46. package/packages/datadog-plugin-nats/src/index.js +1 -1
  47. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  48. package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
  49. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  50. package/packages/datadog-plugin-vitest/src/index.js +6 -0
  51. package/packages/datadog-webpack/index.js +0 -11
  52. package/packages/dd-trace/index.js +0 -1
  53. package/packages/dd-trace/src/agent/info.js +6 -5
  54. package/packages/dd-trace/src/aiguard/client.js +100 -1
  55. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  56. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  57. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  58. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  59. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  60. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  61. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  62. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  63. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  64. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  65. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  66. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  67. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  68. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  69. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  70. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  71. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  72. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  73. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  74. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  75. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  76. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  77. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  78. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  79. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  80. package/packages/dd-trace/src/config/index.js +13 -2
  81. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  82. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  83. package/packages/dd-trace/src/datastreams/index.js +2 -0
  84. package/packages/dd-trace/src/datastreams/manager.js +9 -2
  85. package/packages/dd-trace/src/datastreams/processor.js +15 -13
  86. package/packages/dd-trace/src/datastreams/size.js +5 -0
  87. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  88. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  89. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  90. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  91. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  92. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  93. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  94. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  95. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  96. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  97. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  98. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  99. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  100. package/packages/dd-trace/src/noop/proxy.js +3 -4
  101. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  102. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  103. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  104. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  105. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  106. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  107. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  108. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  109. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  110. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  111. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  112. package/packages/dd-trace/src/plugin_manager.js +3 -3
  113. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  114. package/packages/dd-trace/src/plugins/index.js +1 -0
  115. package/packages/dd-trace/src/plugins/util/jest.js +4 -3
  116. package/packages/dd-trace/src/plugins/util/test.js +9 -9
  117. package/packages/dd-trace/src/plugins/util/web.js +3 -0
  118. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  119. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  120. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  121. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  122. package/packages/dd-trace/src/proxy.js +38 -29
  123. package/packages/dd-trace/src/ritm.js +9 -2
  124. package/packages/dd-trace/src/serverless.js +37 -0
  125. package/packages/dd-trace/src/span_format.js +1 -0
  126. package/packages/dd-trace/src/span_stats.js +30 -3
  127. package/packages/dd-trace/src/storage-channels.js +86 -0
  128. package/packages/dd-trace/src/tracer.js +9 -2
  129. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  130. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  131. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  132. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  133. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  134. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  135. package/packages/dd-trace/src/feature-registry.js +0 -29
  136. package/packages/dd-trace/src/openfeature/register.js +0 -35
  137. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -127,7 +127,7 @@ class TestWorkerCiVisibilityExporter {
127
127
  }
128
128
 
129
129
  /**
130
- * @param {() => void} [onDone]
130
+ * @param {(error?: Error) => void} [onDone]
131
131
  */
132
132
  flush (onDone) {
133
133
  if (!onDone) {
@@ -141,9 +141,11 @@ class TestWorkerCiVisibilityExporter {
141
141
  }
142
142
 
143
143
  let pendingWriters = this._telemetryWriter ? 4 : 3
144
- const onWriterFlushed = () => {
144
+ let flushError
145
+ const onWriterFlushed = (error) => {
146
+ flushError ||= error
145
147
  pendingWriters--
146
- if (pendingWriters === 0) onDone()
148
+ if (pendingWriters === 0) onDone(flushError)
147
149
  }
148
150
 
149
151
  this._writer.flush(onWriterFlushed)
@@ -21,7 +21,7 @@ class Writer {
21
21
  }
22
22
 
23
23
  /**
24
- * @param {() => void} [onDone]
24
+ * @param {(error?: Error) => void} [onDone]
25
25
  */
26
26
  flush (onDone) {
27
27
  const count = this._encoder.count()
@@ -69,8 +69,9 @@ class Writer {
69
69
 
70
70
  // child_process workers (jest default, cucumber)
71
71
  if (process.send) {
72
- process.send(payload, () => {
73
- onDone()
72
+ process.send(payload, (error) => {
73
+ if (error) log.error('Error sending message to parent process', error)
74
+ onDone(error)
74
75
  })
75
76
  return
76
77
  }
@@ -82,9 +83,12 @@ class Writer {
82
83
  parentPort.postMessage(payload)
83
84
  } catch (error) {
84
85
  log.error('Error posting message to parent port', error)
85
- } finally {
86
- onDone()
86
+ onDone(error)
87
+ return
87
88
  }
89
+ // postMessage has no acknowledgement callback. Completion means the
90
+ // message was accepted by the local port, not processed by the parent.
91
+ onDone()
88
92
  return
89
93
  }
90
94
 
@@ -34,10 +34,11 @@ function parseSkippableSuitesResponse (
34
34
  if (validateRequiredFields) {
35
35
  validateSkippableTestsResponse(parsedResponse, { validationMode })
36
36
  }
37
- const coverage = {}
37
+ let coverage
38
38
  const coverageByFilename = parsedResponse.meta?.coverage
39
39
  if (coverageByFilename) {
40
40
  for (const [filename, bitmap] of Object.entries(coverageByFilename)) {
41
+ coverage ??= {}
41
42
  coverage[filename.replaceAll('\\', '/')] = bitmap
42
43
  }
43
44
  }
@@ -0,0 +1,39 @@
1
+ 'use strict'
2
+
3
+ const EPOCH_SECONDS_THRESHOLD = 1_000_000_000
4
+
5
+ /**
6
+ * Returns the delay requested by a Test Optimization intake rate-limit response.
7
+ *
8
+ * @param {import('node:http').IncomingHttpHeaders} [headers]
9
+ * @returns {number}
10
+ */
11
+ function getRateLimitResetDelay (headers) {
12
+ let retryAfter = headers?.['retry-after']
13
+ if (Array.isArray(retryAfter)) retryAfter = retryAfter[0]
14
+ if (typeof retryAfter === 'string' && retryAfter.trim() !== '') {
15
+ const delaySeconds = Number(retryAfter)
16
+ if (Number.isFinite(delaySeconds)) {
17
+ if (delaySeconds >= 0) return delaySeconds * 1000
18
+ } else {
19
+ const resetTimestamp = Date.parse(retryAfter)
20
+ if (Number.isFinite(resetTimestamp)) return Math.max(0, resetTimestamp - Date.now())
21
+ }
22
+ }
23
+
24
+ let reset = headers?.['x-ratelimit-reset']
25
+ if (Array.isArray(reset)) reset = reset[0]
26
+ if (typeof reset !== 'string' || reset.trim() === '') return NaN
27
+
28
+ const resetSeconds = Number(reset)
29
+ if (!Number.isFinite(resetSeconds) || resetSeconds < 0) return NaN
30
+
31
+ // Datadog defines this header as delay seconds. Preserve compatibility with
32
+ // realistic Unix timestamps without confusing ordinary durations with epochs.
33
+ if (resetSeconds >= EPOCH_SECONDS_THRESHOLD) {
34
+ return Math.max(0, resetSeconds * 1000 - Date.now())
35
+ }
36
+ return resetSeconds * 1000
37
+ }
38
+
39
+ module.exports = { getRateLimitResetDelay }
@@ -13,12 +13,13 @@ const {
13
13
  singleJitteredDelay,
14
14
  } = require('../../exporters/common/retry')
15
15
  const { parseUrl } = require('../../exporters/common/url')
16
+ const { getRateLimitResetDelay } = require('./rate-limit')
16
17
 
17
18
  const legacyStorage = storage('legacy')
18
19
 
19
20
  /**
20
21
  * Simplified HTTP request for test optimization (library config). Uses common HTTP agents.
21
- * Retries: 429 (with X-ratelimit-reset, max 30s wait),
22
+ * Retries: 429 (with Retry-After or X-RateLimit-Reset, max 30s wait),
22
23
  * >=500 and transient network errors (5–7.5s delay with jitter). Max one retry.
23
24
  * Destroys connections on errors to prevent reuse of bad connections. Preserves
24
25
  * original status code across retries for telemetry.
@@ -98,11 +99,7 @@ function request (data, options, callback) {
98
99
  }
99
100
 
100
101
  if (res.statusCode === 429 && !hasRetried) {
101
- const resetHeader = res.headers['x-ratelimit-reset']
102
- const resetTs = (resetHeader === null || resetHeader === undefined)
103
- ? NaN
104
- : Number.parseInt(resetHeader, 10)
105
- const waitMs = Number.isFinite(resetTs) ? Math.max(0, resetTs * 1000 - Date.now()) : NaN
102
+ const waitMs = getRateLimitResetDelay(res.headers)
106
103
 
107
104
  if (Number.isFinite(waitMs) && waitMs <= RATE_LIMIT_MAX_WAIT_MS) {
108
105
  hasRetried = true
@@ -4,8 +4,8 @@ const { readFileSync } = require('node:fs')
4
4
  const { extname } = require('node:path')
5
5
 
6
6
  const getConfig = require('../../config')
7
- const request = require('../../exporters/common/request')
8
7
  const log = require('../../log')
8
+ const request = require('../exporters/request')
9
9
 
10
10
  const UPLOAD_TIMEOUT_MS = 30_000
11
11
  const TEST_SCREENSHOT_ENDPOINT_PREFIX = '/api/v2/ci/test-runs/'
@@ -74,10 +74,12 @@ function toIdempotencyQueryValue (idempotencyKey) {
74
74
  * @param {URL} options.url - The base URL for the screenshot upload
75
75
  * @param {boolean} [options.isEvpProxy] - Whether to upload through the Agent's evp_proxy
76
76
  * @param {string} [options.evpProxyPrefix] - The evp_proxy path prefix (e.g. '/evp_proxy/v4')
77
+ * @param {number} [options.deadline] - Absolute finalization deadline in epoch milliseconds
78
+ * @param {AbortSignal} [options.signal] - Signal used to cancel the upload
77
79
  * @param {Function} callback - Callback function (err)
78
80
  */
79
81
  function uploadTestScreenshot (
80
- { filePath, traceId, idempotencyKey, capturedAtMs, url, isEvpProxy, evpProxyPrefix },
82
+ { filePath, traceId, idempotencyKey, capturedAtMs, url, isEvpProxy, evpProxyPrefix, deadline, signal },
81
83
  callback
82
84
  ) {
83
85
  const { DD_API_KEY } = getConfig()
@@ -125,6 +127,8 @@ function uploadTestScreenshot (
125
127
  path: `${basePath}?${query}`,
126
128
  timeout: UPLOAD_TIMEOUT_MS,
127
129
  url,
130
+ deadline,
131
+ signal,
128
132
  }
129
133
 
130
134
  if (isEvpProxy) {
@@ -209,6 +209,7 @@ export interface GeneratedConfig {
209
209
  DD_TRACE_BEAUTIFUL_LOGS: boolean;
210
210
  DD_TRACE_BLUEBIRD_ENABLED: boolean;
211
211
  DD_TRACE_BODY_PARSER_ENABLED: boolean;
212
+ DD_TRACE_BROWSER_BUNYAN_ENABLED: boolean;
212
213
  DD_TRACE_BSON_ENABLED: boolean;
213
214
  DD_TRACE_BULLMQ_ENABLED: boolean;
214
215
  DD_TRACE_BUNYAN_ENABLED: boolean;
@@ -340,6 +341,7 @@ export interface GeneratedConfig {
340
341
  DD_TRACE_OPENSEARCH_PROJECT_OPENSEARCH_ENABLED: boolean;
341
342
  DD_TRACE_OPENTELEMETRY_SDK_TRACE_NODE_ENABLED: boolean;
342
343
  DD_TRACE_ORACLEDB_ENABLED: boolean;
344
+ DD_TRACE_OTEL_CTX_ENABLED: boolean;
343
345
  DD_TRACE_OTEL_ENABLED: boolean;
344
346
  DD_TRACE_OTEL_SEMANTICS_ENABLED: boolean;
345
347
  DD_TRACE_PASSPORT_ENABLED: boolean;
@@ -894,6 +896,7 @@ export interface GeneratedEnvVarConfig {
894
896
  DD_TRACE_BEAUTIFUL_LOGS: boolean;
895
897
  DD_TRACE_BLUEBIRD_ENABLED: boolean;
896
898
  DD_TRACE_BODY_PARSER_ENABLED: boolean;
899
+ DD_TRACE_BROWSER_BUNYAN_ENABLED: boolean;
897
900
  DD_TRACE_BSON_ENABLED: boolean;
898
901
  DD_TRACE_BULLMQ_ENABLED: boolean;
899
902
  DD_TRACE_BUNYAN_ENABLED: boolean;
@@ -1040,6 +1043,7 @@ export interface GeneratedEnvVarConfig {
1040
1043
  DD_TRACE_OPENSEARCH_PROJECT_OPENSEARCH_ENABLED: boolean;
1041
1044
  DD_TRACE_OPENTELEMETRY_SDK_TRACE_NODE_ENABLED: boolean;
1042
1045
  DD_TRACE_ORACLEDB_ENABLED: boolean;
1046
+ DD_TRACE_OTEL_CTX_ENABLED: boolean;
1043
1047
  DD_TRACE_OTEL_ENABLED: boolean;
1044
1048
  DD_TRACE_OTEL_SEMANTICS_ENABLED: boolean;
1045
1049
  DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: number;
@@ -15,6 +15,7 @@ const { isTrue } = require('../util')
15
15
  const telemetry = require('../telemetry')
16
16
  const telemetryMetrics = require('../telemetry/metrics')
17
17
  const {
18
+ getServerlessPlatformTags,
18
19
  IS_SERVERLESS,
19
20
  getIsGCPFunction,
20
21
  getIsAzureFunction,
@@ -477,9 +478,13 @@ class Config extends ConfigBase {
477
478
 
478
479
  // Apply the OTel sampler when the user opted into OTel traces or explicitly set the sampler.
479
480
  // OTEL_TRACES_SAMPLER has `default: parentbased_always_on` (per OTel spec), so opt-in users
480
- // that don't set the sampler still get parent-based sampling.
481
+ // that don't set the sampler still get parent-based sampling. Electron exporter spans go over
482
+ // the Electron SDK's IPC bridge rather than OTLP (see opentracing/tracer.js), so an
483
+ // OTEL_TRACES_EXPORTER=otlp set for an unrelated telemetry pipeline shouldn't also override
484
+ // dd-trace's own sampling policy in that case.
481
485
  if (!trackedConfigOrigins.has('sampleRate') &&
482
- (trackedConfigOrigins.has('OTEL_TRACES_SAMPLER') || this.OTEL_TRACES_EXPORTER === 'otlp')) {
486
+ (trackedConfigOrigins.has('OTEL_TRACES_SAMPLER') ||
487
+ (this.OTEL_TRACES_EXPORTER === 'otlp' && this.experimental.exporter !== 'electron'))) {
483
488
  setAndTrack(this, 'sampleRate',
484
489
  getFromOtelSamplerMap(this.OTEL_TRACES_SAMPLER, this.OTEL_TRACES_SAMPLER_ARG))
485
490
  }
@@ -597,6 +602,12 @@ class Config extends ConfigBase {
597
602
  this.tags.version = this.version
598
603
  }
599
604
  this.tags['runtime-id'] = RUNTIME_ID
605
+ const platformTags = getServerlessPlatformTags()
606
+ if (platformTags) {
607
+ for (let i = 0; i < platformTags.length; i += 2) {
608
+ this.tags[platformTags[i]] ??= platformTags[i + 1]
609
+ }
610
+ }
600
611
 
601
612
  if (IS_SERVERLESS) {
602
613
  setAndTrack(this, 'telemetry.DD_INSTRUMENTATION_TELEMETRY_ENABLED', false)
@@ -58,6 +58,13 @@ function applyMajorOverrides (supportedConfigurations, majorVersion) {
58
58
  delete supportedConfigurations.DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED
59
59
  // eslint-disable-next-line eslint-rules/eslint-env-aliases
60
60
  dropAlias(supportedConfigurations.DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED, 'DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED')
61
+
62
+ if (majorVersion >= 7) {
63
+ // The Electron plugin moved to the Electron SDK and is opt-in (disabled by default) from v7 on,
64
+ // while remaining enabled by default on earlier majors (see ElectronPlugin.optIn).
65
+ const electronEntry = supportedConfigurations.DD_TRACE_ELECTRON_ENABLED?.[0]
66
+ if (electronEntry) electronEntry.default = 'false'
67
+ }
61
68
  }
62
69
 
63
70
  /**
@@ -2452,6 +2452,13 @@
2452
2452
  "default": "true"
2453
2453
  }
2454
2454
  ],
2455
+ "DD_TRACE_BROWSER_BUNYAN_ENABLED": [
2456
+ {
2457
+ "implementation": "A",
2458
+ "type": "boolean",
2459
+ "default": "true"
2460
+ }
2461
+ ],
2455
2462
  "DD_TRACE_BSON_ENABLED": [
2456
2463
  {
2457
2464
  "implementation": "A",
@@ -3518,6 +3525,13 @@
3518
3525
  "default": "false"
3519
3526
  }
3520
3527
  ],
3528
+ "DD_TRACE_OTEL_CTX_ENABLED": [
3529
+ {
3530
+ "implementation": "A",
3531
+ "type": "boolean",
3532
+ "default": "false"
3533
+ }
3534
+ ],
3521
3535
  "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS": [
3522
3536
  {
3523
3537
  "implementation": "B",
@@ -5,6 +5,7 @@ const {
5
5
  getHeadersSize,
6
6
  getMessageSize,
7
7
  getSizeOrZero,
8
+ PATHWAY_FIELD_BYTES,
8
9
  } = require('./size')
9
10
 
10
11
  // This is only needed because DSM code is spread across existing tracing
@@ -121,6 +122,7 @@ module.exports = {
121
122
  DataStreamsManager,
122
123
  DataStreamsProcessor,
123
124
  SchemaBuilder,
125
+ PATHWAY_FIELD_BYTES,
124
126
 
125
127
  // These are small functions so they are exposed directly and not lazy loaded.
126
128
  getAmqpMessageSize,
@@ -8,9 +8,16 @@ class DataStreamsManager {
8
8
  this._dataStreamsProcessor = processor
9
9
  }
10
10
 
11
- setCheckpoint (edgeTags, span, payloadSize = 0) {
11
+ /**
12
+ * @param {string[]} edgeTags
13
+ * @param {import('../opentracing/span')|null} span
14
+ * @param {number} [payloadSize]
15
+ * @param {number} [pathwayContextSize] See `DataStreamsProcessor#setCheckpoint`.
16
+ * @returns {object|undefined}
17
+ */
18
+ setCheckpoint (edgeTags, span, payloadSize = 0, pathwayContextSize) {
12
19
  const ctx = this._dataStreamsProcessor.setCheckpoint(
13
- edgeTags, span, DataStreamsContext.getDataStreamsContext(), payloadSize
20
+ edgeTags, span, DataStreamsContext.getDataStreamsContext(), payloadSize, pathwayContextSize
14
21
  )
15
22
  DataStreamsContext.setDataStreamsContext(ctx)
16
23
  return ctx
@@ -5,25 +5,17 @@ const pkg = require('../../../../package.json')
5
5
 
6
6
  const { LogCollapsingLowestDenseDDSketch } = require('../../../../vendor/dist/@datadog/sketches-js')
7
7
  const { PATHWAY_HASH, DSM_TRANSACTION_ID, DSM_TRANSACTION_CHECKPOINT } = require('../../../../ext/tags')
8
- const { dsmBase64NameLength } = require('../carrier')
9
8
  const log = require('../log')
10
9
  const processTags = require('../process-tags')
11
10
  const propagationHash = require('../propagation-hash')
12
11
  const { computePathwayHash } = require('./pathway')
13
12
  const { DataStreamsWriter } = require('./writer')
14
- const { getAmqpMessageSize, getHeadersSize, getMessageSize, getSizeOrZero } = require('./size')
13
+ const { getAmqpMessageSize, getHeadersSize, getMessageSize, getSizeOrZero, PATHWAY_FIELD_BYTES } = require('./size')
15
14
  const { SchemaBuilder } = require('./schemas/schema_builder')
16
15
  const { SchemaSampler } = require('./schemas/schema_sampler')
17
16
 
18
17
  const ENTRY_PARENT_HASH = Buffer.from('0000000000000000', 'hex')
19
18
 
20
- // A direction:out checkpoint estimates the size cost of the header the
21
- // producer plugin will inject. The pathway context is always 20 binary
22
- // bytes, encoded as 28 base64 chars; together with the header key and
23
- // JSON framing (matching the prior `JSON.stringify({key: value})` byte
24
- // count minus 1), this is a fixed value.
25
- const PATHWAY_HEADER_BYTES = dsmBase64NameLength + 28 + 6
26
-
27
19
  class StatsPoint {
28
20
  constructor (hash, parentHash, edgeTags) {
29
21
  this.hash = hash.readBigUInt64LE()
@@ -290,10 +282,19 @@ class DataStreamsProcessor {
290
282
  if (this.flushInterval === 0) this.onInterval()
291
283
  }
292
284
 
293
- setCheckpoint (edgeTags, span, ctx, payloadSize = 0) {
285
+ /**
286
+ * @param {string[]} edgeTags Direction tag first.
287
+ * @param {import('../opentracing/span')|null} span
288
+ * @param {object|null|undefined} ctx Parent pathway context.
289
+ * @param {number} [payloadSize] Bytes the caller built, before any propagation context.
290
+ * @param {number} [pathwayContextSize] Bytes the pathway context adds to an outbound payload.
291
+ * Most producers size their payload before this call hands them the context to inject, so they
292
+ * cannot measure it and take the default estimate. Pass `0` to report the payload alone.
293
+ * @returns {object|undefined}
294
+ */
295
+ setCheckpoint (edgeTags, span, ctx, payloadSize = 0, pathwayContextSize = PATHWAY_FIELD_BYTES) {
294
296
  if (!this.enabled) return
295
297
  const nowNs = Date.now() * 1e6
296
- // Callers must place the direction tag at index 0.
297
298
  const direction = edgeTags[0]
298
299
  let pathwayStartNs = nowNs
299
300
  let edgeStartNs = nowNs
@@ -338,14 +339,15 @@ class DataStreamsProcessor {
338
339
  const pathwayLatencyNs = nowNs - pathwayStartNs
339
340
  const dataStreamsContext = {
340
341
  hash,
341
- edgeStartNs,
342
+ // start the next hop's edge clock here so edge latency stays per-hop
343
+ edgeStartNs: nowNs,
342
344
  pathwayStartNs,
343
345
  previousDirection: direction,
344
346
  closestOppositeDirectionHash,
345
347
  closestOppositeDirectionEdgeStart,
346
348
  }
347
349
  if (direction === 'direction:out') {
348
- payloadSize += PATHWAY_HEADER_BYTES
350
+ payloadSize += pathwayContextSize
349
351
  }
350
352
  const checkpoint = {
351
353
  currentTimestamp: nowNs,
@@ -2,6 +2,10 @@
2
2
 
3
3
  const { types } = require('util')
4
4
 
5
+ // Byte cost of the `,"dd-pathway-ctx-base64":"<28 chars>"` field a producer adds to a JSON carrier.
6
+ // A literal so eager `size` consumers don't pull in `./pathway`; `pathway.spec.js` pins it there.
7
+ const PATHWAY_FIELD_BYTES = 55
8
+
5
9
  function getSizeOrZero (obj) {
6
10
  if (typeof obj === 'string') {
7
11
  return Buffer.byteLength(obj, 'utf8')
@@ -54,4 +58,5 @@ module.exports = {
54
58
  getHeadersSize,
55
59
  getSizeOrZero,
56
60
  getAmqpMessageSize,
61
+ PATHWAY_FIELD_BYTES,
57
62
  }
@@ -23,7 +23,6 @@ require('./remote_config')
23
23
 
24
24
  /** @typedef {import('node:inspector').Debugger.EvaluateOnCallFrameReturnType} EvaluateOnCallFrameResult */
25
25
 
26
- // Expression to run on a call frame of the paused thread to get its active trace and span id.
27
26
  const templateExpressionSetupCode = `
28
27
  const $dd_inspect = global.require('node:util').inspect;
29
28
  const $dd_segmentInspectOptions = {
@@ -34,8 +33,10 @@ const templateExpressionSetupCode = `
34
33
  breakLength: Infinity
35
34
  };
36
35
  `
36
+
37
+ // Expression to run on a call frame of the paused thread to get its active trace and span id.
37
38
  const getDDTagsExpression = `(() => {
38
- const context = global.require('dd-trace').scope().active()?.context();
39
+ const context = globalThis._ddtrace.scope().active()?.context();
39
40
  return { trace_id: context?.toTraceId(), span_id: context?.toSpanId() }
40
41
  })()`
41
42
 
@@ -3,7 +3,7 @@
3
3
  const { inspect } = require('node:util')
4
4
  const { channel } = require('dc-polyfill')
5
5
 
6
- const request = require('../common/request')
6
+ const commonRequest = require('../common/request')
7
7
  const { logIntegrations, logAgentError } = require('../../startup-log')
8
8
  const runtimeMetrics = require('../../runtime_metrics')
9
9
  const log = require('../../log')
@@ -15,12 +15,15 @@ const METRIC_PREFIX = 'datadog.tracer.node.exporter.agent'
15
15
  const firstFlushChannel = channel('dd-trace:exporter:first-flush')
16
16
 
17
17
  class AgentWriter extends BaseWriter {
18
+ #request = commonRequest
19
+ #requestTracker
20
+
18
21
  constructor (...args) {
19
22
  super({
20
23
  ...args[0],
21
24
  beforeFirstFlush: () => firstFlushChannel.publish(),
22
25
  })
23
- const { prioritySampler, lookup, protocolVersion, headers } = args[0]
26
+ const { prioritySampler, lookup, protocolVersion, headers, isTestOptimization } = args[0]
24
27
  const AgentEncoder = getEncoder(protocolVersion)
25
28
 
26
29
  this._prioritySampler = prioritySampler
@@ -28,13 +31,33 @@ class AgentWriter extends BaseWriter {
28
31
  this._protocolVersion = protocolVersion
29
32
  this._headers = headers
30
33
  this._encoder = new AgentEncoder(this)
34
+ if (isTestOptimization) {
35
+ this.#request = require('../../ci-visibility/exporters/request')
36
+ const TestOptimizationRequestTracker = require('../../ci-visibility/exporters/agentless/request-tracker')
37
+ this.#requestTracker = new TestOptimizationRequestTracker(this)
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Flushes payloads, including requests already in flight during Test Optimization finalization.
43
+ *
44
+ * @param {(error?: Error) => void} [done]
45
+ * @param {{ deadline?: number }} [options]
46
+ * @returns {void}
47
+ */
48
+ flush (done, options) {
49
+ if (this.#requestTracker) {
50
+ this.#requestTracker.flush(done, options)
51
+ return
52
+ }
53
+ super.flush(done, options)
31
54
  }
32
55
 
33
- _sendPayload (data, count, done) {
56
+ _sendPayload (data, count, done, flushOptions) {
34
57
  runtimeMetrics.increment(`${METRIC_PREFIX}.requests`, true)
35
58
 
36
59
  const { _headers, _lookup, _protocolVersion, _url } = this
37
- makeRequest(_protocolVersion, data, count, _url, _headers, _lookup, (err, res, status, headers) => {
60
+ const onResponse = (err, res, status, headers) => {
38
61
  if (status) {
39
62
  runtimeMetrics.increment(`${METRIC_PREFIX}.responses`, true)
40
63
  runtimeMetrics.increment(`${METRIC_PREFIX}.responses.by.status`, `status:${status}`, true)
@@ -49,7 +72,7 @@ class AgentWriter extends BaseWriter {
49
72
 
50
73
  if (err) {
51
74
  log.errorWithoutTelemetry('Error sending payload to the agent (status code: %s)', err.status, err)
52
- done()
75
+ done(flushOptions?.deadline === undefined ? undefined : err)
53
76
  return
54
77
  }
55
78
 
@@ -74,7 +97,19 @@ class AgentWriter extends BaseWriter {
74
97
  runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${e.name}`, true)
75
98
  }
76
99
  done()
77
- })
100
+ }
101
+ makeRequest(
102
+ _protocolVersion,
103
+ data,
104
+ count,
105
+ _url,
106
+ _headers,
107
+ _lookup,
108
+ flushOptions,
109
+ this.#request,
110
+ this.#requestTracker,
111
+ onResponse
112
+ )
78
113
  }
79
114
  }
80
115
 
@@ -84,7 +119,7 @@ function getEncoder (protocolVersion) {
84
119
  : require('../../encode/0.4').AgentEncoder
85
120
  }
86
121
 
87
- function makeRequest (version, data, count, url, headers, lookup, cb) {
122
+ function makeRequest (version, data, count, url, headers, lookup, flushOptions, request, requestTracker, cb) {
88
123
  const options = {
89
124
  path: `/v${version}/traces`,
90
125
  method: 'PUT',
@@ -100,16 +135,21 @@ function makeRequest (version, data, count, url, headers, lookup, cb) {
100
135
  lookup,
101
136
  url,
102
137
  }
138
+ if (flushOptions?.deadline !== undefined) {
139
+ options.deadline = flushOptions.deadline
140
+ }
103
141
 
104
142
  log.debug('Request to the agent: %j', options)
105
143
 
106
- request(data, options, (err, res, status, headers) => {
144
+ const onResponse = (err, res, status, headers) => {
107
145
  logIntegrations()
108
146
  if (status !== 404 && status !== 200 && err) {
109
147
  logAgentError({ status, message: err.message ?? inspect(err) })
110
148
  }
111
149
  cb(err, res, status, headers)
112
- })
150
+ }
151
+ if (requestTracker) requestTracker.send(request, data, options, onResponse)
152
+ else request(data, options, onResponse)
113
153
  }
114
154
 
115
155
  module.exports = AgentWriter
@@ -24,23 +24,25 @@ class BufferingExporter {
24
24
  this._export(trace)
25
25
  }
26
26
 
27
- _export (payload, writer = this._writer, timerKey = '_timer') {
27
+ _export (payload, writer = this._writer, timerKey = '_timer', deferImmediateFlush = false) {
28
28
  if (this._config.isCiVisibility) {
29
29
  incrementCountMetric(TELEMETRY_EVENTS_ENQUEUED_FOR_SERIALIZATION, {}, payload.length)
30
30
  }
31
- writer.append(payload)
31
+ const appended = writer.append(payload)
32
32
 
33
33
  const { flushInterval } = this._config
34
34
 
35
- if (flushInterval === 0) {
35
+ if (flushInterval === 0 && !deferImmediateFlush) {
36
36
  writer.flush()
37
- } else if (this[timerKey] === undefined) {
37
+ } else if (flushInterval !== 0 && this[timerKey] === undefined) {
38
38
  this[timerKey] = setTimeout(() => {
39
39
  writer.flush()
40
40
  this[timerKey] = undefined
41
41
  }, flushInterval)
42
42
  this[timerKey].unref?.()
43
43
  }
44
+
45
+ return appended
44
46
  }
45
47
 
46
48
  getUncodedTraces () {
@@ -17,10 +17,10 @@ class Writer {
17
17
 
18
18
  #isFirstFlush = true
19
19
 
20
- flush (done = () => {}) {
20
+ flush (done = () => {}, options) {
21
21
  const count = this._encoder.count()
22
22
 
23
- if (!request.writable) {
23
+ if (!request.writable && options?.deadline === undefined) {
24
24
  this._encoder.reset()
25
25
  done()
26
26
  } else if (count > 0) {
@@ -44,23 +44,28 @@ class Writer {
44
44
  done()
45
45
  return
46
46
  }
47
- this._sendPayload(payload, count, done)
47
+ if (options === undefined) {
48
+ this._sendPayload(payload, count, done)
49
+ } else {
50
+ this._sendPayload(payload, count, done, options)
51
+ }
48
52
  } else {
49
53
  done()
50
54
  }
51
55
  }
52
56
 
53
- append (payload) {
54
- if (!request.writable) {
57
+ append (payload, options) {
58
+ if (!request.writable && options?.deadline === undefined) {
55
59
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
56
60
  log.debug(() => `Maximum number of active requests reached. Payload discarded: ${safeJSONStringify(payload)}`)
57
- return
61
+ return false
58
62
  }
59
63
 
60
64
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
61
65
  log.debug(() => `Encoding payload: ${safeJSONStringify(payload)}`)
62
66
 
63
67
  this._encode(payload)
68
+ return true
64
69
  }
65
70
 
66
71
  _encode (payload) {