dd-trace 5.123.0 → 6.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE-3rdparty.csv +0 -5
  2. package/index.d.ts +25 -240
  3. package/package.json +18 -19
  4. package/packages/datadog-instrumentations/src/cucumber.js +4 -4
  5. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -5
  6. package/packages/datadog-instrumentations/src/playwright-reporter.js +1 -149
  7. package/packages/datadog-instrumentations/src/playwright.js +33 -33
  8. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +8 -6
  9. package/packages/datadog-plugin-playwright/src/index.js +0 -4
  10. package/packages/dd-trace/src/appsec/reporter.js +0 -2
  11. package/packages/dd-trace/src/appsec/telemetry/index.js +0 -13
  12. package/packages/dd-trace/src/appsec/telemetry/rasp.js +0 -16
  13. package/packages/dd-trace/src/appsec/telemetry/waf.js +0 -24
  14. package/packages/dd-trace/src/carrier.js +48 -107
  15. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  16. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +0 -2
  17. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +3 -5
  18. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -4
  19. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +4 -6
  20. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +3 -5
  21. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +2 -4
  22. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +2 -4
  23. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +2 -4
  24. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  25. package/packages/dd-trace/src/debugger/constants.js +0 -1
  26. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  27. package/packages/dd-trace/src/debugger/devtools_client/index.js +10 -3
  28. package/packages/dd-trace/src/debugger/index.js +2 -4
  29. package/packages/dd-trace/src/exporters/common/request.js +2 -5
  30. package/packages/dd-trace/src/llmobs/experiments/client.js +2 -6
  31. package/packages/dd-trace/src/llmobs/experiments/dataset.js +11 -249
  32. package/packages/dd-trace/src/llmobs/experiments/experiment.js +10 -23
  33. package/packages/dd-trace/src/llmobs/experiments/index.js +6 -18
  34. package/packages/dd-trace/src/llmobs/experiments/noop.js +4 -64
  35. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -60
  36. package/packages/dd-trace/src/openfeature/constants/constants.js +18 -0
  37. package/packages/dd-trace/src/openfeature/index.js +4 -7
  38. package/packages/dd-trace/src/openfeature/writers/base.js +11 -153
  39. package/packages/dd-trace/src/openfeature/writers/exposures.js +14 -62
  40. package/packages/dd-trace/src/openfeature/writers/util.js +15 -97
  41. package/packages/dd-trace/src/opentracing/propagation/text_map.js +13 -22
  42. package/packages/dd-trace/src/telemetry/metrics.js +8 -62
  43. package/packages/dd-trace/src/telemetry/send-data.js +0 -1
  44. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  45. package/vendor/dist/protobufjs/index.js +1 -1
  46. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  47. package/packages/dd-trace/src/debugger/inspect-segment.js +0 -154
  48. package/packages/dd-trace/src/evp_proxy/constants.js +0 -8
  49. package/packages/dd-trace/src/evp_proxy/direct.js +0 -64
  50. package/packages/dd-trace/src/evp_proxy/discovery.js +0 -133
  51. package/packages/dd-trace/src/evp_proxy/path.js +0 -22
  52. package/vendor/dist/https-proxy-agent/LICENSE +0 -22
  53. package/vendor/dist/https-proxy-agent/index.js +0 -4
  54. package/vendor/dist/proxy-from-env/LICENSE +0 -20
  55. package/vendor/dist/proxy-from-env/index.js +0 -1
@@ -22,26 +22,6 @@ function hasEntries (value) {
22
22
  return false
23
23
  }
24
24
 
25
- function validateTagsList (tags) {
26
- if (tags == null) return []
27
- if (!Array.isArray(tags)) throw new TypeError('Tags must be an array of strings')
28
- for (const tag of tags) {
29
- if (typeof tag !== 'string') throw new TypeError('Each tag must be a string')
30
- if (tag.indexOf(':') <= 0) {
31
- throw new Error(`Tag '${tag}' is malformed. Tags must be in 'key:value' format (e.g., 'env:prod').`)
32
- }
33
- }
34
- return [...tags]
35
- }
36
-
37
- function tagOperationsAreEmpty (operations) {
38
- return operations == null || (
39
- !Object.hasOwn(operations, 'replace') &&
40
- !Object.hasOwn(operations, 'add') &&
41
- !Object.hasOwn(operations, 'remove')
42
- )
43
- }
44
-
45
25
  /**
46
26
  * @param {string} name
47
27
  */
@@ -185,20 +165,16 @@ function stringify (value) {
185
165
  * @returns {string[]}
186
166
  */
187
167
  function buildTags (userTags, autoTags) {
188
- const tagsByKey = new Map()
168
+ const tags = new Map()
189
169
  if ((userTags) != null) {
190
170
  for (const [key, value] of Object.entries(userTags)) {
191
- const values = Array.isArray(value) ? value : [value]
192
- tagsByKey.set(key, values.map(item => `${key}:${item}`))
171
+ tags.set(key, `${key}:${value}`)
193
172
  }
194
173
  }
195
174
  for (const [key, value] of Object.entries(autoTags)) {
196
- if (value !== undefined && value !== null && value !== '') tagsByKey.set(key, [`${key}:${value}`])
175
+ if (value !== undefined && value !== null && value !== '') tags.set(key, `${key}:${value}`)
197
176
  }
198
-
199
- const tags = []
200
- for (const values of tagsByKey.values()) tags.push(...values)
201
- return tags
177
+ return [...tags.values()]
202
178
  }
203
179
 
204
180
  /**
@@ -210,35 +186,6 @@ function mergeTags (baseTags, overrideTags) {
210
186
  return { ...baseTags, ...overrideTags }
211
187
  }
212
188
 
213
- /**
214
- * @param {string[] | undefined} tags
215
- * @returns {Record<string, string | string[]>}
216
- */
217
- function recordTagsToObject (tags) {
218
- const result = {}
219
- if (!Array.isArray(tags)) return result
220
- for (const tag of tags) {
221
- const separator = tag.indexOf(':')
222
- if (separator <= 0) continue
223
-
224
- const key = tag.slice(0, separator)
225
- const value = tag.slice(separator + 1)
226
- if (!Object.hasOwn(result, key)) {
227
- Object.defineProperty(result, key, {
228
- configurable: true,
229
- enumerable: true,
230
- value,
231
- writable: true,
232
- })
233
- } else if (Array.isArray(result[key])) {
234
- result[key].push(value)
235
- } else {
236
- result[key] = [result[key], value]
237
- }
238
- }
239
- return result
240
- }
241
-
242
189
  /**
243
190
  * @param {number} ms
244
191
  * @returns {Promise<void>}
@@ -302,11 +249,8 @@ module.exports = {
302
249
  mergeTags,
303
250
  normalizeEvaluators,
304
251
  normalizeJsonMetricValue,
305
- recordTagsToObject,
306
252
  sleep,
307
253
  stringify,
308
- tagOperationsAreEmpty,
309
254
  timestampMs,
310
255
  validateEvaluatorName,
311
- validateTagsList,
312
256
  }
@@ -1,6 +1,24 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
+ /**
5
+ * @constant
6
+ * @type {string} Base path for EVP proxy agent endpoint
7
+ */
8
+ EVP_PROXY_AGENT_BASE_PATH: '/evp_proxy/v2/',
9
+
10
+ /**
11
+ * @constant
12
+ * @type {string} HTTP header name for EVP subdomain routing
13
+ */
14
+ EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
15
+
16
+ /**
17
+ * @constant
18
+ * @type {string} EVP subdomain value for event platform intake
19
+ */
20
+ EVP_SUBDOMAIN_VALUE: 'event-platform-intake',
21
+
4
22
  /**
5
23
  * @constant
6
24
  * @type {string} API endpoint for exposure events EVP track
@@ -3,7 +3,7 @@
3
3
  const { channel } = require('dc-polyfill')
4
4
  const log = require('../log')
5
5
  const ExposuresWriter = require('./writers/exposures')
6
- const { setExposureDeliveryStrategy } = require('./writers/util')
6
+ const { setAgentStrategy } = require('./writers/util')
7
7
 
8
8
  const exposureSubmitCh = channel('ffe:exposure:submit')
9
9
  const flushCh = channel('ffe:writers:flush')
@@ -40,15 +40,12 @@ function enable (config) {
40
40
  return
41
41
  }
42
42
 
43
- const writer = new ExposuresWriter(config)
44
- exposuresWriter = writer
43
+ exposuresWriter = new ExposuresWriter(config)
45
44
  exposureSubmitCh.subscribe(_handleExposureSubmit)
46
45
  flushCh.subscribe(_handleFlush)
47
46
 
48
- setExposureDeliveryStrategy(config, (enabled, route) => {
49
- if (exposuresWriter !== writer) return
50
-
51
- writer.setEnabled(enabled, route)
47
+ setAgentStrategy(config, hasAgent => {
48
+ exposuresWriter?.setEnabled(hasAgent)
52
49
  })
53
50
  }
54
51
 
@@ -11,52 +11,14 @@ const log = require('../../log')
11
11
  * @property {number} [timeout] - Request timeout in milliseconds
12
12
  * @property {object} config - Tracer configuration object
13
13
  * @property {string} endpoint - API endpoint path
14
- * @property {URL} [agentUrl] - Initial delivery URL
14
+ * @property {URL} [agentUrl] - Base URL for the agent
15
15
  * @property {number} [payloadSizeLimit] - Maximum payload size in bytes
16
16
  * @property {number} [eventSizeLimit] - Maximum individual event size in bytes
17
17
  * @property {object} [headers] - Additional HTTP headers
18
18
  */
19
19
 
20
20
  /**
21
- * @typedef {object} WriterRoute
22
- * @property {URL} url - Route base URL
23
- * @property {string} endpoint - Route endpoint
24
- * @property {object} headers - Route-specific headers
25
- * @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
26
- */
27
-
28
- /**
29
- * @typedef {object} ActiveWriterRoute
30
- * @property {URL} url - Route base URL
31
- * @property {string} endpoint - Route endpoint
32
- * @property {object} requestOptions - HTTP request options
33
- */
34
-
35
- /**
36
- * Tests whether a local route definitively rejected an event batch.
37
- *
38
- * @param {Error | null} error - Request error
39
- * @param {number | undefined} statusCode - HTTP response status
40
- * @returns {boolean} Whether direct retry is safe
41
- */
42
- function isDefinitiveRejection (error, statusCode) {
43
- return error?.code === 'EAI_AGAIN' || error?.code === 'ECONNREFUSED' ||
44
- error?.code === 'ENOENT' || error?.code === 'ENOTFOUND' ||
45
- statusCode === 403 || statusCode === 404 || statusCode === 405
46
- }
47
-
48
- /**
49
- * Tests whether a local route can have accepted an event batch before failing.
50
- *
51
- * @param {Error | null} error - Request error
52
- * @returns {boolean} Whether the delivery result is ambiguous
53
- */
54
- function isAmbiguousNetworkFailure (error) {
55
- return error?.code === 'ECONNRESET' || error?.code === 'EPIPE' || error?.code === 'ETIMEDOUT'
56
- }
57
-
58
- /**
59
- * Base writer for Feature Flagging and Experimentation event delivery.
21
+ * BaseFFEWriter is the base class for sending Feature Flagging & Exposure Events payloads to the Datadog Agent.
60
22
  * @class BaseFFEWriter
61
23
  */
62
24
  class BaseFFEWriter {
@@ -78,7 +40,6 @@ class BaseFFEWriter {
78
40
  this._payloadSizeLimit = payloadSizeLimit
79
41
  this._eventSizeLimit = eventSizeLimit
80
42
  this._headers = headers || {}
81
- this._fallbackRoute = undefined
82
43
 
83
44
  this._requestOptions = {
84
45
  headers: {
@@ -86,7 +47,6 @@ class BaseFFEWriter {
86
47
  'Content-Type': 'application/json',
87
48
  },
88
49
  method: 'POST',
89
- retry: true,
90
50
  timeout: this._timeout,
91
51
  url: this._baseUrl,
92
52
  path: this._endpoint,
@@ -155,8 +115,15 @@ class BaseFFEWriter {
155
115
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
156
116
  log.debug(() => `${this.constructor.name} flushing payload: ${safeJSONStringify(payload)}`)
157
117
 
158
- const route = this.#createActiveRoute()
159
- this.#sendRequest(payload, events.length, route, this._fallbackRoute)
118
+ request(payload, this._requestOptions, (err, resp, code) => {
119
+ if (err) {
120
+ log.error('Failed to send events to %s%s', this._baseUrl.href, this._endpoint, err)
121
+ } else if (code >= 200 && code < 300) {
122
+ log.debug('Successfully sent %d events', events.length)
123
+ } else {
124
+ log.warn('Events request returned status %d', code)
125
+ }
126
+ })
160
127
  }
161
128
 
162
129
  /**
@@ -194,115 +161,6 @@ class BaseFFEWriter {
194
161
  _encode (payload) {
195
162
  return JSON.stringify(payload)
196
163
  }
197
-
198
- /**
199
- * Applies the active route and an optional direct fallback route.
200
- *
201
- * @param {WriterRoute} route - Active route
202
- * @param {WriterRoute} [fallbackRoute] - Direct fallback route
203
- * @returns {void}
204
- */
205
- _setRoutes (route, fallbackRoute) {
206
- this.#activateRoute(this.#createRoute(route))
207
- this._fallbackRoute = fallbackRoute ? this.#createRoute(fallbackRoute) : undefined
208
- }
209
-
210
- /**
211
- * Creates request state for a configured writer route.
212
- *
213
- * @param {WriterRoute} route - Configured route
214
- * @returns {ActiveWriterRoute} Active route state
215
- */
216
- #createRoute (route) {
217
- return {
218
- url: route.url,
219
- endpoint: route.endpoint,
220
- requestOptions: {
221
- ...(route.agent && { agent: route.agent }),
222
- headers: {
223
- ...route.headers,
224
- 'Content-Type': 'application/json',
225
- },
226
- method: 'POST',
227
- retry: true,
228
- timeout: this._timeout,
229
- url: route.url,
230
- path: route.endpoint,
231
- },
232
- }
233
- }
234
-
235
- /**
236
- * Captures the current route for one event batch.
237
- *
238
- * @returns {ActiveWriterRoute} Active route state
239
- */
240
- #createActiveRoute () {
241
- return {
242
- url: this._baseUrl,
243
- endpoint: this._endpoint,
244
- requestOptions: this._requestOptions,
245
- }
246
- }
247
-
248
- /**
249
- * Makes a route active for future event batches.
250
- *
251
- * @param {ActiveWriterRoute} route - Route state
252
- * @returns {void}
253
- */
254
- #activateRoute (route) {
255
- this._baseUrl = route.url
256
- this._endpoint = route.endpoint
257
- this._requestOptions = route.requestOptions
258
- }
259
-
260
- /**
261
- * Sends an encoded batch and retries it through direct intake after a local route failure.
262
- *
263
- * @param {string} payload - Encoded event batch
264
- * @param {number} eventCount - Event count
265
- * @param {ActiveWriterRoute} route - Selected route
266
- * @param {ActiveWriterRoute} [fallbackRoute] - Direct fallback route
267
- * @returns {void}
268
- */
269
- #sendRequest (payload, eventCount, route, fallbackRoute) {
270
- request(payload, route.requestOptions, (error, response, statusCode) => {
271
- if (fallbackRoute && isDefinitiveRejection(error, statusCode)) {
272
- log.debug(
273
- '%s switching from %s%s to direct intake after definitive rejection',
274
- this.constructor.name,
275
- route.url.href,
276
- route.endpoint
277
- )
278
- this.#activateRoute(fallbackRoute)
279
- this._fallbackRoute = undefined
280
- this.#sendRequest(payload, eventCount, fallbackRoute)
281
- return
282
- }
283
-
284
- if (fallbackRoute && isAmbiguousNetworkFailure(error)) {
285
- log.debug(
286
- '%s retrying through direct intake and switching future batches from %s%s after ambiguous failure',
287
- this.constructor.name,
288
- route.url.href,
289
- route.endpoint
290
- )
291
- this.#activateRoute(fallbackRoute)
292
- this._fallbackRoute = undefined
293
- this.#sendRequest(payload, eventCount, fallbackRoute)
294
- return
295
- }
296
-
297
- if (error) {
298
- log.error('Failed to send events to %s%s: %s', route.url.href, route.endpoint, error.message)
299
- } else if (statusCode >= 200 && statusCode < 300) {
300
- log.debug('Successfully sent %d events', eventCount)
301
- } else {
302
- log.warn('Events request returned status %d', statusCode)
303
- }
304
- })
305
- }
306
164
  }
307
165
 
308
166
  module.exports = BaseFFEWriter
@@ -2,15 +2,12 @@
2
2
 
3
3
  const {
4
4
  EXPOSURES_ENDPOINT,
5
+ EVP_PROXY_AGENT_BASE_PATH,
6
+ EVP_SUBDOMAIN_HEADER_NAME,
7
+ EVP_SUBDOMAIN_VALUE,
5
8
  EVP_PAYLOAD_SIZE_LIMIT,
6
9
  EVP_EVENT_SIZE_LIMIT,
7
10
  } = require('../constants/constants')
8
- const {
9
- EVP_EVENT_PLATFORM_SUBDOMAIN,
10
- EVP_PROXY_PATH_V2,
11
- EVP_SUBDOMAIN_HEADER_NAME,
12
- } = require('../../evp_proxy/constants')
13
- const { joinEVPProxyPath } = require('../../evp_proxy/path')
14
11
  const log = require('../../log')
15
12
  const BaseFFEWriter = require('./base')
16
13
 
@@ -19,15 +16,6 @@ const BaseFFEWriter = require('./base')
19
16
  // drop emits a warning and `droppedEventCount` accumulates the cumulative loss.
20
17
  const PENDING_MAX_EVENTS = 1000
21
18
 
22
- /**
23
- * @typedef {object} ExposureRoute
24
- * @property {URL} url - Route base URL
25
- * @property {string} basePath - EVP base path
26
- * @property {object} [headers] - Route-specific headers
27
- * @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
28
- * @property {ExposureRoute} [fallback] - Optional direct fallback route
29
- */
30
-
31
19
  /**
32
20
  * @typedef {object} ExposureEvent
33
21
  * @property {number} timestamp - Unix timestamp in milliseconds
@@ -57,10 +45,10 @@ const PENDING_MAX_EVENTS = 1000
57
45
  */
58
46
 
59
47
  /**
60
- * Sends exposure events through the selected local EVP proxy or direct intake route.
48
+ * ExposuresWriter is responsible for sending exposure events to the Datadog Agent.
61
49
  */
62
50
  class ExposuresWriter extends BaseFFEWriter {
63
- // Disabled until route selection resolves.
51
+ // Disabled until the agent strategy probe resolves.
64
52
  #enabled = false
65
53
 
66
54
  /** @type {ExposureEvent[]} */
@@ -73,27 +61,22 @@ class ExposuresWriter extends BaseFFEWriter {
73
61
 
74
62
  /**
75
63
  * @param {import('../../config/config-base')} config - Tracer configuration object
76
- * @param {ExposureRoute} [route] - Caller-supplied route
77
64
  */
78
- constructor (config, route) {
79
- route ??= { url: config.url, basePath: EVP_PROXY_PATH_V2 }
80
- const headers = route.headers ?? {
81
- [EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
82
- }
65
+ constructor (config) {
66
+ const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/$/, '')
67
+ const endpoint = EXPOSURES_ENDPOINT.replace(/^\/+/, '')
68
+ const fullEndpoint = `${basePath}/${endpoint}`
83
69
 
84
70
  super({
85
71
  config,
86
- agentUrl: route.url,
87
- endpoint: joinEVPProxyPath(route.basePath, EXPOSURES_ENDPOINT),
72
+ endpoint: fullEndpoint,
88
73
  payloadSizeLimit: EVP_PAYLOAD_SIZE_LIMIT,
89
74
  eventSizeLimit: EVP_EVENT_SIZE_LIMIT,
90
- headers,
75
+ headers: {
76
+ [EVP_SUBDOMAIN_HEADER_NAME]: EVP_SUBDOMAIN_VALUE,
77
+ },
91
78
  })
92
79
 
93
- if (route.agent || route.fallback) {
94
- this.#setRoute({ ...route, headers })
95
- }
96
-
97
80
  /** @type {ExposureContext} */
98
81
  const context = {
99
82
  service: config.service,
@@ -112,14 +95,8 @@ class ExposuresWriter extends BaseFFEWriter {
112
95
 
113
96
  /**
114
97
  * @param {boolean} enabled - Whether to enable the writer
115
- * @param {ExposureRoute} [route] - Selected EVP route
116
- * @returns {void}
117
98
  */
118
- setEnabled (enabled, route) {
119
- if (route) {
120
- this.#setRoute(route)
121
- }
122
-
99
+ setEnabled (enabled) {
123
100
  this.#enabled = enabled
124
101
 
125
102
  if (enabled && this.#pendingEvents.length > 0) {
@@ -129,31 +106,6 @@ class ExposuresWriter extends BaseFFEWriter {
129
106
  }
130
107
  }
131
108
 
132
- /**
133
- * Applies caller-supplied route data without performing discovery.
134
- *
135
- * @param {ExposureRoute} route - Selected EVP route
136
- * @returns {void}
137
- */
138
- #setRoute (route) {
139
- const fallbackRoute = route.fallback && {
140
- url: route.fallback.url,
141
- endpoint: joinEVPProxyPath(route.fallback.basePath, EXPOSURES_ENDPOINT),
142
- headers: route.fallback.headers ?? {},
143
- agent: route.fallback.agent,
144
- }
145
- const headers = route.headers ?? {
146
- [EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
147
- }
148
-
149
- this._setRoutes({
150
- url: route.url,
151
- endpoint: joinEVPProxyPath(route.basePath, EXPOSURES_ENDPOINT),
152
- headers,
153
- agent: route.agent,
154
- }, fallbackRoute)
155
- }
156
-
157
109
  /**
158
110
  * Appends exposure event(s) to the buffer
159
111
  * @param {ExposureEvent|ExposureEvent[]} events - Exposure event(s) to append
@@ -1,51 +1,30 @@
1
1
  'use strict'
2
2
 
3
- const {
4
- EVP_EVENT_PLATFORM_SUBDOMAIN,
5
- EVP_PROXY_PATH_V2,
6
- EVP_PROXY_PATH_V4,
7
- EVP_SUBDOMAIN_HEADER_NAME,
8
- } = require('../../evp_proxy/constants')
9
- const { createDirectEVPRoute } = require('../../evp_proxy/direct')
10
- const { discoverEVPProxy } = require('../../evp_proxy/discovery')
11
3
  const logger = require('../../log')
12
-
13
- let missingRouteWarningLogged = false
4
+ const { EVP_PROXY_AGENT_BASE_PATH } = require('../constants/constants')
5
+ const { fetchAgentInfo } = require('../../agent/info')
14
6
 
15
7
  /**
16
- * Logs the unavailable exposure-delivery warning once.
17
- *
18
- * @returns {void}
19
- */
20
- function warnExposureDeliveryUnavailable () {
21
- if (missingRouteWarningLogged) return
22
- missingRouteWarningLogged = true
23
- logger.warn(
24
- 'Feature Flags exposure delivery is disabled because no compatible local EVP route or direct intake ' +
25
- 'credentials are available.'
26
- )
27
- }
28
-
29
- /**
30
- * Preserves Agent exposure delivery for the Remote Configuration source.
31
- *
8
+ * Determines if the agent supports EVP proxy and sets the writer enabled state accordingly
32
9
  * @param {import('../../config')} config - Tracer configuration object
33
10
  * @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
34
- * @returns {void}
35
11
  */
36
12
  function setAgentStrategy (config, setWriterEnabledValue) {
37
- discoverEVPProxy(config.url, {
38
- supportedPaths: [EVP_PROXY_PATH_V2],
39
- }, (error, route) => {
40
- if (error) {
41
- logger.debug('FFE Writer disabled - error getting agent info: %s', error.message)
13
+ fetchAgentInfo(config.url, (err, agentInfo) => {
14
+ if (err) {
15
+ logger.debug('FFE Writer disabled - error getting agent info:', err.message)
42
16
  setWriterEnabledValue(false)
43
17
  return
44
18
  }
45
19
 
46
- if (route) {
20
+ const endpoints = agentInfo.endpoints
21
+ const normalizedPath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/$/, '')
22
+ const hasEndpoint = Array.isArray(endpoints) &&
23
+ endpoints.includes(normalizedPath) || endpoints.includes(normalizedPath + '/')
24
+
25
+ if (hasEndpoint) {
47
26
  logger.debug('FFE Writer enabled - agent has EVP proxy support')
48
- setWriterEnabledValue(true, route)
27
+ setWriterEnabledValue(true)
49
28
  } else {
50
29
  logger.debug('FFE Writer disabled - agent does not have EVP proxy support')
51
30
  setWriterEnabledValue(false)
@@ -53,67 +32,6 @@ function setAgentStrategy (config, setWriterEnabledValue) {
53
32
  })
54
33
  }
55
34
 
56
- /**
57
- * Selects a local serverless receiver or authenticated direct intake.
58
- *
59
- * Local discovery is optional for delivery. A missing listener, discovery
60
- * error, or incompatible receiver selects direct intake when credentials exist.
61
- *
62
- * @param {import('../../config')} config - Tracer configuration object
63
- * @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
64
- * @returns {void}
65
- */
66
- function setAgentlessStrategy (config, setWriterEnabledValue) {
67
- const directRoute = createDirectEVPRoute(config, EVP_EVENT_PLATFORM_SUBDOMAIN)
68
-
69
- discoverEVPProxy(config.url, {
70
- supportedPaths: [EVP_PROXY_PATH_V4, EVP_PROXY_PATH_V2],
71
- }, (error, localRoute) => {
72
- if (localRoute) {
73
- const route = {
74
- ...localRoute,
75
- headers: {
76
- [EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
77
- },
78
- ...(directRoute && { fallback: directRoute }),
79
- }
80
- logger.debug('FFE Writer enabled with local EVP route %s', route.basePath)
81
- setWriterEnabledValue(true, route)
82
- return
83
- }
84
-
85
- if (directRoute) {
86
- if (error) {
87
- logger.debug('FFE Writer using direct EVP intake after local discovery failed: %s', error.message)
88
- } else {
89
- logger.debug('FFE Writer using direct EVP intake because no compatible local route was advertised')
90
- }
91
- setWriterEnabledValue(true, directRoute)
92
- return
93
- }
94
-
95
- if (error) {
96
- logger.debug('FFE Writer disabled - error getting local receiver info: %s', error.message)
97
- }
98
- warnExposureDeliveryUnavailable()
99
- setWriterEnabledValue(false)
100
- })
35
+ module.exports = {
36
+ setAgentStrategy,
101
37
  }
102
-
103
- /**
104
- * Applies the exposure-delivery strategy for the configured Feature Flags source.
105
- *
106
- * @param {import('../../config')} config - Tracer configuration object
107
- * @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
108
- * @returns {void}
109
- */
110
- function setExposureDeliveryStrategy (config, setWriterEnabledValue) {
111
- if (config.featureFlags?.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE === 'agentless') {
112
- setAgentlessStrategy(config, setWriterEnabledValue)
113
- return
114
- }
115
-
116
- setAgentStrategy(config, setWriterEnabledValue)
117
- }
118
-
119
- module.exports = { setExposureDeliveryStrategy }
@@ -543,8 +543,7 @@ class TextMapPropagator {
543
543
  this._extractOrigin(carrier, spanContext)
544
544
  this._extractLegacyBaggageItems(carrier, spanContext)
545
545
  this._extractSamplingPriority(carrier, spanContext)
546
- const traceTags = this._extractTags(carrier)
547
- if (traceTags) spanContext._trace.tags = traceTags
546
+ this._extractTags(carrier, spanContext)
548
547
 
549
548
  if (this._config.DD_TRACE_PROPAGATION_EXTRACT_FIRST) return spanContext
550
549
 
@@ -866,45 +865,37 @@ class TextMapPropagator {
866
865
  }
867
866
  }
868
867
 
869
- /**
870
- * @param {Record<string, unknown>} carrier
871
- * @returns {Record<string, string> | undefined}
872
- */
873
- _extractTags (carrier) {
868
+ _extractTags (carrier, spanContext) {
874
869
  const header = readDatadogTags(carrier)
875
870
  if (!header) return
876
871
 
872
+ const trace = spanContext._trace
873
+
877
874
  if (this._config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH === 0) {
878
875
  log.debug('Trace tag propagation is disabled, skipping extraction.')
879
876
  } else if (header.length > this._config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH) {
880
877
  log.error('Trace tags from carrier are too large, skipping extraction.')
881
878
  } else {
879
+ const pairs = header.split(',')
882
880
  const tags = {}
883
- let start = 0
884
- let separator = header.indexOf('=')
885
881
 
886
- while (start <= header.length) {
887
- const comma = header.indexOf(',', start)
888
- const end = comma === -1 ? header.length : comma
889
- const hasSeparator = separator !== -1 && separator < end
890
- const key = header.slice(start, hasSeparator ? separator : end)
891
- const value = hasSeparator ? header.slice(separator + 1, end) : ''
882
+ for (const pair of pairs) {
883
+ const [key, ...rest] = pair.split('=')
884
+ const value = rest.join('=')
892
885
 
893
886
  if (!this._validateTagKey(key) || !this._validateTagValue(value)) {
894
887
  log.error('Trace tags from carrier are invalid, skipping extraction.')
895
888
  return
896
889
  }
897
- if (key === '_dd.p.tid' && !hex16.test(value)) {
890
+ // Check if value is a valid 16 character lower-case hexadecimal encoded number as per spec
891
+ if (key === '_dd.p.tid' && !(hex16.test(value))) {
898
892
  log.error('Invalid _dd.p.tid tag %s, skipping', value)
899
- } else {
900
- tags[key] = value
893
+ continue
901
894
  }
902
-
903
- start = end + 1
904
- if (hasSeparator) separator = header.indexOf('=', start)
895
+ tags[key] = value
905
896
  }
906
897
 
907
- return tags
898
+ Object.assign(trace.tags, tags)
908
899
  }
909
900
  }
910
901