dd-trace 6.10.0 → 6.12.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 +5 -0
- package/README.md +0 -17
- package/index.d.ts +128 -6
- package/initialize.mjs +28 -17
- package/openfeature.js +2 -2
- package/package.json +27 -23
- package/packages/datadog-esbuild/index.js +0 -34
- package/packages/datadog-instrumentations/src/anthropic.js +210 -15
- package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
- package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
- package/packages/datadog-instrumentations/src/bunyan.js +3 -16
- package/packages/datadog-instrumentations/src/cucumber.js +30 -15
- package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
- package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
- package/packages/datadog-instrumentations/src/cypress.js +2 -0
- package/packages/datadog-instrumentations/src/fastify.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
- package/packages/datadog-instrumentations/src/jest.js +25 -17
- package/packages/datadog-instrumentations/src/mariadb.js +278 -21
- package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
- package/packages/datadog-instrumentations/src/mocha/utils.js +135 -19
- package/packages/datadog-instrumentations/src/playwright-reporter.js +247 -0
- package/packages/datadog-instrumentations/src/playwright.js +182 -35
- package/packages/datadog-instrumentations/src/router.js +2 -0
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +6 -8
- package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
- package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
- package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
- package/packages/datadog-plugin-bunyan/src/index.js +5 -8
- package/packages/datadog-plugin-cucumber/src/index.js +11 -0
- package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
- package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
- package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
- package/packages/datadog-plugin-cypress/src/index.js +12 -6
- package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
- package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
- package/packages/datadog-plugin-electron/src/index.js +2 -0
- package/packages/datadog-plugin-fs/src/index.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +6 -0
- package/packages/datadog-plugin-mocha/src/index.js +56 -1
- package/packages/datadog-plugin-nats/src/index.js +1 -1
- package/packages/datadog-plugin-openai/src/tracing.js +41 -30
- package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
- package/packages/datadog-plugin-playwright/src/index.js +46 -13
- package/packages/datadog-plugin-vitest/src/index.js +6 -0
- package/packages/datadog-webpack/index.js +0 -11
- package/packages/dd-trace/index.js +0 -1
- package/packages/dd-trace/src/agent/info.js +6 -5
- package/packages/dd-trace/src/aiguard/client.js +100 -1
- package/packages/dd-trace/src/aiguard/errors.js +41 -0
- package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
- package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
- package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
- package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
- package/packages/dd-trace/src/aiguard/sdk.js +22 -278
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
- package/packages/dd-trace/src/appsec/reporter.js +2 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
- package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
- package/packages/dd-trace/src/carrier.js +107 -48
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
- 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 +29 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +27 -9
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +26 -8
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -4
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +10 -4
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/index.js +13 -2
- package/packages/dd-trace/src/config/major-overrides.js +7 -0
- package/packages/dd-trace/src/config/supported-configurations.json +14 -0
- package/packages/dd-trace/src/datastreams/index.js +2 -0
- package/packages/dd-trace/src/datastreams/manager.js +9 -2
- package/packages/dd-trace/src/datastreams/processor.js +15 -13
- package/packages/dd-trace/src/datastreams/size.js +5 -0
- package/packages/dd-trace/src/debugger/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -11
- package/packages/dd-trace/src/debugger/index.js +4 -2
- package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
- package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
- package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
- package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
- package/packages/dd-trace/src/evp_proxy/path.js +22 -0
- package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
- package/packages/dd-trace/src/exporters/common/request.js +5 -2
- package/packages/dd-trace/src/exporters/common/writer.js +11 -6
- package/packages/dd-trace/src/llmobs/experiments/client.js +167 -5
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +484 -92
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +302 -29
- package/packages/dd-trace/src/llmobs/experiments/index.js +75 -64
- package/packages/dd-trace/src/llmobs/experiments/noop.js +135 -7
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +101 -9
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
- package/packages/dd-trace/src/noop/proxy.js +3 -4
- package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
- package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
- package/packages/dd-trace/src/openfeature/index.js +7 -4
- package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
- package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
- package/packages/dd-trace/src/openfeature/writers/exposures.js +62 -14
- package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
- package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +22 -13
- package/packages/dd-trace/src/opentracing/tracer.js +5 -2
- package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
- package/packages/dd-trace/src/plugin_manager.js +3 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/jest.js +4 -3
- package/packages/dd-trace/src/plugins/util/test.js +9 -9
- package/packages/dd-trace/src/plugins/util/web.js +3 -0
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
- package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
- package/packages/dd-trace/src/proxy.js +38 -29
- package/packages/dd-trace/src/ritm.js +9 -2
- package/packages/dd-trace/src/serverless.js +37 -0
- package/packages/dd-trace/src/span_format.js +1 -0
- package/packages/dd-trace/src/span_stats.js +30 -3
- package/packages/dd-trace/src/storage-channels.js +86 -0
- package/packages/dd-trace/src/telemetry/metrics.js +62 -8
- package/packages/dd-trace/src/telemetry/send-data.js +1 -0
- package/packages/dd-trace/src/tracer.js +9 -2
- package/packages/dd-trace/src/tracer_metadata.js +14 -1
- package/packages/dd-trace/src/web-tags-cache.js +132 -0
- package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/https-proxy-agent/LICENSE +22 -0
- package/vendor/dist/https-proxy-agent/index.js +4 -0
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/proxy-from-env/LICENSE +20 -0
- package/vendor/dist/proxy-from-env/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
- package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
- package/packages/dd-trace/src/feature-registry.js +0 -29
- package/packages/dd-trace/src/openfeature/register.js +0 -35
- package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
|
@@ -11,14 +11,52 @@ 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] - Initial delivery URL
|
|
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
|
-
*
|
|
21
|
+
* @typedef {object} WriterRoute
|
|
22
|
+
* @property {URL} url - Route base URL
|
|
23
|
+
* @property {string} endpoint - Route endpoint
|
|
24
|
+
* @property {object} headers - Route-specific headers
|
|
25
|
+
* @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} ActiveWriterRoute
|
|
30
|
+
* @property {URL} url - Route base URL
|
|
31
|
+
* @property {string} endpoint - Route endpoint
|
|
32
|
+
* @property {object} requestOptions - HTTP request options
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tests whether a local route definitively rejected an event batch.
|
|
37
|
+
*
|
|
38
|
+
* @param {Error | null} error - Request error
|
|
39
|
+
* @param {number | undefined} statusCode - HTTP response status
|
|
40
|
+
* @returns {boolean} Whether direct retry is safe
|
|
41
|
+
*/
|
|
42
|
+
function isDefinitiveRejection (error, statusCode) {
|
|
43
|
+
return error?.code === 'EAI_AGAIN' || error?.code === 'ECONNREFUSED' ||
|
|
44
|
+
error?.code === 'ENOENT' || error?.code === 'ENOTFOUND' ||
|
|
45
|
+
statusCode === 403 || statusCode === 404 || statusCode === 405
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Tests whether a local route can have accepted an event batch before failing.
|
|
50
|
+
*
|
|
51
|
+
* @param {Error | null} error - Request error
|
|
52
|
+
* @returns {boolean} Whether the delivery result is ambiguous
|
|
53
|
+
*/
|
|
54
|
+
function isAmbiguousNetworkFailure (error) {
|
|
55
|
+
return error?.code === 'ECONNRESET' || error?.code === 'EPIPE' || error?.code === 'ETIMEDOUT'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Base writer for Feature Flagging and Experimentation event delivery.
|
|
22
60
|
* @class BaseFFEWriter
|
|
23
61
|
*/
|
|
24
62
|
class BaseFFEWriter {
|
|
@@ -40,6 +78,7 @@ class BaseFFEWriter {
|
|
|
40
78
|
this._payloadSizeLimit = payloadSizeLimit
|
|
41
79
|
this._eventSizeLimit = eventSizeLimit
|
|
42
80
|
this._headers = headers || {}
|
|
81
|
+
this._fallbackRoute = undefined
|
|
43
82
|
|
|
44
83
|
this._requestOptions = {
|
|
45
84
|
headers: {
|
|
@@ -47,6 +86,7 @@ class BaseFFEWriter {
|
|
|
47
86
|
'Content-Type': 'application/json',
|
|
48
87
|
},
|
|
49
88
|
method: 'POST',
|
|
89
|
+
retry: true,
|
|
50
90
|
timeout: this._timeout,
|
|
51
91
|
url: this._baseUrl,
|
|
52
92
|
path: this._endpoint,
|
|
@@ -115,15 +155,8 @@ class BaseFFEWriter {
|
|
|
115
155
|
// eslint-disable-next-line eslint-rules/eslint-log-printf-style
|
|
116
156
|
log.debug(() => `${this.constructor.name} flushing payload: ${safeJSONStringify(payload)}`)
|
|
117
157
|
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
})
|
|
158
|
+
const route = this.#createActiveRoute()
|
|
159
|
+
this.#sendRequest(payload, events.length, route, this._fallbackRoute)
|
|
127
160
|
}
|
|
128
161
|
|
|
129
162
|
/**
|
|
@@ -161,6 +194,115 @@ class BaseFFEWriter {
|
|
|
161
194
|
_encode (payload) {
|
|
162
195
|
return JSON.stringify(payload)
|
|
163
196
|
}
|
|
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
|
+
}
|
|
164
306
|
}
|
|
165
307
|
|
|
166
308
|
module.exports = BaseFFEWriter
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
EXPOSURES_ENDPOINT,
|
|
5
|
-
EVP_PROXY_AGENT_BASE_PATH,
|
|
6
|
-
EVP_SUBDOMAIN_HEADER_NAME,
|
|
7
|
-
EVP_SUBDOMAIN_VALUE,
|
|
8
5
|
EVP_PAYLOAD_SIZE_LIMIT,
|
|
9
6
|
EVP_EVENT_SIZE_LIMIT,
|
|
10
7
|
} = 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')
|
|
11
14
|
const log = require('../../log')
|
|
12
15
|
const BaseFFEWriter = require('./base')
|
|
13
16
|
|
|
@@ -16,6 +19,15 @@ const BaseFFEWriter = require('./base')
|
|
|
16
19
|
// drop emits a warning and `droppedEventCount` accumulates the cumulative loss.
|
|
17
20
|
const PENDING_MAX_EVENTS = 1000
|
|
18
21
|
|
|
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
|
+
|
|
19
31
|
/**
|
|
20
32
|
* @typedef {object} ExposureEvent
|
|
21
33
|
* @property {number} timestamp - Unix timestamp in milliseconds
|
|
@@ -45,10 +57,10 @@ const PENDING_MAX_EVENTS = 1000
|
|
|
45
57
|
*/
|
|
46
58
|
|
|
47
59
|
/**
|
|
48
|
-
*
|
|
60
|
+
* Sends exposure events through the selected local EVP proxy or direct intake route.
|
|
49
61
|
*/
|
|
50
62
|
class ExposuresWriter extends BaseFFEWriter {
|
|
51
|
-
// Disabled until
|
|
63
|
+
// Disabled until route selection resolves.
|
|
52
64
|
#enabled = false
|
|
53
65
|
|
|
54
66
|
/** @type {ExposureEvent[]} */
|
|
@@ -61,22 +73,27 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
61
73
|
|
|
62
74
|
/**
|
|
63
75
|
* @param {import('../../config/config-base')} config - Tracer configuration object
|
|
76
|
+
* @param {ExposureRoute} [route] - Caller-supplied route
|
|
64
77
|
*/
|
|
65
|
-
constructor (config) {
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
78
|
+
constructor (config, route) {
|
|
79
|
+
route ??= { url: config.url, basePath: EVP_PROXY_PATH_V2 }
|
|
80
|
+
const headers = route.headers ?? {
|
|
81
|
+
[EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
|
|
82
|
+
}
|
|
69
83
|
|
|
70
84
|
super({
|
|
71
85
|
config,
|
|
72
|
-
|
|
86
|
+
agentUrl: route.url,
|
|
87
|
+
endpoint: joinEVPProxyPath(route.basePath, EXPOSURES_ENDPOINT),
|
|
73
88
|
payloadSizeLimit: EVP_PAYLOAD_SIZE_LIMIT,
|
|
74
89
|
eventSizeLimit: EVP_EVENT_SIZE_LIMIT,
|
|
75
|
-
headers
|
|
76
|
-
[EVP_SUBDOMAIN_HEADER_NAME]: EVP_SUBDOMAIN_VALUE,
|
|
77
|
-
},
|
|
90
|
+
headers,
|
|
78
91
|
})
|
|
79
92
|
|
|
93
|
+
if (route.agent || route.fallback) {
|
|
94
|
+
this.#setRoute({ ...route, headers })
|
|
95
|
+
}
|
|
96
|
+
|
|
80
97
|
/** @type {ExposureContext} */
|
|
81
98
|
const context = {
|
|
82
99
|
service: config.service,
|
|
@@ -95,8 +112,14 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
95
112
|
|
|
96
113
|
/**
|
|
97
114
|
* @param {boolean} enabled - Whether to enable the writer
|
|
115
|
+
* @param {ExposureRoute} [route] - Selected EVP route
|
|
116
|
+
* @returns {void}
|
|
98
117
|
*/
|
|
99
|
-
setEnabled (enabled) {
|
|
118
|
+
setEnabled (enabled, route) {
|
|
119
|
+
if (route) {
|
|
120
|
+
this.#setRoute(route)
|
|
121
|
+
}
|
|
122
|
+
|
|
100
123
|
this.#enabled = enabled
|
|
101
124
|
|
|
102
125
|
if (enabled && this.#pendingEvents.length > 0) {
|
|
@@ -106,6 +129,31 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
106
129
|
}
|
|
107
130
|
}
|
|
108
131
|
|
|
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
|
+
|
|
109
157
|
/**
|
|
110
158
|
* Appends exposure event(s) to the buffer
|
|
111
159
|
* @param {ExposureEvent|ExposureEvent[]} events - Exposure event(s) to append
|
|
@@ -1,30 +1,51 @@
|
|
|
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')
|
|
3
11
|
const logger = require('../../log')
|
|
4
|
-
|
|
5
|
-
|
|
12
|
+
|
|
13
|
+
let missingRouteWarningLogged = false
|
|
6
14
|
|
|
7
15
|
/**
|
|
8
|
-
*
|
|
16
|
+
* Logs the unavailable exposure-delivery warning once.
|
|
17
|
+
*
|
|
18
|
+
* @returns {void}
|
|
19
|
+
*/
|
|
20
|
+
function warnExposureDeliveryUnavailable () {
|
|
21
|
+
if (missingRouteWarningLogged) return
|
|
22
|
+
missingRouteWarningLogged = true
|
|
23
|
+
logger.warn(
|
|
24
|
+
'Feature Flags exposure delivery is disabled because no compatible local EVP route or direct intake ' +
|
|
25
|
+
'credentials are available.'
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Preserves Agent exposure delivery for the Remote Configuration source.
|
|
31
|
+
*
|
|
9
32
|
* @param {import('../../config')} config - Tracer configuration object
|
|
10
33
|
* @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
|
|
34
|
+
* @returns {void}
|
|
11
35
|
*/
|
|
12
36
|
function setAgentStrategy (config, setWriterEnabledValue) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
37
|
+
discoverEVPProxy(config.url, {
|
|
38
|
+
supportedPaths: [EVP_PROXY_PATH_V2],
|
|
39
|
+
}, (error, route) => {
|
|
40
|
+
if (error) {
|
|
41
|
+
logger.debug('FFE Writer disabled - error getting agent info: %s', error.message)
|
|
16
42
|
setWriterEnabledValue(false)
|
|
17
43
|
return
|
|
18
44
|
}
|
|
19
45
|
|
|
20
|
-
|
|
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) {
|
|
46
|
+
if (route) {
|
|
26
47
|
logger.debug('FFE Writer enabled - agent has EVP proxy support')
|
|
27
|
-
setWriterEnabledValue(true)
|
|
48
|
+
setWriterEnabledValue(true, route)
|
|
28
49
|
} else {
|
|
29
50
|
logger.debug('FFE Writer disabled - agent does not have EVP proxy support')
|
|
30
51
|
setWriterEnabledValue(false)
|
|
@@ -32,6 +53,67 @@ function setAgentStrategy (config, setWriterEnabledValue) {
|
|
|
32
53
|
})
|
|
33
54
|
}
|
|
34
55
|
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Selects a local serverless receiver or authenticated direct intake.
|
|
58
|
+
*
|
|
59
|
+
* Local discovery is optional for delivery. A missing listener, discovery
|
|
60
|
+
* error, or incompatible receiver selects direct intake when credentials exist.
|
|
61
|
+
*
|
|
62
|
+
* @param {import('../../config')} config - Tracer configuration object
|
|
63
|
+
* @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
|
|
64
|
+
* @returns {void}
|
|
65
|
+
*/
|
|
66
|
+
function setAgentlessStrategy (config, setWriterEnabledValue) {
|
|
67
|
+
const directRoute = createDirectEVPRoute(config, EVP_EVENT_PLATFORM_SUBDOMAIN)
|
|
68
|
+
|
|
69
|
+
discoverEVPProxy(config.url, {
|
|
70
|
+
supportedPaths: [EVP_PROXY_PATH_V4, EVP_PROXY_PATH_V2],
|
|
71
|
+
}, (error, localRoute) => {
|
|
72
|
+
if (localRoute) {
|
|
73
|
+
const route = {
|
|
74
|
+
...localRoute,
|
|
75
|
+
headers: {
|
|
76
|
+
[EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
|
|
77
|
+
},
|
|
78
|
+
...(directRoute && { fallback: directRoute }),
|
|
79
|
+
}
|
|
80
|
+
logger.debug('FFE Writer enabled with local EVP route %s', route.basePath)
|
|
81
|
+
setWriterEnabledValue(true, route)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (directRoute) {
|
|
86
|
+
if (error) {
|
|
87
|
+
logger.debug('FFE Writer using direct EVP intake after local discovery failed: %s', error.message)
|
|
88
|
+
} else {
|
|
89
|
+
logger.debug('FFE Writer using direct EVP intake because no compatible local route was advertised')
|
|
90
|
+
}
|
|
91
|
+
setWriterEnabledValue(true, directRoute)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (error) {
|
|
96
|
+
logger.debug('FFE Writer disabled - error getting local receiver info: %s', error.message)
|
|
97
|
+
}
|
|
98
|
+
warnExposureDeliveryUnavailable()
|
|
99
|
+
setWriterEnabledValue(false)
|
|
100
|
+
})
|
|
37
101
|
}
|
|
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 }
|
|
@@ -21,14 +21,14 @@ class ContextManager {
|
|
|
21
21
|
const storedSpan = store ? trace.getSpan(store) : null
|
|
22
22
|
|
|
23
23
|
// Convert DD baggage to OTel format
|
|
24
|
-
|
|
25
|
-
const
|
|
24
|
+
let entries
|
|
25
|
+
for (const [key, value] of Object.entries(getAllBaggageItems())) {
|
|
26
|
+
entries ??= {}
|
|
27
|
+
entries[key] = { value }
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
let otelBaggages
|
|
27
|
-
if (
|
|
28
|
-
const entries = {}
|
|
29
|
-
for (const [key, value] of Object.entries(baggages)) {
|
|
30
|
-
entries[key] = { value }
|
|
31
|
-
}
|
|
31
|
+
if (entries !== undefined) {
|
|
32
32
|
otelBaggages = propagation.createBaggage(entries)
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -42,7 +42,8 @@ class BatchLogRecordProcessor {
|
|
|
42
42
|
*/
|
|
43
43
|
onEmit (logRecord, instrumentationScope) {
|
|
44
44
|
// Store the log record (already enriched by Logger.emit)
|
|
45
|
-
|
|
45
|
+
logRecord.instrumentationScope = instrumentationScope
|
|
46
|
+
this.#logRecords.push(logRecord)
|
|
46
47
|
|
|
47
48
|
if (this.#logRecords.length >= this.#maxExportBatchSize) {
|
|
48
49
|
this.#export()
|
|
@@ -1,8 +1,65 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { context } = require('@opentelemetry/api')
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
millisToHrTime,
|
|
6
|
+
sanitizeAttributes,
|
|
7
|
+
timeInputToHrTime,
|
|
8
|
+
} = require('../../../../../vendor/dist/@opentelemetry/core')
|
|
5
9
|
const { VERSION: packageVersion } = require('../../../../../version')
|
|
10
|
+
const log = require('../../log')
|
|
11
|
+
|
|
12
|
+
const MAX_DATE_MILLISECONDS = 8.64e15
|
|
13
|
+
const NANOSECONDS_PER_SECOND = 1e9
|
|
14
|
+
const MAX_UINT64_SECONDS = 18_446_744_073
|
|
15
|
+
const MAX_UINT64_NANOSECONDS = 709_551_615
|
|
16
|
+
let invalidTimestampWarningLogged = false
|
|
17
|
+
|
|
18
|
+
function isValidHrTime (hrTime) {
|
|
19
|
+
return Array.isArray(hrTime) &&
|
|
20
|
+
hrTime.length === 2 &&
|
|
21
|
+
Number.isInteger(hrTime[0]) &&
|
|
22
|
+
Number.isInteger(hrTime[1]) &&
|
|
23
|
+
hrTime[0] >= 0 &&
|
|
24
|
+
hrTime[1] >= 0 &&
|
|
25
|
+
hrTime[1] < NANOSECONDS_PER_SECOND &&
|
|
26
|
+
(hrTime[0] < MAX_UINT64_SECONDS ||
|
|
27
|
+
(hrTime[0] === MAX_UINT64_SECONDS && hrTime[1] <= MAX_UINT64_NANOSECONDS))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toHrTime (timestamp) {
|
|
31
|
+
if (typeof timestamp === 'number') {
|
|
32
|
+
if (!Number.isFinite(timestamp)) return
|
|
33
|
+
|
|
34
|
+
// Older versions documented numeric timestamps as Unix nanoseconds.
|
|
35
|
+
if (Math.abs(timestamp) > MAX_DATE_MILLISECONDS) {
|
|
36
|
+
const seconds = Math.trunc(timestamp / NANOSECONDS_PER_SECOND)
|
|
37
|
+
const hrTime = [seconds, timestamp - seconds * NANOSECONDS_PER_SECOND]
|
|
38
|
+
return isValidHrTime(hrTime) ? hrTime : undefined
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const hrTime = timeInputToHrTime(timestamp)
|
|
44
|
+
return isValidHrTime(hrTime) ? hrTime : undefined
|
|
45
|
+
} catch {
|
|
46
|
+
// Invalid timestamps fall back to the current time in normalizeTimestamp.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeTimestamp (timestamp) {
|
|
51
|
+
if (timestamp !== undefined) {
|
|
52
|
+
const hrTime = toHrTime(timestamp)
|
|
53
|
+
if (hrTime) return hrTime
|
|
54
|
+
|
|
55
|
+
if (!invalidTimestampWarningLogged) {
|
|
56
|
+
invalidTimestampWarningLogged = true
|
|
57
|
+
log.warn('Invalid OpenTelemetry log timestamp; using the current time instead')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return millisToHrTime(Date.now())
|
|
62
|
+
}
|
|
6
63
|
|
|
7
64
|
/**
|
|
8
65
|
* @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
|
|
@@ -62,21 +119,20 @@ class Logger {
|
|
|
62
119
|
return
|
|
63
120
|
}
|
|
64
121
|
|
|
65
|
-
|
|
66
|
-
logRecord
|
|
122
|
+
const record = {
|
|
123
|
+
...logRecord,
|
|
124
|
+
timestamp: normalizeTimestamp(logRecord.timestamp),
|
|
125
|
+
context: logRecord.context || context.active(),
|
|
67
126
|
}
|
|
68
127
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
logRecord.timestamp = Number(process.hrtime.bigint())
|
|
128
|
+
if (logRecord.observedTimestamp !== undefined) {
|
|
129
|
+
record.observedTimestamp = normalizeTimestamp(logRecord.observedTimestamp)
|
|
72
130
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// Store span context in the log record context for trace correlation
|
|
76
|
-
logRecord.context = context.active()
|
|
131
|
+
if (logRecord.attributes) {
|
|
132
|
+
record.attributes = sanitizeAttributes(logRecord.attributes)
|
|
77
133
|
}
|
|
78
134
|
|
|
79
|
-
this.loggerProvider.processor.onEmit(
|
|
135
|
+
this.loggerProvider.processor.onEmit(record, this.#instrumentationScope)
|
|
80
136
|
}
|
|
81
137
|
}
|
|
82
138
|
|
|
@@ -38,6 +38,17 @@ const SEVERITY_MAP = {
|
|
|
38
38
|
[SeverityNumber.FATAL4]: 'SEVERITY_NUMBER_FATAL4',
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
const NANOSECONDS_PER_SECOND = 1_000_000_000n
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Converts an OpenTelemetry HrTime to an exact OTLP uint64 decimal string.
|
|
45
|
+
* @param {import('@opentelemetry/api').HrTime} hrTime
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
function encodeHrTime (hrTime) {
|
|
49
|
+
return (BigInt(hrTime[0]) * NANOSECONDS_PER_SECOND + BigInt(hrTime[1])).toString()
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
/**
|
|
42
53
|
* OtlpTransformer transforms log records to OTLP format.
|
|
43
54
|
*
|
|
@@ -138,13 +149,13 @@ class OtlpTransformer extends OtlpTransformerBase {
|
|
|
138
149
|
const spanContext = this.#extractSpanContext(logRecord.context)
|
|
139
150
|
|
|
140
151
|
const result = {
|
|
141
|
-
timeUnixNano: logRecord.timestamp,
|
|
152
|
+
timeUnixNano: encodeHrTime(logRecord.timestamp),
|
|
142
153
|
body: this.#transformBody(logRecord.body),
|
|
143
154
|
}
|
|
144
155
|
|
|
145
156
|
// Add optional fields only if they are set
|
|
146
|
-
if (logRecord.observedTimestamp) {
|
|
147
|
-
result.observedTimeUnixNano = logRecord.observedTimestamp
|
|
157
|
+
if (logRecord.observedTimestamp !== undefined) {
|
|
158
|
+
result.observedTimeUnixNano = encodeHrTime(logRecord.observedTimestamp)
|
|
148
159
|
}
|
|
149
160
|
|
|
150
161
|
if (logRecord.severityNumber !== undefined) {
|