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.
- package/LICENSE-3rdparty.csv +1 -0
- package/ci/vitest-no-worker-init-setup.mjs +22 -12
- package/index.d.ts +55 -11
- package/package.json +6 -6
- package/packages/datadog-instrumentations/src/anthropic.js +111 -9
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +5 -1
- package/packages/datadog-instrumentations/src/cucumber.js +50 -9
- package/packages/datadog-instrumentations/src/cypress-config.js +27 -15
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +6 -0
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -6
- package/packages/datadog-instrumentations/src/helpers/register.js +20 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +41 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +15 -13
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +51 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +13 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +28 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +6 -0
- package/packages/datadog-instrumentations/src/jest/session-error.js +102 -0
- package/packages/datadog-instrumentations/src/jest.js +30 -13
- package/packages/datadog-instrumentations/src/mocha/main.js +7 -5
- package/packages/datadog-instrumentations/src/mocha/utils.js +8 -8
- package/packages/datadog-instrumentations/src/playwright-reporter.js +3 -4
- package/packages/datadog-instrumentations/src/playwright.js +73 -6
- package/packages/datadog-instrumentations/src/undici.js +7 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +95 -21
- package/packages/datadog-instrumentations/src/vitest-worker.js +34 -3
- package/packages/datadog-instrumentations/src/webdriverio.js +28 -12
- package/packages/datadog-instrumentations/src/ws.js +41 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +83 -29
- package/packages/datadog-plugin-cucumber/src/index.js +22 -8
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -14
- package/packages/datadog-plugin-cypress/src/index.js +3 -1
- package/packages/datadog-plugin-fetch/src/index.js +3 -0
- package/packages/datadog-plugin-jest/src/index.js +3 -4
- package/packages/datadog-plugin-mocha/src/index.js +3 -4
- package/packages/datadog-plugin-oracledb/src/connection-parser.js +3 -1
- package/packages/datadog-plugin-playwright/src/index.js +11 -6
- package/packages/datadog-plugin-undici/src/index.js +5 -0
- package/packages/datadog-plugin-vitest/src/index.js +3 -4
- package/packages/datadog-plugin-ws/src/producer.js +1 -1
- package/packages/datadog-shimmer/src/shimmer.js +14 -10
- package/packages/datadog-turbopack/index.js +4 -1
- package/packages/datadog-turbopack/src/loader.js +6 -2
- package/packages/dd-trace/src/agent/info.js +5 -4
- package/packages/dd-trace/src/aiguard/client.js +38 -19
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +29 -4
- package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
- package/packages/dd-trace/src/aiguard/integrations/openai.js +20 -56
- package/packages/dd-trace/src/aiguard/integrations/stream.js +60 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +64 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +19 -19
- package/packages/dd-trace/src/config/index.js +7 -6
- package/packages/dd-trace/src/config/supported-configurations.json +16 -5
- package/packages/dd-trace/src/constants.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +5 -3
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/debugger/index.js +1 -1
- package/packages/dd-trace/src/evp_proxy/constants.js +2 -0
- package/packages/dd-trace/src/evp_proxy/direct.js +17 -13
- package/packages/dd-trace/src/evp_proxy/discovery.js +14 -10
- package/packages/dd-trace/src/evp_proxy/path.js +21 -7
- package/packages/dd-trace/src/exporters/common/url.js +33 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +31 -8
- package/packages/dd-trace/src/llmobs/gen-ai-tags.js +127 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +18 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +33 -8
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +77 -41
- package/packages/dd-trace/src/llmobs/plugins/base.js +154 -15
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +162 -10
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +65 -16
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +14 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +34 -35
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +10 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +25 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +8 -7
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +13 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +5 -0
- package/packages/dd-trace/src/llmobs/plugins/vertexai.js +7 -0
- package/packages/dd-trace/src/llmobs/prompts/manager.js +10 -2
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +79 -12
- package/packages/dd-trace/src/llmobs/span_processor.js +10 -65
- package/packages/dd-trace/src/llmobs/tagger.js +11 -40
- package/packages/dd-trace/src/openfeature/configuration_source.js +4 -1
- package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -3
- package/packages/dd-trace/src/openfeature/index.js +6 -2
- package/packages/dd-trace/src/openfeature/writers/base.js +90 -17
- package/packages/dd-trace/src/openfeature/writers/exposures.js +4 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +104 -33
- package/packages/dd-trace/src/opentelemetry/trace/index.js +5 -0
- package/packages/dd-trace/src/opentracing/tracer.js +3 -3
- package/packages/dd-trace/src/plugin_manager.js +27 -8
- package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +13 -4
- package/packages/dd-trace/src/profiler.js +51 -5
- package/packages/dd-trace/src/profiling/profiler.js +24 -20
- package/packages/dd-trace/src/proxy.js +2 -2
- package/packages/dd-trace/src/ritm.js +12 -9
- package/packages/dd-trace/src/span_processor.js +6 -1
- package/packages/dd-trace/src/telemetry/send-data.js +2 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/packages/dd-trace/src/tracer.js +5 -4
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/packages/datadog-instrumentations/src/azure-cosmos.js +0 -7
- package/packages/datadog-instrumentations/src/bullmq.js +0 -11
- package/packages/datadog-instrumentations/src/langchain.js +0 -7
- package/packages/datadog-instrumentations/src/langgraph.js +0 -7
- 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
|
|
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 ===
|
|
53
|
+
statusCode === 404 || statusCode === 405
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
/**
|
|
48
|
-
* Tests whether a local route
|
|
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
|
|
53
|
-
return
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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 &&
|
|
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 &&
|
|
321
|
+
if (fallbackRoute && isTransportFailure(error, statusCode)) {
|
|
281
322
|
log.debug(
|
|
282
|
-
'%s
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
160
|
+
warnExposureDeliveryUnavailable()
|
|
161
|
+
markUnavailable()
|
|
162
|
+
})
|
|
163
|
+
}
|
|
91
164
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
warnExposureDeliveryUnavailable()
|
|
96
|
-
setWriterEnabledValue(false)
|
|
97
|
-
})
|
|
165
|
+
discover()
|
|
166
|
+
return stop
|
|
98
167
|
}
|
|
99
168
|
|
|
100
169
|
/**
|
|
101
|
-
* Applies
|
|
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
|
|
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 = {
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
88
|
+
try {
|
|
89
|
+
const Plugin = plugins[name]
|
|
85
90
|
|
|
86
|
-
|
|
91
|
+
if (!Plugin || typeof Plugin !== 'function') return
|
|
87
92
|
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 {
|
|
889
|
-
* @param {string} emptyReason
|
|
888
|
+
* @param {'zero_tests'|'all_tests_skipped'|'zero_test_shard'|'test_discovery'} emptyReason
|
|
890
889
|
*/
|
|
891
|
-
function setExpectedEmptyTestSessionTags (testSessionSpan, testModuleSpan,
|
|
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
|
}
|