dd-trace 5.121.0 → 5.122.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
@@ -1,90 +1,18 @@
1
1
  'use strict'
2
2
 
3
- const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
4
- const { HTTP_CLIENT_IP, NETWORK_CLIENT_IP, HTTP_USERAGENT } = require('../../../../ext/tags')
5
- const { USER_ID, USER_SESSION_ID } = require('../appsec/addresses')
6
- const { getActiveRequest } = require('../appsec/store')
7
3
  const log = require('../log')
8
- const { extractIp } = require('../plugins/util/ip_extractor')
9
- const telemetryMetrics = require('../telemetry/metrics')
10
- const tracerVersion = require('../../../../package.json').version
11
- const { keepTrace } = require('../priority_sampler')
12
- const { AI_GUARD } = require('../standalone/product')
4
+ const AIGuardClient = require('./client')
5
+ const { createEvaluationOutcome, EvaluationReporter } = require('./evaluation')
6
+ const { AIGuardAbortError } = require('./errors')
13
7
  const NoopAIGuard = require('./noop')
14
- const executeRequest = require('./client')
15
8
  const TAGS = require('./tags')
16
9
 
17
- const aiguardMetrics = telemetryMetrics.manager.namespace('ai_guard')
18
-
19
- const ALLOW = 'ALLOW'
20
-
21
- // Tags from the service entry span that must be mirrored onto every AI Guard span
22
- // so anomaly detection pipelines can process each span independently.
23
- const SERVICE_ENTRY_TAG_MAPPINGS = [
24
- [HTTP_USERAGENT, TAGS.HTTP_USERAGENT_TAG_KEY],
25
- [HTTP_CLIENT_IP, TAGS.HTTP_CLIENT_IP_TAG_KEY],
26
- [NETWORK_CLIENT_IP, TAGS.NETWORK_CLIENT_IP_TAG_KEY],
27
- [USER_ID, TAGS.USR_ID_TAG_KEY],
28
- [USER_SESSION_ID, TAGS.USR_SESSION_ID_TAG_KEY],
29
- ]
30
-
31
- /**
32
- * Reports a telemetry error
33
- *
34
- * @param {string} errorType - The error type constant (client_error, bad_status, bad_response)
35
- * @param {{ source: string, integration: string }} telemetryTags - Source and integration tags
36
- */
37
- function reportTelemetryError (errorType, telemetryTags) {
38
- aiguardMetrics.count(TAGS.TELEMETRY_REQUESTS, { error: true, ...telemetryTags }).inc(1)
39
- aiguardMetrics.count(TAGS.TELEMETRY_ERROR, { type: errorType, ...telemetryTags }).inc(1)
40
- }
41
-
42
- class AIGuardAbortError extends Error {
43
- constructor (reason, tags, tagProbs, sds) {
44
- super(reason)
45
- this.name = 'AIGuardAbortError'
46
- this.reason = reason
47
- this.tags = tags
48
- this.tagProbabilities = tagProbs
49
- this.sds = sds || []
50
- }
51
- }
52
-
53
- class AIGuardClientError extends Error {
54
- constructor (message, opts = {}) {
55
- super(message)
56
- this.name = 'AIGuardClientError'
57
- if (opts.errors) {
58
- this.errors = opts.errors
59
- }
60
- if (opts.cause) {
61
- this.cause = opts.cause
62
- }
63
- }
64
- }
65
-
66
- /**
67
- * Resolves the AI Guard host for a given Datadog site. Sites with a single subdomain level
68
- * (e.g. `datadoghq.com`, `ddog-gov.com`) are served from the `app.` subdomain, while regional
69
- * sites (e.g. `us3.datadoghq.com`, `ap1.datadoghq.com`) are used as-is.
70
- *
71
- * @param {string} site - Datadog site (e.g. `datadoghq.com`, `us3.datadoghq.com`)
72
- * @returns {string} The host to use for the AI Guard endpoint
73
- */
74
- function aiGuardHost (site) {
75
- return site.split('.').length === 2 ? `app.${site}` : site
76
- }
77
-
78
10
  class AIGuard extends NoopAIGuard {
79
11
  #initialized
80
12
  #tracer
81
- #headers
82
- #evaluateUrl
83
- #timeout
84
- #maxMessagesLength
85
- #maxContentSize
13
+ #client
14
+ #reporter
86
15
  #meta
87
- #config
88
16
 
89
17
  /**
90
18
  * @param {import('../tracer')} tracer - Tracer instance
@@ -99,223 +27,39 @@ class AIGuard extends NoopAIGuard {
99
27
  return
100
28
  }
101
29
  this.#tracer = tracer
102
- this.#headers = {
103
- 'DD-API-KEY': config.DD_API_KEY,
104
- 'DD-APPLICATION-KEY': config.DD_APP_KEY,
105
- 'DD-AI-GUARD-VERSION': tracerVersion,
106
- 'DD-AI-GUARD-SOURCE': 'SDK',
107
- 'DD-AI-GUARD-LANGUAGE': 'nodejs',
108
- }
109
- const endpoint = config.experimental.aiguard.endpoint || `https://${aiGuardHost(config.site)}/api/v2/ai-guard`
110
- this.#evaluateUrl = `${endpoint}/evaluate`
111
- this.#timeout = config.experimental.aiguard.timeout
112
- this.#maxMessagesLength = config.experimental.aiguard.maxMessagesLength
113
- this.#maxContentSize = config.experimental.aiguard.maxContentSize
30
+ this.#client = new AIGuardClient(config)
31
+ this.#reporter = new EvaluationReporter(config)
114
32
  this.#meta = { service: config.service, env: config.env }
115
- this.#config = config
116
33
  this.#initialized = true
117
34
  }
118
35
 
119
- /**
120
- * Returns a safe copy of the messages to be serialized into the meta struct.
121
- *
122
- * - Clones each message so callers cannot mutate the data set in the meta struct.
123
- * - Truncates the list of messages and `content` fields emitting metrics accordingly.
124
- *
125
- * @param {import('../../../../index').aiguard.Message[]} messages
126
- * @param {{ source: string, integration: string }} telemetryTags
127
- */
128
- #buildMessagesForMetaStruct (messages, telemetryTags) {
129
- const size = Math.min(messages.length, this.#maxMessagesLength)
130
- if (messages.length > size) {
131
- aiguardMetrics.count(TAGS.TELEMETRY_TRUNCATED, { type: 'messages', ...telemetryTags }).inc(1)
132
- }
133
- const result = []
134
- let contentTruncated = false
135
- for (let i = messages.length - size; i < messages.length; i++) {
136
- const message = rfdc(messages[i])
137
- if (message.content?.length > this.#maxContentSize) {
138
- contentTruncated = true
139
- message.content = message.content.slice(0, this.#maxContentSize)
140
- }
141
- result.push(message)
142
- }
143
- if (contentTruncated) {
144
- aiguardMetrics.count(TAGS.TELEMETRY_TRUNCATED, { type: 'content', ...telemetryTags }).inc(1)
145
- }
146
- return result
147
- }
148
-
149
- #isToolCall (message) {
150
- return message.tool_calls || message.tool_call_id
151
- }
152
-
153
- #getToolName (message, history) {
154
- // 1. assistant message with tool calls
155
- if (message.tool_calls) {
156
- const names = message.tool_calls.map((tool) => tool.function.name)
157
- return names.length === 0 ? null : names.join(',')
158
- }
159
- // 2. assistant message with tool output (search the linked tool call in reverse order)
160
- const id = message.tool_call_id
161
- for (let i = history.length - 2; i >= 0; i--) {
162
- const item = history[i]
163
- if (item.tool_calls) {
164
- for (const toolCall of item.tool_calls) {
165
- if (toolCall.id === id) {
166
- return toolCall.function.name
167
- }
168
- }
169
- }
170
- }
171
- return null
172
- }
173
-
174
- #setRootSpanClientIpTags (rootSpan) {
175
- if (!rootSpan) return
176
-
177
- const currentTags = rootSpan.context().getTags()
178
- const needsHttpClientIp = !Object.hasOwn(currentTags, HTTP_CLIENT_IP)
179
- const needsNetworkClientIp = !Object.hasOwn(currentTags, NETWORK_CLIENT_IP)
180
-
181
- if (!needsHttpClientIp && !needsNetworkClientIp) return
182
-
183
- const req = getActiveRequest()
184
-
185
- if (!req) return
186
-
187
- const newTags = {}
188
-
189
- if (needsHttpClientIp) {
190
- const clientIp = extractIp(this.#config, req)
191
-
192
- if (clientIp) {
193
- newTags[HTTP_CLIENT_IP] = clientIp
194
- }
195
- }
196
-
197
- if (needsNetworkClientIp) {
198
- const networkClientIp = req.socket?.remoteAddress
199
-
200
- if (networkClientIp) {
201
- newTags[NETWORK_CLIENT_IP] = networkClientIp
202
- }
203
- }
204
-
205
- if (Object.keys(newTags).length > 0) {
206
- rootSpan.addTags(newTags)
207
- }
208
- }
209
-
210
- /**
211
- * Copies service entry span tags needed by anomaly detection to the AI Guard span.
212
- *
213
- * @param {import('../opentracing/span')} guardSpan
214
- * @param {import('../opentracing/span')} rootSpan
215
- */
216
- #copyServiceEntryTagsToGuardSpan (guardSpan, rootSpan) {
217
- const rootTags = rootSpan.context().getTags()
218
- for (const [sourceTag, destTag] of SERVICE_ENTRY_TAG_MAPPINGS) {
219
- const value = rootTags[sourceTag]
220
- if (value !== undefined && value !== null) {
221
- guardSpan.setTag(destTag, value)
222
- }
223
- }
224
- }
225
-
226
36
  evaluate (messages, opts) {
227
37
  if (!this.#initialized) {
228
38
  return super.evaluate(messages, opts)
229
39
  }
230
40
  const { block = true, source = TAGS.SOURCE_SDK, integration = TAGS.INTEGRATION_NONE, childOf } = opts ?? {}
231
- const telemetryTags = { source, integration }
232
41
  // Only pass `childOf` when truthy so `tracer.trace`'s default (`scope().active()`)
233
42
  // still applies for SDK callers that don't supply an explicit parent.
234
43
  const traceOpts = childOf ? { childOf } : {}
235
44
  return this.#tracer.trace(TAGS.RESOURCE, traceOpts, async (span) => {
236
- const last = messages.at(-1)
237
- const target = this.#isToolCall(last) ? 'tool' : 'prompt'
238
- span.setTag(TAGS.TARGET_TAG_KEY, target)
239
- if (target === 'tool') {
240
- const name = this.#getToolName(last, messages)
241
- if (name) {
242
- span.setTag(TAGS.TOOL_NAME_TAG_KEY, name)
243
- }
244
- }
245
- const metaStruct = {
246
- messages: this.#buildMessagesForMetaStruct(messages, telemetryTags),
247
- }
248
- span.meta_struct = {
249
- [TAGS.META_STRUCT_KEY]: metaStruct,
250
- }
251
- const rootSpan = span.context()?._trace?.started?.[0]
252
- if (rootSpan) {
253
- this.#setRootSpanClientIpTags(rootSpan)
254
- this.#copyServiceEntryTagsToGuardSpan(span, rootSpan)
255
- // keepTrace must be called before executeRequest so the sampling decision
256
- // is propagated correctly to outgoing HTTP client calls.
257
- keepTrace(rootSpan, AI_GUARD)
258
- rootSpan.setTag(TAGS.EVENT_TAG_KEY, 'true')
259
- }
260
- let response
45
+ const report = this.#reporter.start(span, messages, { source, integration })
46
+ let evaluation
261
47
  try {
262
- const payload = {
263
- data: {
264
- attributes: {
265
- messages,
266
- meta: this.#meta,
267
- },
268
- },
269
- }
270
- response = await executeRequest(
271
- payload,
272
- { url: this.#evaluateUrl, headers: this.#headers, timeout: this.#timeout })
273
- } catch (e) {
274
- reportTelemetryError(TAGS.ERROR_TYPE_CLIENT, telemetryTags)
275
- throw new AIGuardClientError(`Unexpected error calling AI Guard service: ${e.message}`, { cause: e })
276
- }
277
- if (response.status !== 200) {
278
- reportTelemetryError(TAGS.ERROR_TYPE_STATUS, telemetryTags)
279
- throw new AIGuardClientError(
280
- `AI Guard service call failed, status ${response.status}`,
281
- { errors: response.body?.errors })
48
+ evaluation = await this.#client.evaluate(messages, this.#meta)
49
+ } catch (error) {
50
+ this.#reporter.fail(report, error.telemetryType ?? TAGS.ERROR_TYPE_CLIENT)
51
+ throw error
282
52
  }
283
- const attr = response.body?.data?.attributes
284
- if (!attr?.action) {
285
- reportTelemetryError(TAGS.ERROR_TYPE_RESPONSE, telemetryTags)
286
- throw new AIGuardClientError(`AI Guard service returned unexpected response : ${response.body}`)
287
- }
288
- const action = attr.action
289
- const reason = attr.reason
290
- const tags = attr.tags ?? []
291
- if (tags.length > 0) {
292
- metaStruct.attack_categories = tags
293
- }
294
- const sdsFindings = attr.sds_findings ?? []
295
- if (sdsFindings.length > 0) {
296
- metaStruct.sds = sdsFindings
297
- }
298
- const tagProbabilities = attr.tag_probs ?? {}
299
- if (attr.tag_probs) {
300
- metaStruct.tag_probs = tagProbabilities
301
- }
302
- const blockingEnabled = attr.is_blocking_enabled ?? false
303
- const shouldBlock = block && blockingEnabled && action !== ALLOW
304
- aiguardMetrics.count(TAGS.TELEMETRY_REQUESTS, {
305
- action,
306
- error: false,
307
- block: shouldBlock,
308
- ...telemetryTags,
309
- }).inc(1)
310
- span.setTag(TAGS.ACTION_TAG_KEY, action)
311
- if (reason) {
312
- span.setTag(TAGS.REASON_TAG_KEY, reason)
313
- }
314
- if (shouldBlock) {
315
- span.setTag(TAGS.BLOCKED_TAG_KEY, 'true')
316
- throw new AIGuardAbortError(reason, tags, tagProbabilities, sdsFindings)
53
+
54
+ const outcome = createEvaluationOutcome(evaluation, block)
55
+ this.#reporter.finish(report, outcome)
56
+
57
+ if (outcome.shouldBlock) {
58
+ const { reason, tags, tagProbabilities, sds } = outcome.result
59
+ throw new AIGuardAbortError(reason, tags, tagProbabilities, sds)
317
60
  }
318
- return { action, reason, tags, tagProbabilities, sds: sdsFindings }
61
+
62
+ return outcome.result
319
63
  })
320
64
  }
321
65
  }
@@ -97,15 +97,14 @@ function getPrepareStackTraceAccessor () {
97
97
 
98
98
  function getCompileMethodFn (compileMethod) {
99
99
  let delegate = function (content, filename) {
100
+ if (isDdTrace(filename) || !isPrivateModule(filename) || !config.iast?.enabled) {
101
+ return compileMethod.apply(this, [content, filename])
102
+ }
103
+
104
+ // TODO when we have CJS support for orchestrion and taint-tracking, add
105
+ // them here as appropriate
106
+ let rewrittenContent
100
107
  try {
101
- if (isDdTrace(filename)) {
102
- return compileMethod.apply(this, [content, filename])
103
- }
104
- if (!isPrivateModule(filename) || !config.iast?.enabled) {
105
- return compileMethod.apply(this, [content, filename])
106
- }
107
- // TODO when we have CJS support for orchestrion and taint-tracking, add
108
- // them here as appropriate
109
108
  const rewritten = rewriter.rewrite(content, filename, ['iast'])
110
109
 
111
110
  incrementTelemetryIfNeeded(rewritten.metrics)
@@ -114,13 +113,11 @@ function getCompileMethodFn (compileMethod) {
114
113
  hardcodedSecretCh.publish(rewritten.literalsResult)
115
114
  }
116
115
 
117
- if (rewritten?.content) {
118
- return compileMethod.apply(this, [rewritten.content, filename])
119
- }
116
+ rewrittenContent = rewritten?.content
120
117
  } catch (e) {
121
118
  log.error('Error rewriting file %s', filename, e)
122
119
  }
123
- return compileMethod.apply(this, [content, filename])
120
+ return compileMethod.apply(this, [rewrittenContent || content, filename])
124
121
  }
125
122
 
126
123
  const shim = function (...args) {
@@ -92,6 +92,7 @@ function removeEmptyCaptureProperties (value) {
92
92
  if (current.fields === null || (
93
93
  current.fields &&
94
94
  typeof current.fields === 'object' &&
95
+ // eslint-disable-next-line no-restricted-syntax -- snapshot field names are user variables; no key to probe
95
96
  Object.keys(current.fields).length === 0
96
97
  )) {
97
98
  delete current.fields
@@ -4,6 +4,7 @@ const AgentWriter = require('../../../exporters/agent/writer')
4
4
  const AgentlessWriter = require('../agentless/writer')
5
5
  const CoverageWriter = require('../agentless/coverage-writer')
6
6
  const CiVisibilityExporter = require('../ci-visibility-exporter')
7
+ const request = require('../request')
7
8
  const { fetchAgentInfo } = require('../../../agent/info')
8
9
  const { DEBUGGER_INPUT_V1 } = require('../../../debugger/constants')
9
10
 
@@ -43,9 +44,21 @@ class AgentProxyCiVisibilityExporter extends CiVisibilityExporter {
43
44
  testOptimization,
44
45
  } = config
45
46
 
47
+ const initializationController = new AbortController()
48
+ const initializationOptions = { signal: initializationController.signal }
49
+ this._initializationRequest = {
50
+ controller: initializationController,
51
+ options: initializationOptions,
52
+ }
53
+
46
54
  fetchAgentInfo(this._url, (err, agentInfo) => {
55
+ const initializationFinalFlush = this._initializationRequest?.finalFlush
56
+ this._initializationRequest = undefined
57
+ const initializationAborted = initializationController.signal.aborted
58
+ const agentInfoError = err || (initializationAborted ? initializationController.signal.reason : undefined)
59
+
47
60
  this._isInitialized = true
48
- let latestEvpProxyVersion = getLatestEvpProxyVersion(err, agentInfo)
61
+ let latestEvpProxyVersion = getLatestEvpProxyVersion(agentInfoError, agentInfo)
49
62
  const isEvpCompatible = latestEvpProxyVersion >= 2
50
63
  this._isGzipCompatible = latestEvpProxyVersion >= 4
51
64
 
@@ -72,7 +85,7 @@ class AgentProxyCiVisibilityExporter extends CiVisibilityExporter {
72
85
  // path with evpProxyPrefix and sets X-Datadog-EVP-Subdomain: api (see uploadTestScreenshot).
73
86
  this._testScreenshotUploadUrl = this._url
74
87
  if (testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED) {
75
- const canFowardLogs = getCanForwardDebuggerLogs(err, agentInfo)
88
+ const canFowardLogs = getCanForwardDebuggerLogs(agentInfoError, agentInfo)
76
89
  if (canFowardLogs) {
77
90
  const DynamicInstrumentationLogsWriter = require('../agentless/di-logs-writer')
78
91
  this._logsWriter = new DynamicInstrumentationLogsWriter({
@@ -89,14 +102,25 @@ class AgentProxyCiVisibilityExporter extends CiVisibilityExporter {
89
102
  lookup,
90
103
  protocolVersion,
91
104
  headers,
105
+ isTestOptimization: true,
92
106
  })
93
107
  // coverages will never be used, so we discard them
94
108
  this._coverageBuffer = []
95
109
  }
96
110
  this._resolveCanUseCiVisProtocol(isEvpCompatible)
111
+ if (initializationAborted) {
112
+ this.resetUncodedTraces()
113
+ this.resetDeferredTestSuiteSpans()
114
+ return
115
+ }
116
+ if (isEvpCompatible) {
117
+ if (initializationFinalFlush) this.exportDeferredTestSuiteSpans()
118
+ } else {
119
+ this.resetDeferredTestSuiteSpans()
120
+ }
97
121
  this.exportUncodedTraces()
98
122
  this.exportUncodedCoverages()
99
- })
123
+ }, initializationOptions, request)
100
124
  }
101
125
 
102
126
  setUrl (url, coverageUrl) {
@@ -1,11 +1,8 @@
1
1
  'use strict'
2
2
  const getConfig = require('../../../config')
3
- const request = require('../../../exporters/common/request')
4
3
  const log = require('../../../log')
5
4
  const { safeJSONStringify } = require('../../../exporters/common/util')
6
5
 
7
- const { CoverageCIVisibilityEncoder } = require('../../../encode/coverage-ci-visibility')
8
- const BaseWriter = require('../../../exporters/common/writer')
9
6
  const {
10
7
  incrementCountMetric,
11
8
  distributionMetric,
@@ -15,16 +12,34 @@ const {
15
12
  TELEMETRY_ENDPOINT_PAYLOAD_REQUESTS_ERRORS,
16
13
  TELEMETRY_ENDPOINT_PAYLOAD_DROPPED,
17
14
  } = require('../../../ci-visibility/telemetry')
15
+ const { CoverageCIVisibilityEncoder } = require('../../../encode/coverage-ci-visibility')
16
+ const BaseWriter = require('../../../exporters/common/writer')
17
+ const request = require('../request')
18
+ const TestOptimizationRequestTracker = require('./request-tracker')
18
19
 
19
20
  class Writer extends BaseWriter {
21
+ #requestTracker
22
+
20
23
  constructor ({ url, evpProxyPrefix = '' }) {
21
24
  super(...arguments)
25
+ this.#requestTracker = new TestOptimizationRequestTracker(this)
22
26
  this._url = url
23
27
  this._encoder = new CoverageCIVisibilityEncoder(this)
24
28
  this._evpProxyPrefix = evpProxyPrefix
25
29
  }
26
30
 
27
- _sendPayload (form, _, done) {
31
+ /**
32
+ * Flushes buffered coverage, waiting for tracked requests during finalization.
33
+ *
34
+ * @param {(error?: Error) => void} [done]
35
+ * @param {{ deadline?: number }} [options]
36
+ * @returns {void}
37
+ */
38
+ flush (done, options) {
39
+ this.#requestTracker.flush(done, options)
40
+ }
41
+
42
+ _sendPayload (form, _, done, flushOptions) {
28
43
  const options = {
29
44
  path: '/api/v2/citestcov',
30
45
  method: 'POST',
@@ -34,6 +49,7 @@ class Writer extends BaseWriter {
34
49
  },
35
50
  timeout: 15_000,
36
51
  url: this._url,
52
+ deadline: flushOptions?.deadline,
37
53
  }
38
54
 
39
55
  if (this._evpProxyPrefix) {
@@ -50,7 +66,7 @@ class Writer extends BaseWriter {
50
66
  incrementCountMetric(TELEMETRY_ENDPOINT_PAYLOAD_REQUESTS, { endpoint: 'code_coverage' })
51
67
  distributionMetric(TELEMETRY_ENDPOINT_PAYLOAD_BYTES, { endpoint: 'code_coverage' }, form.size())
52
68
 
53
- request(form, options, (err, res, statusCode) => {
69
+ this.#requestTracker.send(request, form, options, (err, res, statusCode) => {
54
70
  distributionMetric(
55
71
  TELEMETRY_ENDPOINT_PAYLOAD_REQUESTS_MS,
56
72
  { endpoint: 'code_coverage' },
@@ -66,7 +82,7 @@ class Writer extends BaseWriter {
66
82
  { endpoint: 'code_coverage' }
67
83
  )
68
84
  log.error('Error sending CI coverage payload', err)
69
- done()
85
+ done(err)
70
86
  return
71
87
  }
72
88
  log.debug('Response from the intake:', res)
@@ -1,27 +1,42 @@
1
1
  'use strict'
2
2
  const getConfig = require('../../../config')
3
- const request = require('../../../exporters/common/request')
4
3
  const log = require('../../../log')
5
4
  const { safeJSONStringify } = require('../../../exporters/common/util')
6
5
  const { JSONEncoder } = require('../../encode/json-encoder')
7
6
  const { DEBUGGER_INPUT_V1 } = require('../../../debugger/constants')
8
-
9
7
  const BaseWriter = require('../../../exporters/common/writer')
10
8
 
9
+ const request = require('../request')
10
+ const TestOptimizationRequestTracker = require('./request-tracker')
11
+
11
12
  // Writer used by the integration between Dynamic Instrumentation and Test Visibility
12
13
  // It is used to encode and send logs to both the logs intake directly and the
13
14
  // `/debugger/v1/input` endpoint in the agent, which is a proxy to the logs intake.
14
15
  class DynamicInstrumentationLogsWriter extends BaseWriter {
16
+ #requestTracker
17
+
15
18
  // TODO: what's a good value for timeout for the logs intake?
16
19
  constructor ({ url, timeout = 15_000, isAgentProxy = false }) {
17
20
  super(...arguments)
21
+ this.#requestTracker = new TestOptimizationRequestTracker(this)
18
22
  this._url = url
19
23
  this._encoder = new JSONEncoder()
20
24
  this._isAgentProxy = isAgentProxy
21
25
  this.timeout = timeout
22
26
  }
23
27
 
24
- _sendPayload (data, _, done) {
28
+ /**
29
+ * Flushes buffered logs, waiting for tracked requests during finalization.
30
+ *
31
+ * @param {(error?: Error) => void} [done]
32
+ * @param {{ deadline?: number }} [options]
33
+ * @returns {void}
34
+ */
35
+ flush (done, options) {
36
+ this.#requestTracker.flush(done, options)
37
+ }
38
+
39
+ _sendPayload (data, _, done, flushOptions) {
25
40
  const options = {
26
41
  path: '/api/v2/logs',
27
42
  method: 'POST',
@@ -31,6 +46,7 @@ class DynamicInstrumentationLogsWriter extends BaseWriter {
31
46
  },
32
47
  timeout: this.timeout,
33
48
  url: this._url,
49
+ deadline: flushOptions?.deadline,
34
50
  }
35
51
 
36
52
  if (this._isAgentProxy) {
@@ -41,10 +57,10 @@ class DynamicInstrumentationLogsWriter extends BaseWriter {
41
57
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
42
58
  log.debug(() => `Request to the logs intake: ${safeJSONStringify(options)}`)
43
59
 
44
- request(data, options, (err, res) => {
60
+ this.#requestTracker.send(request, data, options, (err, res) => {
45
61
  if (err) {
46
62
  log.error('Error sending DI logs payload', err)
47
- done()
63
+ done(err)
48
64
  return
49
65
  }
50
66
  log.debug('Response from the logs intake:', res)