dd-trace 5.105.0 → 5.107.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 (108) hide show
  1. package/index.d.ts +20 -1
  2. package/package.json +5 -7
  3. package/packages/datadog-core/src/storage.js +47 -48
  4. package/packages/datadog-esbuild/index.js +6 -1
  5. package/packages/datadog-instrumentations/src/ai.js +12 -3
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +3 -2
  7. package/packages/datadog-instrumentations/src/body-parser.js +5 -2
  8. package/packages/datadog-instrumentations/src/connect.js +3 -2
  9. package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
  10. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +19 -0
  11. package/packages/datadog-instrumentations/src/cucumber.js +319 -152
  12. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
  13. package/packages/datadog-instrumentations/src/express-session.js +12 -11
  14. package/packages/datadog-instrumentations/src/express.js +24 -20
  15. package/packages/datadog-instrumentations/src/fastify.js +18 -6
  16. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
  17. package/packages/datadog-instrumentations/src/http/client.js +9 -12
  18. package/packages/datadog-instrumentations/src/http/server.js +30 -16
  19. package/packages/datadog-instrumentations/src/http2/client.js +15 -12
  20. package/packages/datadog-instrumentations/src/http2/server.js +15 -8
  21. package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
  22. package/packages/datadog-instrumentations/src/jest.js +143 -73
  23. package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
  25. package/packages/datadog-instrumentations/src/multer.js +3 -2
  26. package/packages/datadog-instrumentations/src/mysql2.js +34 -0
  27. package/packages/datadog-instrumentations/src/net.js +8 -6
  28. package/packages/datadog-instrumentations/src/openai.js +19 -7
  29. package/packages/datadog-instrumentations/src/pg.js +19 -0
  30. package/packages/datadog-instrumentations/src/router.js +12 -10
  31. package/packages/datadog-instrumentations/src/vitest.js +29 -4
  32. package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
  33. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
  34. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
  35. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
  36. package/packages/datadog-plugin-cucumber/src/index.js +2 -0
  37. package/packages/datadog-plugin-cypress/src/support.js +31 -1
  38. package/packages/datadog-plugin-http/src/client.js +0 -3
  39. package/packages/datadog-plugin-http/src/server.js +11 -1
  40. package/packages/datadog-plugin-mocha/src/index.js +2 -0
  41. package/packages/datadog-plugin-pg/src/index.js +10 -0
  42. package/packages/dd-trace/src/aiguard/index.js +34 -15
  43. package/packages/dd-trace/src/aiguard/sdk.js +34 -3
  44. package/packages/dd-trace/src/aiguard/tags.js +6 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  46. package/packages/dd-trace/src/config/defaults.js +14 -0
  47. package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
  48. package/packages/dd-trace/src/config/helper.js +1 -0
  49. package/packages/dd-trace/src/config/index.js +5 -9
  50. package/packages/dd-trace/src/config/parsers.js +8 -0
  51. package/packages/dd-trace/src/config/supported-configurations.json +13 -6
  52. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  53. package/packages/dd-trace/src/datastreams/writer.js +1 -2
  54. package/packages/dd-trace/src/debugger/config.js +1 -1
  55. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
  56. package/packages/dd-trace/src/debugger/index.js +1 -2
  57. package/packages/dd-trace/src/dogstatsd.js +2 -3
  58. package/packages/dd-trace/src/encode/0.4.js +49 -41
  59. package/packages/dd-trace/src/encode/agentless-json.js +5 -1
  60. package/packages/dd-trace/src/encode/tags-processors.js +14 -0
  61. package/packages/dd-trace/src/exporters/agent/index.js +1 -2
  62. package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
  63. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
  64. package/packages/dd-trace/src/exporters/common/request.js +26 -0
  65. package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
  66. package/packages/dd-trace/src/id.js +15 -0
  67. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +91 -5
  68. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
  69. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
  70. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
  71. package/packages/dd-trace/src/llmobs/sdk.js +4 -1
  72. package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
  73. package/packages/dd-trace/src/llmobs/tagger.js +5 -3
  74. package/packages/dd-trace/src/llmobs/util.js +54 -0
  75. package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
  76. package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
  77. package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
  78. package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
  79. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
  80. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
  81. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
  82. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +25 -5
  83. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -10
  84. package/packages/dd-trace/src/opentracing/span.js +23 -18
  85. package/packages/dd-trace/src/opentracing/span_context.js +1 -3
  86. package/packages/dd-trace/src/opentracing/tracer.js +16 -12
  87. package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
  88. package/packages/dd-trace/src/priority_sampler.js +6 -5
  89. package/packages/dd-trace/src/profiling/config.js +11 -25
  90. package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
  91. package/packages/dd-trace/src/profiling/profiler.js +19 -9
  92. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -3
  93. package/packages/dd-trace/src/proxy.js +13 -10
  94. package/packages/dd-trace/src/remote_config/index.js +1 -2
  95. package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
  96. package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
  97. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
  98. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
  99. package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
  100. package/packages/dd-trace/src/span_format.js +33 -25
  101. package/packages/dd-trace/src/span_stats.js +1 -1
  102. package/packages/dd-trace/src/startup-log.js +1 -2
  103. package/packages/dd-trace/src/telemetry/send-data.js +1 -1
  104. package/packages/dd-trace/src/tracer.js +1 -1
  105. package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
  106. package/vendor/dist/shell-quote/index.js +1 -1
  107. package/packages/dd-trace/src/agent/url.js +0 -28
  108. package/scripts/preinstall.js +0 -34
@@ -5,6 +5,43 @@ const BaseAwsSdkPlugin = require('../base')
5
5
  const { DsmPathwayCodec, getHeadersSize } = require('../../../dd-trace/src/datastreams')
6
6
  const { extractQueueMetadata, isEmpty } = require('../util')
7
7
 
8
+ /**
9
+ * @typedef {{
10
+ * 'detail-type'?: string,
11
+ * detail?: { _datadog?: Record<string, string> },
12
+ * Type?: string,
13
+ * Message?: string
14
+ * }} ParsedSqsBody
15
+ */
16
+
17
+ /**
18
+ * Resolve the EventBridge `_datadog` text map from a parsed SQS body — for both
19
+ * EventBridge -> SQS (`body.detail._datadog`) and EventBridge -> SNS -> SQS (the
20
+ * envelope is the SNS `Notification`'s stringified `Message`). Keyed off
21
+ * `detail-type`, the marker AWS sets on every PutEvents delivery. Relies on the
22
+ * default SQS-target shape; a target InputTransformer can drop `detail`.
23
+ *
24
+ * @param {ParsedSqsBody} [parsedBody]
25
+ * @returns {Record<string, string> | undefined}
26
+ */
27
+ function getEventBridgeContext (parsedBody) {
28
+ let envelope
29
+ if (parsedBody?.['detail-type'] !== undefined) {
30
+ envelope = parsedBody // EventBridge -> SQS
31
+ } else if (parsedBody?.Type === 'Notification' && typeof parsedBody.Message === 'string') {
32
+ // EventBridge -> SNS -> SQS
33
+ try {
34
+ const innerEnvelope = JSON.parse(parsedBody.Message)
35
+ if (innerEnvelope?.['detail-type'] !== undefined) {
36
+ envelope = innerEnvelope
37
+ }
38
+ } catch {
39
+ // SNS `Message` not JSON
40
+ }
41
+ }
42
+ return envelope?.detail?._datadog
43
+ }
44
+
8
45
  class Sqs extends BaseAwsSdkPlugin {
9
46
  static id = 'sqs'
10
47
  static peerServicePrecursors = ['queuename']
@@ -149,17 +186,28 @@ class Sqs extends BaseAwsSdkPlugin {
149
186
  }
150
187
  }
151
188
 
152
- if (!message.MessageAttributes || !message.MessageAttributes._datadog) {
153
- return { parsedBody, bodyChecked: true }
189
+ // Check MessageAttributes first (common direct-SQS/SNS path): avoids parsing
190
+ // the body (e.g. a large SNS `Message`) just to rule out an EventBridge
191
+ // envelope. Precedence matches responseExtractDSMContext.
192
+ const datadogAttribute = message.MessageAttributes?._datadog
193
+ if (datadogAttribute) {
194
+ const parsedAttributes = this.parseDatadogAttributes(datadogAttribute)
195
+ if (parsedAttributes) {
196
+ return {
197
+ datadogContext: this.tracer.extract('text_map', parsedAttributes),
198
+ parsedAttributes,
199
+ parsedBody,
200
+ bodyChecked: true,
201
+ }
202
+ }
154
203
  }
155
204
 
156
- const datadogAttribute = message.MessageAttributes._datadog
157
-
158
- const parsedAttributes = this.parseDatadogAttributes(datadogAttribute)
159
- if (parsedAttributes) {
205
+ // Then the EventBridge envelope (optionally via SNS); see getEventBridgeContext.
206
+ const eventBridgeContext = getEventBridgeContext(parsedBody)
207
+ if (eventBridgeContext) {
160
208
  return {
161
- datadogContext: this.tracer.extract('text_map', parsedAttributes),
162
- parsedAttributes,
209
+ datadogContext: this.tracer.extract('text_map', eventBridgeContext),
210
+ parsedAttributes: eventBridgeContext,
163
211
  parsedBody,
164
212
  bodyChecked: true,
165
213
  }
@@ -213,15 +261,18 @@ class Sqs extends BaseAwsSdkPlugin {
213
261
  if (body?.Type === 'Notification') {
214
262
  message = body
215
263
  }
216
- if (message.MessageAttributes && message.MessageAttributes._datadog) {
217
- parsedAttributes = this.parseDatadogAttributes(message.MessageAttributes._datadog)
218
- }
264
+ // MessageAttributes for direct SQS/SNS; else the EventBridge envelope.
265
+ parsedAttributes = message.MessageAttributes?._datadog
266
+ ? this.parseDatadogAttributes(message.MessageAttributes._datadog)
267
+ : getEventBridgeContext(body)
219
268
  }
220
269
  const payloadSize = getHeadersSize({
221
270
  Body: message.Body,
222
271
  MessageAttributes: message.MessageAttributes,
223
272
  })
224
273
  if (parsedAttributes) {
274
+ // Inert for EventBridge until its producer emits a pathway (separate
275
+ // change) — no `dd-pathway-ctx-base64` to decode yet; SQS/SNS decode now.
225
276
  this.tracer.decodeDataStreamsContext(parsedAttributes)
226
277
  }
227
278
  this.tracer
@@ -80,6 +80,7 @@ class CucumberPlugin extends CiPlugin {
80
80
  isTestManagementTestsEnabled,
81
81
  isParallel,
82
82
  }) => {
83
+ this._exportPendingWorkerTraces()
83
84
  const {
84
85
  isSuitesSkippingEnabled,
85
86
  isCodeCoverageEnabled,
@@ -186,6 +187,7 @@ class CucumberPlugin extends CiPlugin {
186
187
  integrationName: this.constructor.id,
187
188
  })
188
189
  this._testSuiteSpansByTestSuite.set(testSuitePath, testSuiteSpan)
190
+ this._exportPendingWorkerTracesForTestSuite(testSuitePath)
189
191
 
190
192
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
191
193
  if (this.libraryConfig?.isCodeCoverageEnabled) {
@@ -136,6 +136,7 @@ function getRetriedTests (test, numRetries, tags) {
136
136
  // TODO: signal in framework logs that this is a retry.
137
137
  // TODO: Change it so these tests are allowed to fail.
138
138
  const clonedTest = test.clone()
139
+ disableFrameworkRetries(clonedTest)
139
140
  if (tags.includes('_ddIsEfdRetry')) {
140
141
  clonedTest._ddEfdRetryIndex = retryIndex + 1
141
142
  }
@@ -149,6 +150,19 @@ function getRetriedTests (test, numRetries, tags) {
149
150
  return retriedTests
150
151
  }
151
152
 
153
+ function disableFrameworkRetries (test) {
154
+ test._retries = 0
155
+ }
156
+
157
+ function shouldDisableFrameworkRetries (test) {
158
+ return test && (
159
+ test._ddIsAttemptToFix ||
160
+ (isTestIsolationEnabled &&
161
+ isEarlyFlakeDetectionEnabled &&
162
+ (test._ddIsNew || test._ddIsModified))
163
+ )
164
+ }
165
+
152
166
  const oldRunTests = Cypress.mocha.getRunner().runTests
153
167
  Cypress.mocha.getRunner().runTests = function (suite, fn) {
154
168
  if (!isKnownTestsEnabled && !isTestManagementEnabled && !isImpactedTestsEnabled) {
@@ -188,9 +202,11 @@ Cypress.mocha.getRunner().runTests = function (suite, fn) {
188
202
  let retryMessage = ''
189
203
  if (isAtemptToFix) {
190
204
  test._ddIsAttemptToFix = true
205
+ disableFrameworkRetries(test)
191
206
  retryMessage = 'because it is an attempt to fix'
192
207
  retriedTests = getRetriedTests(test, testManagementAttemptToFixRetries, ['_ddIsAttemptToFix'])
193
208
  } else if (isModified && isEarlyFlakeDetectionEnabled) {
209
+ disableFrameworkRetries(test)
194
210
  retryMessage = 'to detect flakes because it is modified'
195
211
  retriedTests = getRetriedTests(test, earlyFlakeDetectionNumRetries, [
196
212
  '_ddIsModified',
@@ -198,6 +214,7 @@ Cypress.mocha.getRunner().runTests = function (suite, fn) {
198
214
  isKnownTestsEnabled && isNewTest(test) && '_ddIsNew',
199
215
  ])
200
216
  } else if (isNew && isEarlyFlakeDetectionEnabled) {
217
+ disableFrameworkRetries(test)
201
218
  retryMessage = 'to detect flakes because it is new'
202
219
  retriedTests = getRetriedTests(test, earlyFlakeDetectionNumRetries, ['_ddIsNew', '_ddIsEfdRetry'])
203
220
  }
@@ -214,8 +231,21 @@ Cypress.mocha.getRunner().runTests = function (suite, fn) {
214
231
  return oldRunTests.apply(this, [suite, fn])
215
232
  }
216
233
 
234
+ Cypress.on('test:before:run', (attributes, test) => {
235
+ if (shouldDisableFrameworkRetries(test)) {
236
+ disableFrameworkRetries(test)
237
+ }
238
+ })
239
+
240
+ Cypress.on('test:before:run:async', (attributes, test) => {
241
+ if (shouldDisableFrameworkRetries(test)) {
242
+ disableFrameworkRetries(test)
243
+ }
244
+ })
245
+
217
246
  beforeEach(function () {
218
- const testName = Cypress.mocha.getRunner().suite.ctx.currentTest.fullTitle()
247
+ const currentTest = Cypress.mocha.getRunner().suite.ctx.currentTest
248
+ const testName = currentTest.fullTitle()
219
249
 
220
250
  const retryMessage = retryReasonsByTestName.get(testName)
221
251
  if (retryMessage) {
@@ -5,7 +5,6 @@ const { URL } = require('url')
5
5
  const ClientPlugin = require('../../dd-trace/src/plugins/client')
6
6
  const { storage } = require('../../datadog-core')
7
7
  const tags = require('../../../ext/tags')
8
- const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
9
8
  const formats = require('../../../ext/formats')
10
9
  const HTTP_HEADERS = formats.HTTP_HEADERS
11
10
  const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
@@ -69,8 +68,6 @@ class HttpClientPlugin extends ClientPlugin {
69
68
  this.tracer.inject(span, HTTP_HEADERS, options.headers)
70
69
  }
71
70
 
72
- analyticsSampler.sample(span, this.config.measured)
73
-
74
71
  message.span = span
75
72
  message.parentStore = store
76
73
  message.currentStore = { ...store, span }
@@ -34,6 +34,11 @@ class HttpServerPlugin extends ServerPlugin {
34
34
  if (this.#startConfig === undefined) {
35
35
  this.#refreshStartCache()
36
36
  }
37
+ // A fresh request has no span yet; web.startSpan creates and enters one.
38
+ // A reused context (HTTP/2 stream, serverless re-entry) returns the
39
+ // existing span without entering, so the explicit enter below is the only
40
+ // one in that case.
41
+ const spanCreated = !web.getContext(req)?.span
37
42
  const span = web.startSpan(
38
43
  this.tracer,
39
44
  this.#startConfig,
@@ -57,7 +62,12 @@ class HttpServerPlugin extends ServerPlugin {
57
62
  store = withRequest(store, req)
58
63
  }
59
64
 
60
- this.enter(span, store)
65
+ // Skip the re-enter when web.startSpan already entered { ...store, span }
66
+ // and AppSec did not rebuild the store with req: the bind would be
67
+ // identical to the one startSpan just made.
68
+ if (!spanCreated || appsecActive) {
69
+ this.enter(span, store)
70
+ }
61
71
 
62
72
  if (!context.instrumented) {
63
73
  context.res.writeHead = web.wrapWriteHead(context)
@@ -150,6 +150,7 @@ class MochaPlugin extends CiPlugin {
150
150
  ctx.parentStore = store
151
151
  ctx.currentStore = { ...store, testSuiteSpan }
152
152
  this._testSuiteSpansByTestSuite.set(testSuite, testSuiteSpan)
153
+ this._exportPendingWorkerTracesForTestSuite(testSuite)
153
154
  })
154
155
 
155
156
  this.addSub('ci:mocha:test-suite:finish', ({ testSuiteSpan, status }) => {
@@ -365,6 +366,7 @@ class MochaPlugin extends CiPlugin {
365
366
  isTestManagementEnabled,
366
367
  isParallel,
367
368
  }) => {
369
+ this._exportPendingWorkerTraces()
368
370
  if (this.testSessionSpan) {
369
371
  const {
370
372
  isSuitesSkippingEnabled,
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { storage } = require('../../datadog-core')
3
4
  const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
4
5
  const DatabasePlugin = require('../../dd-trace/src/plugins/database')
5
6
 
@@ -8,6 +9,15 @@ class PGPlugin extends DatabasePlugin {
8
9
  static operation = 'query'
9
10
  static system = 'postgres'
10
11
 
12
+ constructor () {
13
+ super(...arguments)
14
+
15
+ this.addSub('apm:pg:pool:connect:start', ctx => {
16
+ ctx.parentStore = storage('legacy').getStore()
17
+ })
18
+ this.addBind('apm:pg:pool:connect:finish', ctx => ctx.parentStore)
19
+ }
20
+
11
21
  bindStart (ctx) {
12
22
  const { params = {}, query, originalText, processId, stream } = ctx
13
23
  const service = this.serviceName({ pluginConfig: this.config, params })
@@ -44,27 +44,46 @@ function disable () {
44
44
  /**
45
45
  * Handles channel messages with pre-converted messages.
46
46
  *
47
- * @param {{messages: Array<object>, integration?: string, resolve: Function, reject: Function}} ctx
47
+ * @param {object} ctx
48
+ * @param {Array<object>} ctx.messages
49
+ * @param {string} [ctx.integration]
50
+ * @param {object} [ctx.parentSpan] - LLM span to parent the `ai_guard` span under.
51
+ * @param {AbortController} ctx.abortController
52
+ * @param {Array<Promise<void>>} ctx.pending - Subscribers push only when they evaluate.
48
53
  */
49
54
  function onEvaluate (ctx) {
55
+ // Decline to evaluate empty payloads by not pushing to pending.
50
56
  if (!ctx.messages?.length) {
51
- ctx.resolve()
52
57
  return
53
58
  }
54
59
 
55
- const opts = { block, source: SOURCE_AUTO, integration: ctx.integration || INTEGRATION_NONE }
56
- aiguard.evaluate(ctx.messages, opts)
57
- .then(() => {
58
- ctx.resolve()
59
- })
60
- .catch(err => {
61
- if (err.name === 'AIGuardAbortError') {
62
- ctx.reject(err)
63
- } else {
64
- log.error('AIGuard: unexpected error during evaluation: %s', err.message)
65
- ctx.resolve()
66
- }
67
- })
60
+ const opts = {
61
+ block,
62
+ source: SOURCE_AUTO,
63
+ integration: ctx.integration || INTEGRATION_NONE,
64
+ childOf: ctx.parentSpan,
65
+ }
66
+
67
+ try {
68
+ ctx.pending.push(aiguard.evaluate(ctx.messages, opts).catch(handleEvaluationError.bind(null, ctx)))
69
+ } catch (err) {
70
+ ctx.pending.push(Promise.resolve().then(() => handleEvaluationError(ctx, err)))
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Handles an AI Guard evaluation failure.
76
+ *
77
+ * @param {object} ctx
78
+ * @param {AbortController} ctx.abortController
79
+ * @param {Error} err
80
+ */
81
+ function handleEvaluationError (ctx, err) {
82
+ if (err.name === 'AIGuardAbortError') {
83
+ ctx.abortController.abort(err)
84
+ } else {
85
+ log.error('AIGuard: unexpected error during evaluation: %s', err.message)
86
+ }
68
87
  }
69
88
 
70
89
  module.exports = { enable, disable }
@@ -1,7 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
4
- const { HTTP_CLIENT_IP, NETWORK_CLIENT_IP } = require('../../../../ext/tags')
4
+ const { HTTP_CLIENT_IP, NETWORK_CLIENT_IP, HTTP_USERAGENT } = require('../../../../ext/tags')
5
+ const { USER_ID, USER_SESSION_ID } = require('../appsec/addresses')
5
6
  const { getActiveRequest } = require('../appsec/store')
6
7
  const log = require('../log')
7
8
  const { extractIp } = require('../plugins/util/ip_extractor')
@@ -17,6 +18,16 @@ const aiguardMetrics = telemetryMetrics.manager.namespace('ai_guard')
17
18
 
18
19
  const ALLOW = 'ALLOW'
19
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
+
20
31
  /**
21
32
  * Reports a telemetry error
22
33
  *
@@ -181,13 +192,32 @@ class AIGuard extends NoopAIGuard {
181
192
  }
182
193
  }
183
194
 
195
+ /**
196
+ * Copies service entry span tags needed by anomaly detection to the AI Guard span.
197
+ *
198
+ * @param {import('../opentracing/span')} guardSpan
199
+ * @param {import('../opentracing/span')} rootSpan
200
+ */
201
+ #copyServiceEntryTagsToGuardSpan (guardSpan, rootSpan) {
202
+ const rootTags = rootSpan.context().getTags()
203
+ for (const [sourceTag, destTag] of SERVICE_ENTRY_TAG_MAPPINGS) {
204
+ const value = rootTags[sourceTag]
205
+ if (value !== undefined && value !== null) {
206
+ guardSpan.setTag(destTag, value)
207
+ }
208
+ }
209
+ }
210
+
184
211
  evaluate (messages, opts) {
185
212
  if (!this.#initialized) {
186
213
  return super.evaluate(messages, opts)
187
214
  }
188
- const { block = true, source = TAGS.SOURCE_SDK, integration = TAGS.INTEGRATION_NONE } = opts ?? {}
215
+ const { block = true, source = TAGS.SOURCE_SDK, integration = TAGS.INTEGRATION_NONE, childOf } = opts ?? {}
189
216
  const telemetryTags = { source, integration }
190
- return this.#tracer.trace(TAGS.RESOURCE, {}, async (span) => {
217
+ // Only pass `childOf` when truthy so `tracer.trace`'s default (`scope().active()`)
218
+ // still applies for SDK callers that don't supply an explicit parent.
219
+ const traceOpts = childOf ? { childOf } : {}
220
+ return this.#tracer.trace(TAGS.RESOURCE, traceOpts, async (span) => {
191
221
  const last = messages[messages.length - 1]
192
222
  const target = this.#isToolCall(last) ? 'tool' : 'prompt'
193
223
  span.setTag(TAGS.TARGET_TAG_KEY, target)
@@ -206,6 +236,7 @@ class AIGuard extends NoopAIGuard {
206
236
  const rootSpan = span.context()?._trace?.started?.[0]
207
237
  if (rootSpan) {
208
238
  this.#setRootSpanClientIpTags(rootSpan)
239
+ this.#copyServiceEntryTagsToGuardSpan(span, rootSpan)
209
240
  // keepTrace must be called before executeRequest so the sampling decision
210
241
  // is propagated correctly to outgoing HTTP client calls.
211
242
  keepTrace(rootSpan, AI_GUARD)
@@ -10,6 +10,12 @@ module.exports = {
10
10
  EVENT_TAG_KEY: 'ai_guard.event',
11
11
  META_STRUCT_KEY: 'ai_guard',
12
12
 
13
+ HTTP_USERAGENT_TAG_KEY: 'ai_guard.http.useragent',
14
+ HTTP_CLIENT_IP_TAG_KEY: 'ai_guard.http.client_ip',
15
+ NETWORK_CLIENT_IP_TAG_KEY: 'ai_guard.network.client.ip',
16
+ USR_ID_TAG_KEY: 'ai_guard.usr.id',
17
+ USR_SESSION_ID_TAG_KEY: 'ai_guard.usr.session_id',
18
+
13
19
  TELEMETRY_REQUESTS: 'requests',
14
20
  TELEMETRY_TRUNCATED: 'truncated',
15
21
  TELEMETRY_ERROR: 'error',
@@ -9,7 +9,7 @@ const CoverageWriter = require('./coverage-writer')
9
9
  class AgentlessCiVisibilityExporter extends CiVisibilityExporter {
10
10
  constructor (config) {
11
11
  super(config)
12
- const { tags, site, url, isTestDynamicInstrumentationEnabled } = config
12
+ const { tags, site, DD_CIVISIBILITY_AGENTLESS_URL: url, isTestDynamicInstrumentationEnabled } = config
13
13
  // we don't need to request /info because we are using agentless by configuration
14
14
  this._isInitialized = true
15
15
  this._resolveCanUseCiVisProtocol(true)
@@ -72,6 +72,12 @@ for (const [name, value] of Object.entries(defaults)) {
72
72
  function generateTelemetry (value = null, origin, optionName) {
73
73
  const tableEntry = configurationsTable[optionName]
74
74
  const { type, canonicalName = optionName } = tableEntry ?? { type: typeof value }
75
+ // Sensitive configurations are excluded from configuration telemetry: their
76
+ // entry is never added to `configWithOrigin`, the single source for every
77
+ // telemetry path (app-started and app-client-configuration-change).
78
+ if (sensitiveConfigurations.has(canonicalName)) {
79
+ return
80
+ }
75
81
  // TODO: Should we not send defaults to telemetry to reduce size?
76
82
  // TODO: How to handle aliases/actual names in the future? Optional fields? Normalize the name at intake?
77
83
  // TODO: Validate that space separated tags are parsed by the backend. Optimizations would be possible with that.
@@ -196,6 +202,11 @@ const fallbackConfigurations = new Map()
196
202
 
197
203
  const regExps = {}
198
204
 
205
+ // Canonical names of configurations whose value is excluded from configuration
206
+ // telemetry. Driven by the `sensitive: true` attribute in
207
+ // `supported-configurations.json` so new entries opt in without code changes.
208
+ const sensitiveConfigurations = new Set()
209
+
199
210
  for (const [canonicalName, entries] of Object.entries(supportedConfigurations)) {
200
211
  if (entries.length !== 1) {
201
212
  // TODO: Determine if we really want to support multiple entries for a canonical name.
@@ -207,6 +218,9 @@ for (const [canonicalName, entries] of Object.entries(supportedConfigurations))
207
218
  )
208
219
  }
209
220
  for (const entry of entries) {
221
+ if (entry.sensitive) {
222
+ sensitiveConfigurations.add(canonicalName)
223
+ }
210
224
  const configurationNames = entry.internalPropertyName ? [entry.internalPropertyName] : entry.configurationNames
211
225
  const fullPropertyName = configurationNames?.[0] ?? canonicalName
212
226
  const type = entry.type.toUpperCase()
@@ -73,7 +73,7 @@ export interface GeneratedConfig {
73
73
  DD_APP_KEY: string | undefined;
74
74
  DD_AZURE_RESOURCE_GROUP: string | undefined;
75
75
  DD_CIVISIBILITY_AGENTLESS_ENABLED: boolean;
76
- DD_CIVISIBILITY_AGENTLESS_URL: string | undefined;
76
+ DD_CIVISIBILITY_AGENTLESS_URL: URL | undefined;
77
77
  DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER: string | undefined;
78
78
  DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE: boolean;
79
79
  DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING: boolean;
@@ -13,6 +13,7 @@
13
13
  * @property {string} [transform]
14
14
  * @property {string} [allowed]
15
15
  * @property {string|boolean} [deprecated]
16
+ * @property {boolean} [sensitive] Excludes the configuration value from configuration telemetry.
16
17
  */
17
18
 
18
19
  /**
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('node:fs')
4
4
  const os = require('node:os')
5
- const { URL } = require('node:url')
5
+ const { URL, format } = require('node:url')
6
6
 
7
7
  const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
8
8
  const uuid = require('../../../../vendor/dist/crypto-randomuuid') // we need to keep the old uuid dep because of cypress
@@ -322,18 +322,14 @@ class Config extends ConfigBase {
322
322
  #applyCalculated () {
323
323
  undo(this, 'calculated')
324
324
 
325
- if (this.DD_CIVISIBILITY_AGENTLESS_URL ||
326
- this.url ||
325
+ if (this.url ||
327
326
  os.type() !== 'Windows_NT' &&
328
327
  !trackedConfigOrigins.has('hostname') &&
329
328
  !trackedConfigOrigins.has('port') &&
330
- !this.DD_CIVISIBILITY_AGENTLESS_ENABLED &&
331
329
  fs.existsSync('/var/run/datadog/apm.socket')) {
332
- setAndTrack(
333
- this,
334
- 'url',
335
- new URL(this.DD_CIVISIBILITY_AGENTLESS_URL || this.url || 'unix:///var/run/datadog/apm.socket')
336
- )
330
+ setAndTrack(this, 'url', new URL(this.url || 'unix:///var/run/datadog/apm.socket'))
331
+ } else {
332
+ setAndTrack(this, 'url', new URL(format({ protocol: 'http:', hostname: this.hostname, port: this.port })))
337
333
  }
338
334
 
339
335
  if (this.isCiVisibility) {
@@ -145,6 +145,14 @@ const transformers = {
145
145
  }
146
146
  return value.replaceAll(/\s*:\s*/g, ':')
147
147
  },
148
+ /**
149
+ * @param {string} value
150
+ */
151
+ toURL (value) {
152
+ try {
153
+ return new URL(value)
154
+ } catch {}
155
+ },
148
156
  validatePropagationStyles (value, optionName) {
149
157
  value = transformers.toLowerCase(value)
150
158
  for (let index = 0; index < value.length; index++) {
@@ -111,7 +111,8 @@
111
111
  "aliases": [
112
112
  "DATADOG_API_KEY"
113
113
  ],
114
- "internalPropertyName": "apiKey"
114
+ "internalPropertyName": "apiKey",
115
+ "sensitive": true
115
116
  }
116
117
  ],
117
118
  "DD_API_SECURITY_ENABLED": [
@@ -423,7 +424,8 @@
423
424
  {
424
425
  "implementation": "A",
425
426
  "type": "string",
426
- "default": null
427
+ "default": null,
428
+ "sensitive": true
427
429
  }
428
430
  ],
429
431
  "DD_AZURE_RESOURCE_GROUP": [
@@ -444,6 +446,7 @@
444
446
  {
445
447
  "implementation": "A",
446
448
  "type": "string",
449
+ "transform": "toURL",
447
450
  "default": null
448
451
  }
449
452
  ],
@@ -3913,7 +3916,8 @@
3913
3916
  {
3914
3917
  "implementation": "B",
3915
3918
  "type": "map",
3916
- "default": null
3919
+ "default": null,
3920
+ "sensitive": true
3917
3921
  }
3918
3922
  ],
3919
3923
  "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": [
@@ -3930,7 +3934,8 @@
3930
3934
  "default": null,
3931
3935
  "aliases": [
3932
3936
  "OTEL_EXPORTER_OTLP_HEADERS"
3933
- ]
3937
+ ],
3938
+ "sensitive": true
3934
3939
  }
3935
3940
  ],
3936
3941
  "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL": [
@@ -3968,7 +3973,8 @@
3968
3973
  "default": null,
3969
3974
  "aliases": [
3970
3975
  "OTEL_EXPORTER_OTLP_HEADERS"
3971
- ]
3976
+ ],
3977
+ "sensitive": true
3972
3978
  }
3973
3979
  ],
3974
3980
  "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL": [
@@ -4006,7 +4012,8 @@
4006
4012
  "default": null,
4007
4013
  "aliases": [
4008
4014
  "OTEL_EXPORTER_OTLP_HEADERS"
4009
- ]
4015
+ ],
4016
+ "sensitive": true
4010
4017
  }
4011
4018
  ],
4012
4019
  "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": [
@@ -7,7 +7,6 @@ const libdatadog = require('@datadog/libdatadog')
7
7
  const binding = libdatadog.load('crashtracker')
8
8
 
9
9
  const log = require('../log')
10
- const { getAgentUrl } = require('../agent/url')
11
10
  const pkg = require('../../../../package.json')
12
11
  const processTags = require('../process-tags')
13
12
 
@@ -68,7 +67,7 @@ class Crashtracker {
68
67
  * @param {import('../config/config-base')} config - Tracer configuration
69
68
  */
70
69
  #getConfig (config) {
71
- const url = getAgentUrl(config)
70
+ const url = config.url
72
71
 
73
72
  // Out-of-process symbolication currently works on
74
73
  // Linux only, does not work on Mac.
@@ -78,6 +77,7 @@ class Crashtracker {
78
77
 
79
78
  return {
80
79
  additional_files: [],
80
+ collect_all_threads: true,
81
81
  create_alt_stack: true,
82
82
  use_alt_stack: true,
83
83
  endpoint: {
@@ -5,7 +5,6 @@ const pkg = require('../../../../package.json')
5
5
  const log = require('../log')
6
6
  const request = require('../exporters/common/request')
7
7
  const { encode: encodeMsgpack } = require('../msgpack')
8
- const { getAgentUrl } = require('../agent/url')
9
8
 
10
9
  function makeRequest (data, url, cb) {
11
10
  const options = {
@@ -29,7 +28,7 @@ function makeRequest (data, url, cb) {
29
28
 
30
29
  class DataStreamsWriter {
31
30
  constructor (config) {
32
- this._url = getAgentUrl(config)
31
+ this._url = config.url
33
32
  }
34
33
 
35
34
  flush (payload) {
@@ -16,7 +16,7 @@ module.exports = function getDebuggerConfig (config, inputPath) {
16
16
  repositoryUrl,
17
17
  runtimeId: config.tags['runtime-id'],
18
18
  service: config.service,
19
- url: config.url?.toString(),
19
+ url: config.url.toString(),
20
20
  version: config.version,
21
21
  inputPath,
22
22
  }