dd-trace 6.17.0 → 6.19.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 (110) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/ci/vitest-no-worker-init-setup.mjs +22 -12
  3. package/index.d.ts +55 -11
  4. package/package.json +6 -6
  5. package/packages/datadog-instrumentations/src/anthropic.js +111 -9
  6. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +5 -1
  7. package/packages/datadog-instrumentations/src/cucumber.js +50 -9
  8. package/packages/datadog-instrumentations/src/cypress-config.js +27 -15
  9. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +6 -0
  10. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +2 -1
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -6
  12. package/packages/datadog-instrumentations/src/helpers/register.js +20 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +41 -4
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +15 -13
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +51 -0
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +13 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +28 -3
  18. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +6 -0
  19. package/packages/datadog-instrumentations/src/jest/session-error.js +102 -0
  20. package/packages/datadog-instrumentations/src/jest.js +30 -13
  21. package/packages/datadog-instrumentations/src/mocha/main.js +7 -5
  22. package/packages/datadog-instrumentations/src/mocha/utils.js +8 -8
  23. package/packages/datadog-instrumentations/src/playwright-reporter.js +3 -4
  24. package/packages/datadog-instrumentations/src/playwright.js +73 -6
  25. package/packages/datadog-instrumentations/src/undici.js +7 -3
  26. package/packages/datadog-instrumentations/src/vitest-main.js +95 -21
  27. package/packages/datadog-instrumentations/src/vitest-worker.js +34 -3
  28. package/packages/datadog-instrumentations/src/webdriverio.js +28 -12
  29. package/packages/datadog-instrumentations/src/ws.js +41 -1
  30. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +83 -29
  31. package/packages/datadog-plugin-cucumber/src/index.js +22 -8
  32. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -14
  33. package/packages/datadog-plugin-cypress/src/index.js +3 -1
  34. package/packages/datadog-plugin-fetch/src/index.js +3 -0
  35. package/packages/datadog-plugin-jest/src/index.js +3 -4
  36. package/packages/datadog-plugin-mocha/src/index.js +3 -4
  37. package/packages/datadog-plugin-oracledb/src/connection-parser.js +3 -1
  38. package/packages/datadog-plugin-playwright/src/index.js +11 -6
  39. package/packages/datadog-plugin-undici/src/index.js +5 -0
  40. package/packages/datadog-plugin-vitest/src/index.js +3 -4
  41. package/packages/datadog-plugin-ws/src/producer.js +1 -1
  42. package/packages/datadog-shimmer/src/shimmer.js +14 -10
  43. package/packages/datadog-turbopack/index.js +4 -1
  44. package/packages/datadog-turbopack/src/loader.js +6 -2
  45. package/packages/dd-trace/src/agent/info.js +5 -4
  46. package/packages/dd-trace/src/aiguard/client.js +38 -19
  47. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +29 -4
  48. package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
  49. package/packages/dd-trace/src/aiguard/integrations/openai.js +20 -56
  50. package/packages/dd-trace/src/aiguard/integrations/stream.js +60 -0
  51. package/packages/dd-trace/src/aiguard/messages/anthropic.js +64 -0
  52. package/packages/dd-trace/src/config/generated-config-types.d.ts +19 -19
  53. package/packages/dd-trace/src/config/index.js +7 -6
  54. package/packages/dd-trace/src/config/supported-configurations.json +16 -5
  55. package/packages/dd-trace/src/constants.js +2 -0
  56. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
  57. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
  58. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +5 -3
  59. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  60. package/packages/dd-trace/src/debugger/index.js +1 -1
  61. package/packages/dd-trace/src/evp_proxy/constants.js +2 -0
  62. package/packages/dd-trace/src/evp_proxy/direct.js +17 -13
  63. package/packages/dd-trace/src/evp_proxy/discovery.js +14 -10
  64. package/packages/dd-trace/src/evp_proxy/path.js +21 -7
  65. package/packages/dd-trace/src/exporters/common/url.js +33 -2
  66. package/packages/dd-trace/src/llmobs/constants/tags.js +31 -8
  67. package/packages/dd-trace/src/llmobs/gen-ai-tags.js +127 -0
  68. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +18 -0
  69. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +33 -8
  70. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +77 -41
  71. package/packages/dd-trace/src/llmobs/plugins/base.js +154 -15
  72. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +162 -10
  73. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +65 -16
  74. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +14 -0
  75. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +34 -35
  76. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +10 -0
  77. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +25 -2
  78. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +8 -7
  79. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +13 -0
  80. package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +5 -0
  81. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +7 -0
  82. package/packages/dd-trace/src/llmobs/prompts/manager.js +10 -2
  83. package/packages/dd-trace/src/llmobs/prompts/prompt.js +79 -12
  84. package/packages/dd-trace/src/llmobs/span_processor.js +10 -65
  85. package/packages/dd-trace/src/llmobs/tagger.js +11 -40
  86. package/packages/dd-trace/src/openfeature/configuration_source.js +4 -1
  87. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -3
  88. package/packages/dd-trace/src/openfeature/index.js +6 -2
  89. package/packages/dd-trace/src/openfeature/writers/base.js +90 -17
  90. package/packages/dd-trace/src/openfeature/writers/exposures.js +4 -0
  91. package/packages/dd-trace/src/openfeature/writers/util.js +104 -33
  92. package/packages/dd-trace/src/opentelemetry/trace/index.js +5 -0
  93. package/packages/dd-trace/src/opentracing/tracer.js +3 -3
  94. package/packages/dd-trace/src/plugin_manager.js +27 -8
  95. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
  96. package/packages/dd-trace/src/plugins/util/test.js +13 -4
  97. package/packages/dd-trace/src/profiler.js +51 -5
  98. package/packages/dd-trace/src/profiling/profiler.js +24 -20
  99. package/packages/dd-trace/src/proxy.js +2 -2
  100. package/packages/dd-trace/src/ritm.js +12 -9
  101. package/packages/dd-trace/src/span_processor.js +6 -1
  102. package/packages/dd-trace/src/telemetry/send-data.js +2 -1
  103. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  104. package/packages/dd-trace/src/tracer.js +5 -4
  105. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  106. package/packages/datadog-instrumentations/src/azure-cosmos.js +0 -7
  107. package/packages/datadog-instrumentations/src/bullmq.js +0 -11
  108. package/packages/datadog-instrumentations/src/langchain.js +0 -7
  109. package/packages/datadog-instrumentations/src/langgraph.js +0 -7
  110. package/packages/datadog-instrumentations/src/mercurius.js +0 -11
@@ -1,9 +1,14 @@
1
1
  'use strict'
2
2
 
3
3
  const request = require('../../exporters/common/request')
4
- const { safeJSONStringify } = require('../../exporters/common/util')
5
4
 
6
5
  const log = require('../../log')
6
+ const tracerVersion = require('../../../../../package.json').version
7
+
8
+ const EVP_ORIGIN_HEADERS = {
9
+ 'DD-EVP-ORIGIN': 'dd-trace-js',
10
+ 'DD-EVP-ORIGIN-VERSION': tracerVersion,
11
+ }
7
12
 
8
13
  /**
9
14
  * @typedef {object} BaseFFEWriterOptions
@@ -23,6 +28,8 @@ const log = require('../../log')
23
28
  * @property {string} endpoint - Route endpoint
24
29
  * @property {object} headers - Route-specific headers
25
30
  * @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
31
+ * @property {Function} [onFallback] - Called after direct fallback becomes active
32
+ * @property {Function} [onUnavailable] - Called after the local route becomes unavailable
26
33
  */
27
34
 
28
35
  /**
@@ -30,6 +37,8 @@ const log = require('../../log')
30
37
  * @property {URL} url - Route base URL
31
38
  * @property {string} endpoint - Route endpoint
32
39
  * @property {object} requestOptions - HTTP request options
40
+ * @property {Function} [onFallback] - Called after direct fallback becomes active
41
+ * @property {Function} [onUnavailable] - Called after the local route becomes unavailable
33
42
  */
34
43
 
35
44
  /**
@@ -38,19 +47,31 @@ const log = require('../../log')
38
47
  * @param {Error | null} error - Request error
39
48
  * @param {number | undefined} statusCode - HTTP response status
40
49
  */
41
- function isDefinitiveRejection (error, statusCode) {
50
+ function isSafeToReplay (error, statusCode) {
42
51
  return error?.code === 'EAI_AGAIN' || error?.code === 'ECONNREFUSED' ||
43
52
  error?.code === 'ENOENT' || error?.code === 'ENOTFOUND' ||
44
- statusCode === 403 || statusCode === 404 || statusCode === 405
53
+ statusCode === 404 || statusCode === 405
45
54
  }
46
55
 
47
56
  /**
48
- * Tests whether a local route can have accepted an event batch before failing.
57
+ * Tests whether a local route failed without an authoritative HTTP response.
49
58
  *
50
59
  * @param {Error | null} error - Request error
60
+ * @param {number | undefined} statusCode - HTTP response status
61
+ */
62
+ function isTransportFailure (error, statusCode) {
63
+ // SDK backpressure says nothing about the local receiver's health.
64
+ return error !== null && error !== undefined && statusCode === undefined &&
65
+ error.code !== 'ERR_DD_REQUEST_BUFFER_FULL'
66
+ }
67
+
68
+ /**
69
+ * Tests whether an HTTP response should move only future Agentless batches.
70
+ *
71
+ * @param {number | undefined} statusCode - HTTP response status
51
72
  */
52
- function isAmbiguousNetworkFailure (error) {
53
- return error?.code === 'ECONNRESET' || error?.code === 'EPIPE' || error?.code === 'ETIMEDOUT'
73
+ function shouldSwitchFutureRoute (statusCode) {
74
+ return statusCode === 403 || statusCode === 429 || statusCode >= 500 && statusCode < 600
54
75
  }
55
76
 
56
77
  /**
@@ -81,10 +102,11 @@ class BaseFFEWriter {
81
102
  this._requestOptions = {
82
103
  headers: {
83
104
  ...this._headers,
105
+ ...EVP_ORIGIN_HEADERS,
84
106
  'Content-Type': 'application/json',
85
107
  },
86
108
  method: 'POST',
87
- retry: true,
109
+ retry: false,
88
110
  timeout: this._timeout,
89
111
  url: this._baseUrl,
90
112
  path: this._endpoint,
@@ -150,11 +172,10 @@ class BaseFFEWriter {
150
172
 
151
173
  const payload = this._encode(this.makePayload(events))
152
174
 
175
+ // Keep byte counting behind the debug callback because payloads can be several megabytes.
153
176
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
154
- log.debug(() => `${this.constructor.name} flushing payload: ${safeJSONStringify(payload)}`)
155
-
156
- const route = this.#createActiveRoute()
157
- this.#sendRequest(payload, events.length, route, this._fallbackRoute)
177
+ log.debug(() => `${this.constructor.name} flushing ${events.length} events (${Buffer.byteLength(payload)} bytes)`)
178
+ this._sendPayload(payload, events.length)
158
179
  }
159
180
 
160
181
  /**
@@ -192,6 +213,18 @@ class BaseFFEWriter {
192
213
  return JSON.stringify(payload)
193
214
  }
194
215
 
216
+ /**
217
+ * Sends one encoded event batch through a snapshot of the selected routes.
218
+ *
219
+ * @protected
220
+ * @param {string} payload - Encoded event batch
221
+ * @param {number} eventCount - Event count
222
+ */
223
+ _sendPayload (payload, eventCount) {
224
+ const route = this.#createActiveRoute()
225
+ this.#sendRequest(payload, eventCount, route, this._fallbackRoute)
226
+ }
227
+
195
228
  /**
196
229
  * Applies the active route and an optional direct fallback route.
197
230
  *
@@ -213,10 +246,11 @@ class BaseFFEWriter {
213
246
  const requestOptions = {
214
247
  headers: {
215
248
  ...route.headers,
249
+ ...EVP_ORIGIN_HEADERS,
216
250
  'Content-Type': 'application/json',
217
251
  },
218
252
  method: 'POST',
219
- retry: true,
253
+ retry: false,
220
254
  timeout: this._timeout,
221
255
  url: route.url,
222
256
  path: route.endpoint,
@@ -227,6 +261,8 @@ class BaseFFEWriter {
227
261
  url: route.url,
228
262
  endpoint: route.endpoint,
229
263
  requestOptions,
264
+ onFallback: route.onFallback,
265
+ onUnavailable: route.onUnavailable,
230
266
  }
231
267
  }
232
268
 
@@ -240,6 +276,8 @@ class BaseFFEWriter {
240
276
  url: this._baseUrl,
241
277
  endpoint: this._endpoint,
242
278
  requestOptions: this._requestOptions,
279
+ onFallback: this._onFallback,
280
+ onUnavailable: this._onUnavailable,
243
281
  }
244
282
  }
245
283
 
@@ -252,10 +290,12 @@ class BaseFFEWriter {
252
290
  this._baseUrl = route.url
253
291
  this._endpoint = route.endpoint
254
292
  this._requestOptions = route.requestOptions
293
+ this._onFallback = route.onFallback
294
+ this._onUnavailable = route.onUnavailable
255
295
  }
256
296
 
257
297
  /**
258
- * Sends an encoded batch and retries it through direct intake after a local route failure.
298
+ * Sends an encoded batch once per route and applies safe local fallback semantics.
259
299
  *
260
300
  * @param {string} payload - Encoded event batch
261
301
  * @param {number} eventCount - Event count
@@ -264,7 +304,7 @@ class BaseFFEWriter {
264
304
  */
265
305
  #sendRequest (payload, eventCount, route, fallbackRoute) {
266
306
  request(payload, route.requestOptions, (error, response, statusCode) => {
267
- if (fallbackRoute && isDefinitiveRejection(error, statusCode)) {
307
+ if (fallbackRoute && isSafeToReplay(error, statusCode)) {
268
308
  log.debug(
269
309
  '%s switching from %s%s to direct intake after definitive rejection',
270
310
  this.constructor.name,
@@ -273,20 +313,53 @@ class BaseFFEWriter {
273
313
  )
274
314
  this.#activateRoute(fallbackRoute)
275
315
  this._fallbackRoute = undefined
316
+ route.onFallback?.()
276
317
  this.#sendRequest(payload, eventCount, fallbackRoute)
277
318
  return
278
319
  }
279
320
 
280
- if (fallbackRoute && isAmbiguousNetworkFailure(error)) {
321
+ if (fallbackRoute && isTransportFailure(error, statusCode)) {
281
322
  log.debug(
282
- '%s retrying through direct intake and switching future batches from %s%s after ambiguous failure',
323
+ '%s switching future batches from %s%s to direct intake after an ambiguous failure without replay',
283
324
  this.constructor.name,
284
325
  route.url.href,
285
326
  route.endpoint
286
327
  )
287
328
  this.#activateRoute(fallbackRoute)
288
329
  this._fallbackRoute = undefined
289
- this.#sendRequest(payload, eventCount, fallbackRoute)
330
+ route.onFallback?.()
331
+ log.error('Failed to send events to %s%s: %s', route.url.href, route.endpoint, error.message)
332
+ return
333
+ }
334
+
335
+ if (fallbackRoute && shouldSwitchFutureRoute(statusCode)) {
336
+ log.debug(
337
+ '%s switching future batches from %s%s to direct intake after status %d without replay',
338
+ this.constructor.name,
339
+ route.url.href,
340
+ route.endpoint,
341
+ statusCode
342
+ )
343
+ this.#activateRoute(fallbackRoute)
344
+ this._fallbackRoute = undefined
345
+ route.onFallback?.()
346
+ log.warn('Events request returned status %d', statusCode)
347
+ return
348
+ }
349
+
350
+ if (
351
+ !fallbackRoute &&
352
+ route.onUnavailable &&
353
+ (isSafeToReplay(error, statusCode) ||
354
+ isTransportFailure(error, statusCode) ||
355
+ shouldSwitchFutureRoute(statusCode))
356
+ ) {
357
+ route.onUnavailable()
358
+ if (error) {
359
+ log.error('Failed to send events to %s%s: %s', route.url.href, route.endpoint, error.message)
360
+ } else {
361
+ log.warn('Events request returned status %d', statusCode)
362
+ }
290
363
  return
291
364
  }
292
365
 
@@ -26,6 +26,8 @@ const PENDING_MAX_EVENTS = 1000
26
26
  * @property {object} [headers] - Route-specific headers
27
27
  * @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
28
28
  * @property {ExposureRoute} [fallback] - Optional direct fallback route
29
+ * @property {Function} [onFallback] - Called after direct fallback becomes active
30
+ * @property {Function} [onUnavailable] - Called after the local route becomes unavailable
29
31
  */
30
32
 
31
33
  /**
@@ -150,6 +152,8 @@ class ExposuresWriter extends BaseFFEWriter {
150
152
  endpoint: joinEVPProxyPath(route.basePath, EXPOSURES_ENDPOINT),
151
153
  headers,
152
154
  agent: route.agent,
155
+ onFallback: route.onFallback,
156
+ onUnavailable: route.onUnavailable,
153
157
  }, fallbackRoute)
154
158
  }
155
159
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  const {
4
4
  EVP_EVENT_PLATFORM_SUBDOMAIN,
5
+ EVP_ORIGIN_HEADER_NAME,
6
+ EVP_ORIGIN_VERSION_HEADER_NAME,
5
7
  EVP_PROXY_PATH_V2,
6
8
  EVP_PROXY_PATH_V4,
7
9
  EVP_SUBDOMAIN_HEADER_NAME,
@@ -10,6 +12,10 @@ const { createDirectEVPRoute } = require('../../evp_proxy/direct')
10
12
  const { discoverEVPProxy } = require('../../evp_proxy/discovery')
11
13
  const logger = require('../../log')
12
14
 
15
+ const ROUTE_DISCOVERY_COOLDOWN_MS = 60_000
16
+ const REQUIRED_LOCAL_HEADERS = [EVP_ORIGIN_HEADER_NAME, EVP_ORIGIN_VERSION_HEADER_NAME]
17
+ const STOP_NOOP = () => {}
18
+
13
19
  let missingRouteWarningLogged = false
14
20
 
15
21
  /**
@@ -26,7 +32,7 @@ function warnExposureDeliveryUnavailable () {
26
32
  }
27
33
 
28
34
  /**
29
- * Preserves Agent exposure delivery for the Remote Configuration source.
35
+ * Preserves the one-time EVP v2 capability check for the Remote Configuration source.
30
36
  *
31
37
  * @param {import('../../config')} config - Tracer configuration object
32
38
  * @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
@@ -49,6 +55,7 @@ function setAgentStrategy (config, setWriterEnabledValue) {
49
55
  setWriterEnabledValue(false)
50
56
  }
51
57
  })
58
+ return STOP_NOOP
52
59
  }
53
60
 
54
61
  /**
@@ -62,54 +69,118 @@ function setAgentStrategy (config, setWriterEnabledValue) {
62
69
  */
63
70
  function setAgentlessStrategy (config, setWriterEnabledValue) {
64
71
  const directRoute = createDirectEVPRoute(config, EVP_EVENT_PLATFORM_SUBDOMAIN)
72
+ let discovering = false
73
+ let recoveryTimer
74
+ let state = 'unknown'
75
+ let stopped = false
65
76
 
66
- discoverEVPProxy(config.url, {
67
- supportedPaths: [EVP_PROXY_PATH_V4, EVP_PROXY_PATH_V2],
68
- }, (error, localRoute) => {
69
- if (localRoute) {
70
- const route = {
71
- ...localRoute,
72
- headers: {
73
- [EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
74
- },
75
- }
76
- if (directRoute) route.fallback = directRoute
77
- logger.debug('FFE Writer enabled with local EVP route %s', route.basePath)
78
- setWriterEnabledValue(true, route)
79
- return
77
+ const stop = () => {
78
+ stopped = true
79
+ clearTimeout(recoveryTimer)
80
+ recoveryTimer = undefined
81
+ }
82
+
83
+ const scheduleRecovery = () => {
84
+ if (stopped || state !== 'unavailable' || recoveryTimer !== undefined) return
85
+
86
+ recoveryTimer = setTimeout(discover, ROUTE_DISCOVERY_COOLDOWN_MS)
87
+ recoveryTimer.unref?.()
88
+ }
89
+
90
+ const markUnavailable = () => {
91
+ if (stopped || state === 'direct') return
92
+ if (state !== 'unavailable') {
93
+ state = 'unavailable'
94
+ setWriterEnabledValue(false)
80
95
  }
96
+ scheduleRecovery()
97
+ }
81
98
 
99
+ const activateDirect = () => {
100
+ if (stopped || state === 'direct' || directRoute === undefined) return
101
+ state = 'direct'
102
+ clearTimeout(recoveryTimer)
103
+ recoveryTimer = undefined
104
+ setWriterEnabledValue(true, directRoute)
105
+ }
106
+
107
+ /**
108
+ * @param {{url: URL, basePath: string}} localRoute - Discovered local route
109
+ */
110
+ const activateLocal = localRoute => {
111
+ state = 'local'
112
+ const route = {
113
+ ...localRoute,
114
+ headers: {
115
+ [EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
116
+ },
117
+ }
82
118
  if (directRoute) {
119
+ route.fallback = directRoute
120
+ route.onFallback = activateDirect
121
+ } else {
122
+ route.onUnavailable = markUnavailable
123
+ }
124
+ logger.debug('FFE Writer enabled with local EVP route %s', route.basePath)
125
+ setWriterEnabledValue(true, route)
126
+ }
127
+
128
+ function discover () {
129
+ recoveryTimer = undefined
130
+ if (stopped || state === 'direct' || discovering) return
131
+ discovering = true
132
+
133
+ discoverEVPProxy(config.url, {
134
+ requiredHeaders: REQUIRED_LOCAL_HEADERS,
135
+ supportedPaths: [EVP_PROXY_PATH_V4, EVP_PROXY_PATH_V2],
136
+ // Agentless owns fallback and unavailable-state recovery; do not delay them with HTTP retries.
137
+ retry: false,
138
+ }, (error, localRoute) => {
139
+ discovering = false
140
+ if (stopped || state === 'direct') return
141
+
142
+ if (localRoute) {
143
+ activateLocal(localRoute)
144
+ return
145
+ }
146
+
147
+ if (directRoute) {
148
+ if (error) {
149
+ logger.debug('FFE Writer using direct EVP intake after local discovery failed: %s', error.message)
150
+ } else {
151
+ logger.debug('FFE Writer using direct EVP intake because no compatible local route was advertised')
152
+ }
153
+ activateDirect()
154
+ return
155
+ }
156
+
83
157
  if (error) {
84
- logger.debug('FFE Writer using direct EVP intake after local discovery failed: %s', error.message)
85
- } else {
86
- logger.debug('FFE Writer using direct EVP intake because no compatible local route was advertised')
158
+ logger.debug('FFE Writer disabled - error getting local receiver info: %s', error.message)
87
159
  }
88
- setWriterEnabledValue(true, directRoute)
89
- return
90
- }
160
+ warnExposureDeliveryUnavailable()
161
+ markUnavailable()
162
+ })
163
+ }
91
164
 
92
- if (error) {
93
- logger.debug('FFE Writer disabled - error getting local receiver info: %s', error.message)
94
- }
95
- warnExposureDeliveryUnavailable()
96
- setWriterEnabledValue(false)
97
- })
165
+ discover()
166
+ return stop
98
167
  }
99
168
 
100
169
  /**
101
- * Applies the exposure-delivery strategy for the configured Feature Flags source.
170
+ * Applies one event-delivery strategy that can be shared by all Feature Flags writers.
102
171
  *
103
172
  * @param {import('../../config')} config - Tracer configuration object
104
173
  * @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
105
174
  */
106
- function setExposureDeliveryStrategy (config, setWriterEnabledValue) {
175
+ function setEventDeliveryStrategy (config, setWriterEnabledValue) {
107
176
  if (config.featureFlags?.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE === 'agentless') {
108
- setAgentlessStrategy(config, setWriterEnabledValue)
109
- return
177
+ return setAgentlessStrategy(config, setWriterEnabledValue)
110
178
  }
111
179
 
112
- setAgentStrategy(config, setWriterEnabledValue)
180
+ return setAgentStrategy(config, setWriterEnabledValue)
113
181
  }
114
182
 
115
- module.exports = { setExposureDeliveryStrategy }
183
+ module.exports = {
184
+ setEventDeliveryStrategy,
185
+ setExposureDeliveryStrategy: setEventDeliveryStrategy,
186
+ }
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { VERSION } = require('../../../../../version')
4
+ const { SDK_OTLP_EXPORT_KEY, SDK_SEMANTICS_KEY } = require('../../constants')
4
5
  const { registerResourceAttributeRefresh } = require('../resource-attributes')
5
6
  const OtlpHttpTraceExporter = require('./otlp_http_trace_exporter')
6
7
 
@@ -46,6 +47,10 @@ function buildResourceAttributes (config) {
46
47
  const { service, version, env, ...filteredTags } = config.tags
47
48
  Object.assign(resourceAttributes, filteredTags)
48
49
 
50
+ // Tracer-owned adoption markers; set after the global tags so a user tag can't contradict them.
51
+ resourceAttributes[SDK_OTLP_EXPORT_KEY] = 'true'
52
+ resourceAttributes[SDK_SEMANTICS_KEY] = config.DD_TRACE_OTEL_SEMANTICS_ENABLED ? 'otel' : 'datadog'
53
+
49
54
  if (config.OTEL_TRACES_SPAN_METRICS_ENABLED) {
50
55
  resourceAttributes['_dd.stats_computed'] = 'true'
51
56
  }
@@ -40,11 +40,11 @@ class DatadogTracer {
40
40
  // spans must reach the Electron SDK's IPC bridge, not an OTLP endpoint,
41
41
  // even when OTEL_* vars are set for unrelated telemetry.
42
42
  if (config.OTEL_TRACES_EXPORTER === 'otlp' && !config.isCiVisibility &&
43
- config.experimental.exporter !== 'electron') {
43
+ config.tracing.DD_TRACE_EXPERIMENTAL_EXPORTER !== 'electron') {
44
44
  const { createOtlpTraceExporter } = require('../opentelemetry/trace')
45
45
  this._exporter = createOtlpTraceExporter(config)
46
46
  } else {
47
- const Exporter = getExporter(config.experimental.exporter)
47
+ const Exporter = getExporter(config.tracing.DD_TRACE_EXPERIMENTAL_EXPORTER)
48
48
  this._exporter = new Exporter(config, this._prioritySampler)
49
49
  }
50
50
 
@@ -55,7 +55,7 @@ class DatadogTracer {
55
55
  }
56
56
  this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config, otlpStatsExporter)
57
57
  this._url = this._exporter._url
58
- this._enableGetRumData = config.experimental.enableGetRumData
58
+ this._enableGetRumData = config.rum.DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED
59
59
  this._traceId128BitGenerationEnabled = config.traceId128BitGenerationEnabled
60
60
  this._propagators = {
61
61
  [formats.TEXT_MAP]: new TextMapPropagator(config),
@@ -36,7 +36,11 @@ const pluginClasses = {}
36
36
  // during instrumentation initialization (e.g. re-requires in bundler contexts)
37
37
  // are captured and populate pluginClasses correctly.
38
38
  loadChannel.subscribe(({ name }) => {
39
- maybeEnable(plugins[name])
39
+ try {
40
+ maybeEnable(plugins[name])
41
+ } catch (error) {
42
+ log.error('Error activating plugin %s', name, error)
43
+ }
40
44
  })
41
45
 
42
46
  // instrument everything that needs Plugin System V2 instrumentation
@@ -81,11 +85,15 @@ module.exports = class PluginManager {
81
85
  this._configsByName = {}
82
86
 
83
87
  this._loadedSubscriber = ({ name }) => {
84
- const Plugin = plugins[name]
88
+ try {
89
+ const Plugin = plugins[name]
85
90
 
86
- if (!Plugin || typeof Plugin !== 'function') return
91
+ if (!Plugin || typeof Plugin !== 'function') return
87
92
 
88
- this.loadPlugin(Plugin.id)
93
+ this.loadPlugin(Plugin.id)
94
+ } catch (error) {
95
+ log.error('Error activating plugin %s', name, error)
96
+ }
89
97
  }
90
98
 
91
99
  loadChannel.subscribe(this._loadedSubscriber)
@@ -112,10 +120,21 @@ module.exports = class PluginManager {
112
120
  }
113
121
 
114
122
  // extracts predetermined configuration from tracer and combines it with plugin-specific config
115
- this._pluginsByName[name].configure({
123
+ const config = {
116
124
  ...this.#getSharedConfig(name),
117
125
  ...pluginConfig,
118
- })
126
+ }
127
+ const plugin = this._pluginsByName[name]
128
+ try {
129
+ plugin.configure(config)
130
+ } catch (error) {
131
+ try {
132
+ plugin.configure({ enabled: false })
133
+ } catch (disableError) {
134
+ log.error('Error disabling plugin %s after failed activation', name, disableError)
135
+ }
136
+ throw error
137
+ }
119
138
  }
120
139
 
121
140
  // TODO: merge config instead of replacing
@@ -189,7 +208,7 @@ module.exports = class PluginManager {
189
208
  traceWebsocketMessagesEnabled,
190
209
  traceWebsocketMessagesInheritSampling,
191
210
  traceWebsocketMessagesSeparateTraces,
192
- experimental,
211
+ tracing,
193
212
  DD_TRACE_RESOURCE_RENAMING_ENABLED,
194
213
  } = /** @type {import('./config/config-base')} */ (this._tracerConfig)
195
214
 
@@ -212,7 +231,7 @@ module.exports = class PluginManager {
212
231
  traceWebsocketMessagesEnabled,
213
232
  traceWebsocketMessagesInheritSampling,
214
233
  traceWebsocketMessagesSeparateTraces,
215
- experimental,
234
+ tracing,
216
235
  resourceRenamingEnabled: DD_TRACE_RESOURCE_RENAMING_ENABLED,
217
236
  }
218
237
 
@@ -788,7 +788,7 @@ module.exports = class CiPlugin extends Plugin {
788
788
  return
789
789
  }
790
790
 
791
- const exporter = this.config.experimental?.exporter
791
+ const exporter = this.config.tracing.DD_TRACE_EXPERIMENTAL_EXPORTER
792
792
  const workerTestFramework = WORKER_EXPORTER_TO_TEST_FRAMEWORK[exporter]
793
793
  this.shouldSkipGitMetadataExtraction = workerTestFramework &&
794
794
  TEST_FRAMEWORKS_TO_SKIP_GIT_METADATA_EXTRACTION.has(workerTestFramework)
@@ -881,14 +881,19 @@ function setRumTestTags (testSpan, isRumActive, browserVersion) {
881
881
  }
882
882
 
883
883
  /**
884
- * Marks a successful test session that intentionally executed no tests.
884
+ * Marks a successful test session that did not execute any tests.
885
885
  *
886
886
  * @param {import('../../opentracing/span')} testSessionSpan
887
887
  * @param {import('../../opentracing/span')|undefined} testModuleSpan
888
- * @param {string} skipReason
889
- * @param {string} emptyReason
888
+ * @param {'zero_tests'|'all_tests_skipped'|'zero_test_shard'|'test_discovery'} emptyReason
890
889
  */
891
- function setExpectedEmptyTestSessionTags (testSessionSpan, testModuleSpan, skipReason, emptyReason) {
890
+ function setExpectedEmptyTestSessionTags (testSessionSpan, testModuleSpan, emptyReason) {
891
+ const skipReason = emptyReason === 'test_discovery'
892
+ ? 'Test discovery only (--list)'
893
+ : emptyReason === 'zero_test_shard'
894
+ ? 'No tests were assigned to this shard'
895
+ : emptyReason === 'all_tests_skipped' ? 'All tests were skipped' : 'No tests were detected'
896
+
892
897
  for (const span of [testSessionSpan, testModuleSpan]) {
893
898
  if (!span) continue
894
899
 
@@ -2020,6 +2025,10 @@ function getModifiedFilesFromDiff (diff) {
2020
2025
  if (lineMatch && currentFile) {
2021
2026
  const start = Number(lineMatch.groups.start)
2022
2027
  const count = lineMatch.groups.count ? Number(lineMatch.groups.count) : 1
2028
+ if (count === 0) {
2029
+ // Keep the surviving line before a deletion so line-based consumers can match its scope.
2030
+ result[currentFile].push(start)
2031
+ }
2023
2032
  for (let j = 0; j < count; j++) {
2024
2033
  result[currentFile].push(start + j)
2025
2034
  }