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.
- package/LICENSE-3rdparty.csv +0 -5
- package/index.d.ts +25 -240
- package/package.json +18 -19
- package/packages/datadog-instrumentations/src/cucumber.js +4 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -5
- package/packages/datadog-instrumentations/src/playwright-reporter.js +1 -149
- package/packages/datadog-instrumentations/src/playwright.js +33 -33
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +8 -6
- package/packages/datadog-plugin-playwright/src/index.js +0 -4
- package/packages/dd-trace/src/appsec/reporter.js +0 -2
- package/packages/dd-trace/src/appsec/telemetry/index.js +0 -13
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +0 -16
- package/packages/dd-trace/src/appsec/telemetry/waf.js +0 -24
- package/packages/dd-trace/src/carrier.js +48 -107
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +0 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +3 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -4
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +4 -6
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +3 -5
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +2 -4
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +2 -4
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +2 -4
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
- package/packages/dd-trace/src/debugger/constants.js +0 -1
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/index.js +10 -3
- package/packages/dd-trace/src/debugger/index.js +2 -4
- package/packages/dd-trace/src/exporters/common/request.js +2 -5
- package/packages/dd-trace/src/llmobs/experiments/client.js +2 -6
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +11 -249
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +10 -23
- package/packages/dd-trace/src/llmobs/experiments/index.js +6 -18
- package/packages/dd-trace/src/llmobs/experiments/noop.js +4 -64
- package/packages/dd-trace/src/llmobs/experiments/util.js +4 -60
- package/packages/dd-trace/src/openfeature/constants/constants.js +18 -0
- package/packages/dd-trace/src/openfeature/index.js +4 -7
- package/packages/dd-trace/src/openfeature/writers/base.js +11 -153
- package/packages/dd-trace/src/openfeature/writers/exposures.js +14 -62
- package/packages/dd-trace/src/openfeature/writers/util.js +15 -97
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +13 -22
- package/packages/dd-trace/src/telemetry/metrics.js +8 -62
- package/packages/dd-trace/src/telemetry/send-data.js +0 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/packages/dd-trace/src/debugger/inspect-segment.js +0 -154
- package/packages/dd-trace/src/evp_proxy/constants.js +0 -8
- package/packages/dd-trace/src/evp_proxy/direct.js +0 -64
- package/packages/dd-trace/src/evp_proxy/discovery.js +0 -133
- package/packages/dd-trace/src/evp_proxy/path.js +0 -22
- package/vendor/dist/https-proxy-agent/LICENSE +0 -22
- package/vendor/dist/https-proxy-agent/index.js +0 -4
- package/vendor/dist/proxy-from-env/LICENSE +0 -20
- 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
|
|
168
|
+
const tags = new Map()
|
|
189
169
|
if ((userTags) != null) {
|
|
190
170
|
for (const [key, value] of Object.entries(userTags)) {
|
|
191
|
-
|
|
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 !== '')
|
|
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 {
|
|
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
|
-
|
|
44
|
-
exposuresWriter = writer
|
|
43
|
+
exposuresWriter = new ExposuresWriter(config)
|
|
45
44
|
exposureSubmitCh.subscribe(_handleExposureSubmit)
|
|
46
45
|
flushCh.subscribe(_handleFlush)
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
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] -
|
|
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
|
-
*
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
*
|
|
48
|
+
* ExposuresWriter is responsible for sending exposure events to the Datadog Agent.
|
|
61
49
|
*/
|
|
62
50
|
class ExposuresWriter extends BaseFFEWriter {
|
|
63
|
-
// Disabled until
|
|
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
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
4
|
+
const { EVP_PROXY_AGENT_BASE_PATH } = require('../constants/constants')
|
|
5
|
+
const { fetchAgentInfo } = require('../../agent/info')
|
|
14
6
|
|
|
15
7
|
/**
|
|
16
|
-
*
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
887
|
-
const
|
|
888
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
898
|
+
Object.assign(trace.tags, tags)
|
|
908
899
|
}
|
|
909
900
|
}
|
|
910
901
|
|