dd-trace 5.124.0 → 5.126.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 (208) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  8. package/ci/test-optimization-validation/package-check.js +1 -1
  9. package/ci/test-optimization-validation/report-writer.js +7 -5
  10. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  11. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  12. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  13. package/index.d.ts +8 -0
  14. package/package.json +13 -10
  15. package/packages/datadog-esbuild/index.js +3 -1
  16. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  17. package/packages/datadog-instrumentations/src/fs.js +24 -20
  18. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  19. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  20. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  21. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  22. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +14 -5
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
  33. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  34. package/packages/datadog-instrumentations/src/jest.js +166 -45
  35. package/packages/datadog-instrumentations/src/knex.js +76 -0
  36. package/packages/datadog-instrumentations/src/mariadb.js +6 -0
  37. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  38. package/packages/datadog-instrumentations/src/mocha/main.js +66 -20
  39. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  40. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  41. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  42. package/packages/datadog-instrumentations/src/net.js +14 -17
  43. package/packages/datadog-instrumentations/src/next.js +212 -62
  44. package/packages/datadog-instrumentations/src/openai.js +42 -40
  45. package/packages/datadog-instrumentations/src/pg.js +15 -0
  46. package/packages/datadog-instrumentations/src/playwright.js +113 -13
  47. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  48. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  49. package/packages/datadog-instrumentations/src/undici.js +53 -4
  50. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
  51. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  52. package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
  53. package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
  54. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  55. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  56. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  58. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  59. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  60. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  61. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  62. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  63. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  64. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  65. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  66. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  67. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  68. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  69. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  70. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  71. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  72. package/packages/datadog-plugin-jest/src/index.js +0 -6
  73. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  74. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  75. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  76. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  77. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  78. package/packages/datadog-plugin-next/src/index.js +63 -29
  79. package/packages/datadog-plugin-playwright/src/index.js +167 -27
  80. package/packages/datadog-plugin-undici/src/index.js +27 -41
  81. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  82. package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
  83. package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
  84. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  85. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  86. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  87. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  88. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  89. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  90. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  91. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  92. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
  93. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -95
  94. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  95. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  96. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  97. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  100. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
  101. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  102. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  103. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  104. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
  105. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  106. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  107. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  108. package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
  109. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  110. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  111. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
  112. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  113. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +180 -0
  114. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  115. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  116. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  117. package/packages/dd-trace/src/config/index.js +73 -13
  118. package/packages/dd-trace/src/config/parsers.js +11 -1
  119. package/packages/dd-trace/src/config/stable.js +3 -3
  120. package/packages/dd-trace/src/config/supported-configurations.json +22 -2
  121. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  122. package/packages/dd-trace/src/debugger/config.js +12 -1
  123. package/packages/dd-trace/src/debugger/constants.js +1 -0
  124. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  125. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +28 -0
  126. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
  127. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  128. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  129. package/packages/dd-trace/src/debugger/index.js +23 -3
  130. package/packages/dd-trace/src/dogstatsd.js +510 -76
  131. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  132. package/packages/dd-trace/src/encode/0.4.js +5 -5
  133. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  134. package/packages/dd-trace/src/evp_proxy/direct.js +3 -25
  135. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  136. package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
  137. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  138. package/packages/dd-trace/src/exporters/agentless/writer.js +103 -119
  139. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  140. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  141. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  142. package/packages/dd-trace/src/exporters/common/proxy.js +52 -0
  143. package/packages/dd-trace/src/exporters/common/request.js +42 -14
  144. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  145. package/packages/dd-trace/src/exporters/common/writer.js +9 -7
  146. package/packages/dd-trace/src/flush.js +6 -3
  147. package/packages/dd-trace/src/id.js +14 -0
  148. package/packages/dd-trace/src/index.js +5 -2
  149. package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
  150. package/packages/dd-trace/src/llmobs/index.js +32 -32
  151. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  152. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  153. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  154. package/packages/dd-trace/src/llmobs/util.js +0 -33
  155. package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
  156. package/packages/dd-trace/src/log/index.js +7 -1
  157. package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
  158. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  159. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  160. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
  161. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  162. package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
  163. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
  164. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
  165. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +5 -0
  166. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  167. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  168. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  169. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  170. package/packages/dd-trace/src/opentracing/propagation/text_map.js +101 -20
  171. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  172. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  173. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  174. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  175. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  176. package/packages/dd-trace/src/plugins/util/test.js +83 -20
  177. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  178. package/packages/dd-trace/src/plugins/util/web.js +4 -9
  179. package/packages/dd-trace/src/priority_sampler.js +4 -0
  180. package/packages/dd-trace/src/profiling/config.js +1 -0
  181. package/packages/dd-trace/src/profiling/oom.js +7 -2
  182. package/packages/dd-trace/src/profiling/profiler.js +23 -13
  183. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  184. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  185. package/packages/dd-trace/src/proxy.js +75 -5
  186. package/packages/dd-trace/src/remote_config/index.js +319 -101
  187. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  188. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  189. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
  190. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
  191. package/packages/dd-trace/src/sampling_rule.js +39 -10
  192. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
  193. package/packages/dd-trace/src/serverless/vercel.js +53 -20
  194. package/packages/dd-trace/src/serverless.js +10 -3
  195. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  196. package/packages/dd-trace/src/span_processor.js +18 -3
  197. package/packages/dd-trace/src/span_stats.js +24 -28
  198. package/packages/dd-trace/src/startup-log.js +3 -2
  199. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  200. package/packages/dd-trace/src/telemetry/send-data.js +23 -20
  201. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  202. package/packages/dd-trace/src/tracer.js +4 -5
  203. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  204. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  205. package/vendor/dist/protobufjs/index.js +1 -1
  206. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  207. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  208. package/packages/dd-trace/src/encode/agentless-json.js +0 -210
@@ -3,7 +3,9 @@
3
3
  const { HTTP_CLIENT_IP } = require('../../../../ext/tags')
4
4
 
5
5
  const log = require('../log')
6
+ const { isEmpty } = require('../util')
6
7
  const addresses = require('./addresses')
8
+ const apiSecurity = require('./api_security')
7
9
  const Reporter = require('./reporter')
8
10
  const waf = require('./waf')
9
11
 
@@ -22,7 +24,7 @@ const activeInvocations = new WeakMap()
22
24
  */
23
25
  function onLambdaStartInvocation (data) {
24
26
  try {
25
- const { span, headers, method, path, query, body, clientIp, pathParams, cookies } = data
27
+ const { span, headers, method, path, query, body, clientIp, pathParams, cookies, route } = data
26
28
 
27
29
  if (!span) {
28
30
  log.warn('[ASM] No span provided in Lambda start invocation')
@@ -30,7 +32,7 @@ function onLambdaStartInvocation (data) {
30
32
  }
31
33
 
32
34
  const req = { headers: headers ?? {} }
33
- activeInvocations.set(span, req)
35
+ activeInvocations.set(span, { req, method, route })
34
36
 
35
37
  span.addTags({
36
38
  '_dd.appsec.enabled': 1,
@@ -80,8 +82,8 @@ function onLambdaStartInvocation (data) {
80
82
  }
81
83
 
82
84
  /**
83
- * Maps response data to WAF addresses, runs a final WAF pass,
84
- * disposes the WAF context, and finishes the request report.
85
+ * Maps response data to WAF addresses, takes the API Security sampling decision, runs a final
86
+ * WAF pass, disposes the WAF context, and finishes the request report.
85
87
  *
86
88
  * @param {{ span: object, statusCode: string | undefined,
87
89
  * responseHeaders: Record<string, string> | undefined }} data
@@ -99,31 +101,47 @@ function onLambdaEndInvocation (data) {
99
101
  return
100
102
  }
101
103
 
102
- const req = activeInvocations.get(span)
104
+ const { req, method, route } = activeInvocations.get(span)
103
105
  activeInvocations.delete(span)
104
106
 
105
- let hasPersistentData = false
106
- const persistent = {}
107
-
108
- if (statusCode) {
109
- persistent[addresses.HTTP_INCOMING_RESPONSE_CODE] = String(statusCode)
110
- hasPersistentData = true
111
- }
112
-
113
- if (responseHeaders) {
114
- const filteredHeaders = { ...responseHeaders }
115
- delete filteredHeaders['set-cookie']
116
- persistent[addresses.HTTP_INCOMING_RESPONSE_HEADERS] = filteredHeaders
117
- hasPersistentData = true
107
+ try {
108
+ const persistent = {}
109
+
110
+ if (statusCode) {
111
+ persistent[addresses.HTTP_INCOMING_RESPONSE_CODE] = String(statusCode)
112
+ }
113
+
114
+ if (responseHeaders) {
115
+ const filteredHeaders = { ...responseHeaders }
116
+ delete filteredHeaders['set-cookie']
117
+ persistent[addresses.HTTP_INCOMING_RESPONSE_HEADERS] = filteredHeaders
118
+ }
119
+
120
+ const samplingDecision = apiSecurity.sampleRootSpanRequest(span, {
121
+ method,
122
+ statusCode,
123
+ route,
124
+ // The tracer does not block in Lambda yet, so a response is never a blocked one.
125
+ blocked: false,
126
+ }, true)
127
+
128
+ if (samplingDecision === apiSecurity.SamplingDecision.SAMPLE) {
129
+ persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
130
+ }
131
+
132
+ let wafResult
133
+ if (!isEmpty(persistent)) {
134
+ wafResult = waf.run({ persistent }, req, undefined, span)
135
+ }
136
+
137
+ apiSecurity.reportRootSpanRequest(span, samplingDecision, wafResult)
138
+ } finally {
139
+ // The execution environment outlives the invocation, so the native WAF context and the
140
+ // module-level metrics queue must be released even if the work above threw.
141
+ waf.disposeContext(req)
142
+
143
+ Reporter.finishRequest(req, null, {}, undefined, span)
118
144
  }
119
-
120
- if (hasPersistentData) {
121
- waf.run({ persistent }, req, undefined, span)
122
- }
123
-
124
- waf.disposeContext(req)
125
-
126
- Reporter.finishRequest(req, null, {}, undefined, span)
127
145
  } catch (err) {
128
146
  log.error('[ASM] Error in Lambda end-invocation handler', err)
129
147
  }
@@ -1,12 +1,29 @@
1
1
  'use strict'
2
2
 
3
3
  class JSONEncoder {
4
- constructor () {
4
+ #bytes = 2
5
+ #maxBytes
6
+
7
+ /**
8
+ * @param {number} [maxBytes]
9
+ */
10
+ constructor (maxBytes) {
11
+ this.#maxBytes = maxBytes
5
12
  this.payloads = []
6
13
  }
7
14
 
8
15
  encode (payload) {
16
+ if (this.#maxBytes !== undefined) {
17
+ const serializedPayload = JSON.stringify(payload) ?? 'null'
18
+ const payloadBytes = Buffer.byteLength(serializedPayload)
19
+ const separatorBytes = this.payloads.length === 0 ? 0 : 1
20
+ if (payloadBytes + separatorBytes > this.#maxBytes - this.#bytes) return false
21
+ this.#bytes += payloadBytes + separatorBytes
22
+ this.payloads.push(serializedPayload)
23
+ return true
24
+ }
9
25
  this.payloads.push(payload)
26
+ return true
10
27
  }
11
28
 
12
29
  count () {
@@ -14,11 +31,24 @@ class JSONEncoder {
14
31
  }
15
32
 
16
33
  reset () {
34
+ this.#bytes = 2
17
35
  this.payloads = []
18
36
  }
19
37
 
20
38
  makePayload () {
21
- const data = JSON.stringify(this.payloads)
39
+ let data
40
+ if (this.#maxBytes === undefined) {
41
+ data = JSON.stringify(this.payloads)
42
+ } else if (this.payloads.length === 0) {
43
+ data = ['[', ']']
44
+ } else {
45
+ data = new Array(this.payloads.length * 2 + 1)
46
+ data[0] = '['
47
+ for (let index = 0; index < this.payloads.length; index++) {
48
+ data[index * 2 + 1] = this.payloads[index]
49
+ data[index * 2 + 2] = index === this.payloads.length - 1 ? ']' : ','
50
+ }
51
+ }
22
52
  this.reset()
23
53
  return data
24
54
  }
@@ -10,6 +10,7 @@ const { DEBUGGER_INPUT_V1 } = require('../../../debugger/constants')
10
10
 
11
11
  // Product-specific discovery: newest advertised version, skip v3 (citestcycle), gzip if >= v4.
12
12
  // Shared `evp_proxy` discovery is an explicit path allowlist and does not cover this contract.
13
+ const AGENT_INFO_TIMEOUT_MS = 5000
13
14
  const AGENT_EVP_PROXY_PATH_PREFIX = '/evp_proxy/v'
14
15
  const AGENT_EVP_PROXY_PATH_REGEX = /\/evp_proxy\/v(\d+)\/?/
15
16
 
@@ -47,14 +48,19 @@ class AgentProxyCiVisibilityExporter extends CiVisibilityExporter {
47
48
  } = config
48
49
 
49
50
  const initializationController = new AbortController()
50
- const initializationOptions = { signal: initializationController.signal }
51
+ const initializationOptions = {
52
+ deadline: Date.now() + AGENT_INFO_TIMEOUT_MS,
53
+ signal: initializationController.signal,
54
+ // Test runners await agent discovery before starting. A detached retry can let Node exit
55
+ // while that promise is still pending because promises alone do not keep the event loop alive.
56
+ keepProcessAlive: true,
57
+ }
51
58
  this._initializationRequest = {
52
59
  controller: initializationController,
53
60
  options: initializationOptions,
54
61
  }
55
62
 
56
63
  fetchAgentInfo(this._url, (err, agentInfo) => {
57
- const initializationFinalFlush = this._initializationRequest?.finalFlush
58
64
  this._initializationRequest = undefined
59
65
  const initializationAborted = initializationController.signal.aborted
60
66
  const agentInfoError = err || (initializationAborted ? initializationController.signal.reason : undefined)
@@ -112,14 +118,8 @@ class AgentProxyCiVisibilityExporter extends CiVisibilityExporter {
112
118
  this._resolveCanUseCiVisProtocol(isEvpCompatible)
113
119
  if (initializationAborted) {
114
120
  this.resetUncodedTraces()
115
- this.resetDeferredTestSuiteSpans()
116
121
  return
117
122
  }
118
- if (isEvpCompatible) {
119
- if (initializationFinalFlush) this.exportDeferredTestSuiteSpans()
120
- } else {
121
- this.resetDeferredTestSuiteSpans()
122
- }
123
123
  this.exportUncodedTraces()
124
124
  this.exportUncodedCoverages()
125
125
  }, initializationOptions, request)
@@ -16,6 +16,7 @@ const {
16
16
  } = require('../../../ci-visibility/telemetry')
17
17
  const { CoverageCIVisibilityEncoder } = require('../../../encode/coverage-ci-visibility')
18
18
  const BaseWriter = require('../../../exporters/common/writer')
19
+ const { getAgent } = require('../agents')
19
20
  const request = require('../request')
20
21
  const TestOptimizationRequestTracker = require('./request-tracker')
21
22
 
@@ -23,7 +24,7 @@ class Writer extends BaseWriter {
23
24
  #requestTracker
24
25
 
25
26
  constructor ({ url, evpProxyPrefix = '' }) {
26
- super(...arguments)
27
+ super({ ...arguments[0], retainOnBackpressure: true })
27
28
  this.#requestTracker = new TestOptimizationRequestTracker(this)
28
29
  this._url = url
29
30
  this._encoder = new CoverageCIVisibilityEncoder(this)
@@ -51,6 +52,7 @@ class Writer extends BaseWriter {
51
52
  },
52
53
  timeout: 15_000,
53
54
  url: this._url,
55
+ agent: getAgent(this._url),
54
56
  deadline: flushOptions?.deadline,
55
57
  }
56
58
 
@@ -61,7 +63,7 @@ class Writer extends BaseWriter {
61
63
  }
62
64
 
63
65
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
64
- log.debug(() => `Request to the intake: ${safeJSONStringify(options)}`)
66
+ log.debug(() => `Request to the intake: ${safeJSONStringify({ ...options, agent: undefined })}`)
65
67
 
66
68
  const startRequestTime = Date.now()
67
69
 
@@ -77,11 +79,11 @@ class Writer extends BaseWriter {
77
79
  if (err) {
78
80
  incrementCountMetric(
79
81
  TELEMETRY_ENDPOINT_PAYLOAD_REQUESTS_ERRORS,
80
- { endpoint: 'code_coverage', statusCode }
82
+ { endpoint: 'code_coverage', statusCode, errorType: statusCode ? undefined : err.code }
81
83
  )
82
84
  incrementCountMetric(
83
85
  TELEMETRY_ENDPOINT_PAYLOAD_DROPPED,
84
- { endpoint: 'code_coverage' }
86
+ { endpoint: 'code_coverage', statusCode, errorType: statusCode ? undefined : err.code }
85
87
  )
86
88
  log.error('Error sending CI coverage payload', err)
87
89
  done(err)
@@ -5,7 +5,14 @@ const { safeJSONStringify } = require('../../../exporters/common/util')
5
5
  const { JSONEncoder } = require('../../encode/json-encoder')
6
6
  const { DEBUGGER_INPUT_V1 } = require('../../../debugger/constants')
7
7
  const BaseWriter = require('../../../exporters/common/writer')
8
+ const {
9
+ incrementCountMetric,
10
+ TELEMETRY_ENDPOINT_PAYLOAD_REQUESTS_ERRORS,
11
+ TELEMETRY_ENDPOINT_PAYLOAD_DROPPED,
12
+ } = require('../../../ci-visibility/telemetry')
8
13
 
14
+ const { getAgent } = require('../agents')
15
+ const { MAX_DI_LOG_BUFFERED_BYTES } = require('../limits')
9
16
  const request = require('../request')
10
17
  const TestOptimizationRequestTracker = require('./request-tracker')
11
18
 
@@ -17,10 +24,10 @@ class DynamicInstrumentationLogsWriter extends BaseWriter {
17
24
 
18
25
  // TODO: what's a good value for timeout for the logs intake?
19
26
  constructor ({ url, timeout = 15_000, isAgentProxy = false }) {
20
- super(...arguments)
27
+ super({ ...arguments[0], retainOnBackpressure: true })
21
28
  this.#requestTracker = new TestOptimizationRequestTracker(this)
22
29
  this._url = url
23
- this._encoder = new JSONEncoder()
30
+ this._encoder = new JSONEncoder(MAX_DI_LOG_BUFFERED_BYTES)
24
31
  this._isAgentProxy = isAgentProxy
25
32
  this.timeout = timeout
26
33
  }
@@ -46,6 +53,7 @@ class DynamicInstrumentationLogsWriter extends BaseWriter {
46
53
  },
47
54
  timeout: this.timeout,
48
55
  url: this._url,
56
+ agent: getAgent(this._url),
49
57
  deadline: flushOptions?.deadline,
50
58
  }
51
59
 
@@ -55,10 +63,18 @@ class DynamicInstrumentationLogsWriter extends BaseWriter {
55
63
  }
56
64
 
57
65
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
58
- log.debug(() => `Request to the logs intake: ${safeJSONStringify(options)}`)
66
+ log.debug(() => `Request to the logs intake: ${safeJSONStringify({ ...options, agent: undefined })}`)
59
67
 
60
- this.#requestTracker.send(request, data, options, (err, res) => {
68
+ this.#requestTracker.send(request, data, options, (err, res, statusCode) => {
61
69
  if (err) {
70
+ incrementCountMetric(
71
+ TELEMETRY_ENDPOINT_PAYLOAD_REQUESTS_ERRORS,
72
+ { endpoint: 'di_logs', statusCode, errorType: statusCode ? undefined : err.code }
73
+ )
74
+ incrementCountMetric(
75
+ TELEMETRY_ENDPOINT_PAYLOAD_DROPPED,
76
+ { endpoint: 'di_logs', statusCode, errorType: statusCode ? undefined : err.code }
77
+ )
62
78
  log.error('Error sending DI logs payload', err)
63
79
  done(err)
64
80
  return
@@ -1,179 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseWriter = require('../../../exporters/common/writer')
4
+ const FinalFlushRequestTracker = require('../../../exporters/common/final-flush-request-tracker')
4
5
  const { createFinalFlushTimeoutError } = require('../../final-flush')
5
6
 
6
- class FinalFlushRequestTracker {
7
- #flush
8
- #pendingRequests = new Set()
9
- #finalFlushes = new Set()
10
- #activeFinalFlush
11
-
12
- /**
13
- * Creates request tracking for a Test Optimization exporter.
14
- *
15
- * @param {(done?: (error?: Error) => void, options?: { deadline?: number }) => void} flush
16
- */
17
- constructor (flush) {
18
- this.#flush = flush
19
- }
20
-
21
- /**
22
- * Flushes queued payloads and, for a final flush, waits for requests that were
23
- * already in flight. The absolute deadline prevents Test Optimization from
24
- * keeping the test process alive indefinitely.
25
- *
26
- * @param {(error?: Error) => void} [done]
27
- * @param {{ deadline?: number }} [options]
28
- * @returns {void}
29
- */
30
- flush (done, options) {
31
- if (options?.deadline === undefined) {
32
- this.#flush(done, options)
33
- return
34
- }
35
-
36
- const finalFlush = {
37
- deadline: options.deadline,
38
- done: done || (() => {}),
39
- error: undefined,
40
- requests: new Set(),
41
- timeoutId: undefined,
42
- writerDone: false,
43
- }
44
- this.#finalFlushes.add(finalFlush)
45
-
46
- for (const pendingRequest of this.#pendingRequests) {
47
- this.#attachRequest(finalFlush, pendingRequest)
48
- }
49
-
50
- const remaining = Math.max(0, options.deadline - Date.now())
51
- finalFlush.timeoutId = setTimeout(() => {
52
- const error = createFinalFlushTimeoutError()
53
-
54
- finalFlush.error ||= error
55
- finalFlush.writerDone = true
56
-
57
- for (const pendingRequest of finalFlush.requests) {
58
- pendingRequest.finalFlushes.delete(finalFlush)
59
- if (pendingRequest.finalFlushes.size === 0) {
60
- pendingRequest.controller.abort(error)
61
- this.#pendingRequests.delete(pendingRequest)
62
- } else {
63
- this.#updateRequestDeadline(pendingRequest)
64
- }
65
- }
66
- finalFlush.requests.clear()
67
- this.#finishFinalFlush(finalFlush)
68
- }, remaining)
69
-
70
- const previousFinalFlush = this.#activeFinalFlush
71
- this.#activeFinalFlush = finalFlush
72
- try {
73
- this.#flush((error) => {
74
- finalFlush.error ||= error
75
- finalFlush.writerDone = true
76
- this.#finishFinalFlush(finalFlush)
77
- }, options)
78
- } finally {
79
- this.#activeFinalFlush = previousFinalFlush
80
- }
81
- this.#finishFinalFlush(finalFlush)
82
- }
83
-
84
- /**
85
- * Sends and tracks a request so a later final flush can wait for or abort it.
86
- *
87
- * @param {Function} request
88
- * @param {Buffer|string|object} data
89
- * @param {object} options
90
- * @param {(error: Error|null, result?: string|null, statusCode?: number,
91
- * headers?: import('node:http').IncomingHttpHeaders) => void} callback
92
- * @returns {void}
93
- */
94
- send (request, data, options, callback) {
95
- const controller = new AbortController()
96
- const requestOptions = { ...options, signal: controller.signal }
97
- const pendingRequest = { controller, finalFlushes: new Set(), options: requestOptions }
98
- this.#pendingRequests.add(pendingRequest)
99
- if (this.#activeFinalFlush) this.#attachRequest(this.#activeFinalFlush, pendingRequest)
100
-
101
- try {
102
- request(data, requestOptions, (error, result, statusCode, headers) => {
103
- if (error) {
104
- for (const finalFlush of pendingRequest.finalFlushes) finalFlush.error ||= error
105
- }
106
-
107
- try {
108
- callback(error, result, statusCode, headers)
109
- } finally {
110
- this.#dropRequest(pendingRequest)
111
- }
112
- })
113
- } catch (error) {
114
- this.#dropRequest(pendingRequest)
115
- throw error
116
- }
117
- }
118
-
119
- /**
120
- * Stops tracking a settled request and releases final flushes waiting for it.
121
- *
122
- * @param {object} pendingRequest
123
- * @returns {void}
124
- */
125
- #dropRequest (pendingRequest) {
126
- this.#pendingRequests.delete(pendingRequest)
127
- for (const finalFlush of pendingRequest.finalFlushes) {
128
- finalFlush.requests.delete(pendingRequest)
129
- this.#finishFinalFlush(finalFlush)
130
- }
131
- pendingRequest.finalFlushes.clear()
132
- }
133
-
134
- /**
135
- * Associates a request with the final flush boundary that must wait for it.
136
- *
137
- * @param {object} finalFlush
138
- * @param {object} pendingRequest
139
- * @returns {void}
140
- */
141
- #attachRequest (finalFlush, pendingRequest) {
142
- finalFlush.requests.add(pendingRequest)
143
- pendingRequest.finalFlushes.add(finalFlush)
144
- this.#updateRequestDeadline(pendingRequest)
145
- }
146
-
147
- /**
148
- * Gives a shared request the latest deadline of the flushes waiting for it.
149
- *
150
- * @param {object} pendingRequest
151
- * @returns {void}
152
- */
153
- #updateRequestDeadline (pendingRequest) {
154
- let deadline = 0
155
- for (const finalFlush of pendingRequest.finalFlushes) {
156
- deadline = Math.max(deadline, finalFlush.deadline)
157
- }
158
- pendingRequest.options.deadline = deadline
159
- }
160
-
161
- /**
162
- * Completes a final flush callback once its writer and associated requests
163
- * have settled.
164
- *
165
- * @param {object} finalFlush
166
- * @returns {void}
167
- */
168
- #finishFinalFlush (finalFlush) {
169
- if (!this.#finalFlushes.has(finalFlush) || !finalFlush.writerDone || finalFlush.requests.size !== 0) return
170
-
171
- this.#finalFlushes.delete(finalFlush)
172
- clearTimeout(finalFlush.timeoutId)
173
- finalFlush.done(finalFlush.error)
174
- }
175
- }
176
-
177
7
  class TestOptimizationRequestTracker extends FinalFlushRequestTracker {
178
8
  /**
179
9
  * Creates request tracking for a Test Optimization writer.
@@ -181,9 +11,11 @@ class TestOptimizationRequestTracker extends FinalFlushRequestTracker {
181
11
  * @param {BaseWriter} writer
182
12
  */
183
13
  constructor (writer) {
184
- super((done, options) => BaseWriter.prototype.flushDirect.call(writer, done, options))
14
+ super(
15
+ (done, options) => BaseWriter.prototype.flushDirect.call(writer, done, options),
16
+ createFinalFlushTimeoutError
17
+ )
185
18
  }
186
19
  }
187
20
 
188
21
  module.exports = TestOptimizationRequestTracker
189
- module.exports.FinalFlushRequestTracker = FinalFlushRequestTracker
@@ -16,6 +16,7 @@ const {
16
16
  } = require('../../../ci-visibility/telemetry')
17
17
  const { AgentlessCiVisibilityEncoder } = require('../../../encode/agentless-ci-visibility')
18
18
  const BaseWriter = require('../../../exporters/common/writer')
19
+ const { getAgent } = require('../agents')
19
20
  const request = require('../request')
20
21
  const TestOptimizationRequestTracker = require('./request-tracker')
21
22
 
@@ -23,11 +24,10 @@ class Writer extends BaseWriter {
23
24
  #requestTracker
24
25
 
25
26
  constructor ({ url, tags, evpProxyPrefix = '' }) {
26
- super(...arguments)
27
+ super({ ...arguments[0], retainOnBackpressure: true })
27
28
  this.#requestTracker = new TestOptimizationRequestTracker(this)
28
- const { 'runtime-id': runtimeId, env, service } = tags
29
29
  this._url = url
30
- this._encoder = new AgentlessCiVisibilityEncoder(this, { runtimeId, env, service })
30
+ this._encoder = new AgentlessCiVisibilityEncoder(this, { tags })
31
31
  this._evpProxyPrefix = evpProxyPrefix
32
32
  }
33
33
 
@@ -52,6 +52,7 @@ class Writer extends BaseWriter {
52
52
  },
53
53
  timeout: 15_000,
54
54
  url: this._url,
55
+ agent: getAgent(this._url),
55
56
  deadline: flushOptions?.deadline,
56
57
  }
57
58
 
@@ -62,7 +63,7 @@ class Writer extends BaseWriter {
62
63
  }
63
64
 
64
65
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
65
- log.debug(() => `Request to the intake: ${safeJSONStringify(options)}`)
66
+ log.debug(() => `Request to the intake: ${safeJSONStringify({ ...options, agent: undefined })}`)
66
67
 
67
68
  const startRequestTime = Date.now()
68
69
 
@@ -78,11 +79,11 @@ class Writer extends BaseWriter {
78
79
  if (err) {
79
80
  incrementCountMetric(
80
81
  TELEMETRY_ENDPOINT_PAYLOAD_REQUESTS_ERRORS,
81
- { endpoint: 'test_cycle', statusCode }
82
+ { endpoint: 'test_cycle', statusCode, errorType: statusCode ? undefined : err.code }
82
83
  )
83
84
  incrementCountMetric(
84
85
  TELEMETRY_ENDPOINT_PAYLOAD_DROPPED,
85
- { endpoint: 'test_cycle' }
86
+ { endpoint: 'test_cycle', statusCode, errorType: statusCode ? undefined : err.code }
86
87
  )
87
88
  log.error('Error sending CI agentless payload', err)
88
89
  done(err)
@@ -0,0 +1,54 @@
1
+ 'use strict'
2
+
3
+ const http = require('http')
4
+ const https = require('https')
5
+
6
+ const { createAgents } = require('../../exporters/common/agents')
7
+
8
+ // Finalization can flush several Test Optimization payload types together. Keep media on a separate
9
+ // bounded pool so long uploads cannot consume the sockets needed by trace, coverage, and log writers.
10
+ const maxSockets = 16
11
+
12
+ const payloadAgents = createAgents(maxSockets)
13
+ const mediaAgents = createAgents(maxSockets)
14
+
15
+ /**
16
+ * Selects an agent by intake URL protocol.
17
+ *
18
+ * @param {string|URL|object} url
19
+ * @param {{httpAgent: http.Agent, httpsAgent: https.Agent}} agents
20
+ * @returns {http.Agent|https.Agent}
21
+ */
22
+ function selectAgent (url, { httpAgent, httpsAgent }) {
23
+ const protocol = url?.protocol
24
+ if (protocol === 'https:' || protocol === 'https') return httpsAgent
25
+ if (protocol === 'http:' || protocol === 'http') return httpAgent
26
+
27
+ try {
28
+ return new URL(url).protocol === 'https:' ? httpsAgent : httpAgent
29
+ } catch {
30
+ return String(url).startsWith('https:') ? httpsAgent : httpAgent
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Selects the dedicated Test Optimization payload agent for an intake URL.
36
+ *
37
+ * @param {string|URL|object} url
38
+ * @returns {http.Agent|https.Agent}
39
+ */
40
+ function getAgent (url) {
41
+ return selectAgent(url, payloadAgents)
42
+ }
43
+
44
+ /**
45
+ * Selects the isolated Test Optimization media agent for an intake URL.
46
+ *
47
+ * @param {string|URL|object} url
48
+ * @returns {http.Agent|https.Agent}
49
+ */
50
+ function getMediaAgent (url) {
51
+ return selectAgent(url, mediaAgents)
52
+ }
53
+
54
+ module.exports = { getAgent, getMediaAgent }
@@ -11,9 +11,8 @@ class CiValidationWriter {
11
11
  * @param {object} options.tags tracer tags
12
12
  */
13
13
  constructor ({ sink, tags }) {
14
- const { 'runtime-id': runtimeId, env, service } = tags
15
14
  this._sink = sink
16
- this._encoder = new AgentlessCiVisibilityEncoder(this, { runtimeId, env, service })
15
+ this._encoder = new AgentlessCiVisibilityEncoder(this, { tags })
17
16
  }
18
17
 
19
18
  /**