dd-trace 6.11.0 → 6.13.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/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
- package/index.d.ts +143 -25
- package/package.json +26 -25
- package/packages/datadog-instrumentations/src/bunyan.js +4 -2
- package/packages/datadog-instrumentations/src/cucumber.js +4 -4
- package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +28 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/jest.js +140 -18
- package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
- package/packages/datadog-instrumentations/src/mariadb.js +7 -0
- package/packages/datadog-instrumentations/src/mocha/utils.js +5 -2
- package/packages/datadog-instrumentations/src/pino.js +15 -4
- package/packages/datadog-instrumentations/src/playwright-reporter.js +149 -1
- package/packages/datadog-instrumentations/src/playwright.js +67 -53
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +13 -12
- package/packages/datadog-instrumentations/src/vitest-worker.js +11 -0
- package/packages/datadog-instrumentations/src/webdriverio.js +26 -0
- package/packages/datadog-instrumentations/src/winston.js +6 -3
- package/packages/datadog-plugin-cypress/src/support.js +1 -0
- package/packages/datadog-plugin-http/src/client.js +2 -15
- package/packages/datadog-plugin-http2/src/client.js +2 -15
- package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
- package/packages/datadog-plugin-playwright/src/index.js +13 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -15
- package/packages/dd-trace/src/aiguard/evaluation.js +94 -20
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +21 -9
- package/packages/dd-trace/src/aiguard/noop.js +9 -1
- package/packages/dd-trace/src/aiguard/redaction.js +183 -0
- package/packages/dd-trace/src/aiguard/sdk.js +7 -2
- package/packages/dd-trace/src/aiguard/tags.js +2 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
- package/packages/dd-trace/src/appsec/reporter.js +2 -0
- package/packages/dd-trace/src/appsec/rule_manager.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/early-flake-detection/get-known-tests.js +5 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
- package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +5 -3
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
- 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 +4 -2
- 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 +6 -0
- package/packages/dd-trace/src/config/index.js +2 -1
- package/packages/dd-trace/src/config/supported-configurations.json +27 -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 +3 -10
- package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -3
- 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/dogstatsd.js +45 -18
- package/packages/dd-trace/src/encode/agentless-json.js +5 -4
- 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/index.js +22 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
- package/packages/dd-trace/src/exporters/common/request.js +5 -2
- package/packages/dd-trace/src/exporters/common/writer.js +24 -2
- package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
- package/packages/dd-trace/src/external-logger/src/index.js +4 -3
- package/packages/dd-trace/src/flush.js +102 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +10 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +301 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +440 -112
- package/packages/dd-trace/src/llmobs/experiments/index.js +68 -44
- package/packages/dd-trace/src/llmobs/experiments/noop.js +103 -12
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +81 -4
- package/packages/dd-trace/src/llmobs/index.js +80 -17
- package/packages/dd-trace/src/llmobs/noop.js +2 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
- package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
- package/packages/dd-trace/src/llmobs/writers/base.js +60 -24
- package/packages/dd-trace/src/noop/span.js +5 -0
- package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
- package/packages/dd-trace/src/openfeature/index.js +7 -4
- package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
- package/packages/dd-trace/src/openfeature/writers/exposures.js +70 -15
- package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
- package/packages/dd-trace/src/opentelemetry/logs/index.js +4 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +3 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +4 -2
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +4 -2
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
- package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +429 -352
- package/packages/dd-trace/src/opentracing/span.js +28 -0
- package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
- package/packages/dd-trace/src/plugins/util/test.js +74 -21
- package/packages/dd-trace/src/plugins/util/web.js +2 -65
- package/packages/dd-trace/src/profiling/oom.js +4 -3
- package/packages/dd-trace/src/profiling/profiler.js +7 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
- package/packages/dd-trace/src/proxy.js +36 -8
- package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -6
- package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +55 -0
- package/packages/dd-trace/src/serverless/vercel.js +122 -0
- package/packages/dd-trace/src/serverless.js +37 -20
- package/packages/dd-trace/src/span_stats.js +40 -3
- 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 +22 -0
- package/packages/dd-trace/src/web-tags-cache.js +67 -13
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- 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
|
@@ -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
|
|
@@ -25,6 +37,7 @@ const PENDING_MAX_EVENTS = 1000
|
|
|
25
37
|
* @property {string} flag.key - Flag key
|
|
26
38
|
* @property {object} variant - Variant information
|
|
27
39
|
* @property {string} variant.key - Variant key
|
|
40
|
+
* @property {number} [serial_id] - Serial id of the split the subject landed in
|
|
28
41
|
* @property {object} subject - Subject (user/entity) information
|
|
29
42
|
* @property {string} subject.id - Subject identifier
|
|
30
43
|
* @property {string} [subject.type] - Subject type
|
|
@@ -45,10 +58,10 @@ const PENDING_MAX_EVENTS = 1000
|
|
|
45
58
|
*/
|
|
46
59
|
|
|
47
60
|
/**
|
|
48
|
-
*
|
|
61
|
+
* Sends exposure events through the selected local EVP proxy or direct intake route.
|
|
49
62
|
*/
|
|
50
63
|
class ExposuresWriter extends BaseFFEWriter {
|
|
51
|
-
// Disabled until
|
|
64
|
+
// Disabled until route selection resolves.
|
|
52
65
|
#enabled = false
|
|
53
66
|
|
|
54
67
|
/** @type {ExposureEvent[]} */
|
|
@@ -61,22 +74,27 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
61
74
|
|
|
62
75
|
/**
|
|
63
76
|
* @param {import('../../config/config-base')} config - Tracer configuration object
|
|
77
|
+
* @param {ExposureRoute} [route] - Caller-supplied route
|
|
64
78
|
*/
|
|
65
|
-
constructor (config) {
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
79
|
+
constructor (config, route) {
|
|
80
|
+
route ??= { url: config.url, basePath: EVP_PROXY_PATH_V2 }
|
|
81
|
+
const headers = route.headers ?? {
|
|
82
|
+
[EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
|
|
83
|
+
}
|
|
69
84
|
|
|
70
85
|
super({
|
|
71
86
|
config,
|
|
72
|
-
|
|
87
|
+
agentUrl: route.url,
|
|
88
|
+
endpoint: joinEVPProxyPath(route.basePath, EXPOSURES_ENDPOINT),
|
|
73
89
|
payloadSizeLimit: EVP_PAYLOAD_SIZE_LIMIT,
|
|
74
90
|
eventSizeLimit: EVP_EVENT_SIZE_LIMIT,
|
|
75
|
-
headers
|
|
76
|
-
[EVP_SUBDOMAIN_HEADER_NAME]: EVP_SUBDOMAIN_VALUE,
|
|
77
|
-
},
|
|
91
|
+
headers,
|
|
78
92
|
})
|
|
79
93
|
|
|
94
|
+
if (route.agent || route.fallback) {
|
|
95
|
+
this.#setRoute({ ...route, headers })
|
|
96
|
+
}
|
|
97
|
+
|
|
80
98
|
/** @type {ExposureContext} */
|
|
81
99
|
const context = {
|
|
82
100
|
service: config.service,
|
|
@@ -95,8 +113,14 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
95
113
|
|
|
96
114
|
/**
|
|
97
115
|
* @param {boolean} enabled - Whether to enable the writer
|
|
116
|
+
* @param {ExposureRoute} [route] - Selected EVP route
|
|
117
|
+
* @returns {void}
|
|
98
118
|
*/
|
|
99
|
-
setEnabled (enabled) {
|
|
119
|
+
setEnabled (enabled, route) {
|
|
120
|
+
if (route) {
|
|
121
|
+
this.#setRoute(route)
|
|
122
|
+
}
|
|
123
|
+
|
|
100
124
|
this.#enabled = enabled
|
|
101
125
|
|
|
102
126
|
if (enabled && this.#pendingEvents.length > 0) {
|
|
@@ -106,6 +130,31 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
106
130
|
}
|
|
107
131
|
}
|
|
108
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Applies caller-supplied route data without performing discovery.
|
|
135
|
+
*
|
|
136
|
+
* @param {ExposureRoute} route - Selected EVP route
|
|
137
|
+
* @returns {void}
|
|
138
|
+
*/
|
|
139
|
+
#setRoute (route) {
|
|
140
|
+
const fallbackRoute = route.fallback && {
|
|
141
|
+
url: route.fallback.url,
|
|
142
|
+
endpoint: joinEVPProxyPath(route.fallback.basePath, EXPOSURES_ENDPOINT),
|
|
143
|
+
headers: route.fallback.headers ?? {},
|
|
144
|
+
agent: route.fallback.agent,
|
|
145
|
+
}
|
|
146
|
+
const headers = route.headers ?? {
|
|
147
|
+
[EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this._setRoutes({
|
|
151
|
+
url: route.url,
|
|
152
|
+
endpoint: joinEVPProxyPath(route.basePath, EXPOSURES_ENDPOINT),
|
|
153
|
+
headers,
|
|
154
|
+
agent: route.agent,
|
|
155
|
+
}, fallbackRoute)
|
|
156
|
+
}
|
|
157
|
+
|
|
109
158
|
/**
|
|
110
159
|
* Appends exposure event(s) to the buffer
|
|
111
160
|
* @param {ExposureEvent|ExposureEvent[]} events - Exposure event(s) to append
|
|
@@ -156,7 +205,7 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
156
205
|
makePayload (events) {
|
|
157
206
|
const formattedEvents = events.map(event => {
|
|
158
207
|
/** @type {ExposureEvent} */
|
|
159
|
-
|
|
208
|
+
const formatted = {
|
|
160
209
|
timestamp: event.timestamp || Date.now(),
|
|
161
210
|
allocation: {
|
|
162
211
|
key: event.allocation?.key || event['allocation.key'],
|
|
@@ -173,6 +222,12 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
173
222
|
attributes: event.subject?.attributes,
|
|
174
223
|
},
|
|
175
224
|
}
|
|
225
|
+
|
|
226
|
+
if (typeof event.serial_id === 'number') {
|
|
227
|
+
formatted.serial_id = event.serial_id
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return formatted
|
|
176
231
|
})
|
|
177
232
|
|
|
178
233
|
return {
|
|
@@ -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 }
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../../log')
|
|
4
|
+
const { createServerlessDeliveryTracker } = require('../../serverless')
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
|
|
5
8
|
* @typedef {import('@opentelemetry/core').InstrumentationScope} InstrumentationScope
|
|
@@ -54,10 +57,48 @@ class BatchLogRecordProcessor {
|
|
|
54
57
|
|
|
55
58
|
/**
|
|
56
59
|
* Forces an immediate flush of all pending log records.
|
|
57
|
-
* @
|
|
60
|
+
* @param {Function} [done] Called after all pending log exports complete
|
|
58
61
|
*/
|
|
59
|
-
forceFlush () {
|
|
60
|
-
this.#
|
|
62
|
+
forceFlush (done) {
|
|
63
|
+
this.#clearTimer()
|
|
64
|
+
|
|
65
|
+
const deliveryTracker = createServerlessDeliveryTracker()
|
|
66
|
+
if (!deliveryTracker) {
|
|
67
|
+
// Normal processes preserve the existing fire-and-forget batch flush.
|
|
68
|
+
this.#export()
|
|
69
|
+
done?.()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Flush only records present at this boundary. New records belong to the
|
|
74
|
+
// later request that produced them and must not extend this lifecycle flush.
|
|
75
|
+
const logRecords = this.#logRecords
|
|
76
|
+
this.#logRecords = []
|
|
77
|
+
|
|
78
|
+
// Join exports already active at this boundary before draining this snapshot.
|
|
79
|
+
if (typeof this.exporter.flush === 'function') {
|
|
80
|
+
deliveryTracker.track(complete => this.exporter.flush(complete))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
deliveryTracker.track(complete => {
|
|
84
|
+
const flushNext = () => {
|
|
85
|
+
if (logRecords.length === 0) {
|
|
86
|
+
complete()
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Drain the boundary snapshot one batch at a time.
|
|
91
|
+
const batch = logRecords.splice(0, this.#maxExportBatchSize)
|
|
92
|
+
try {
|
|
93
|
+
this.exporter.export(batch, flushNext)
|
|
94
|
+
} catch (error) {
|
|
95
|
+
log.error('Error exporting OTLP logs:', error)
|
|
96
|
+
complete()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
flushNext()
|
|
100
|
+
})
|
|
101
|
+
deliveryTracker.waitForIdle(done)
|
|
61
102
|
}
|
|
62
103
|
|
|
63
104
|
/**
|
|
@@ -79,6 +120,7 @@ class BatchLogRecordProcessor {
|
|
|
79
120
|
* @private
|
|
80
121
|
*/
|
|
81
122
|
#export () {
|
|
123
|
+
if (this.#logRecords.length === 0) return
|
|
82
124
|
const logRecords = this.#logRecords.slice(0, this.#maxExportBatchSize)
|
|
83
125
|
this.#logRecords = this.#logRecords.slice(this.#maxExportBatchSize)
|
|
84
126
|
|
|
@@ -27,6 +27,7 @@ const os = require('os')
|
|
|
27
27
|
* @package
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
+
const { registerTelemetryFlusher } = require('../../flush')
|
|
30
31
|
const LoggerProvider = require('./logger_provider')
|
|
31
32
|
const BatchLogRecordProcessor = require('./batch_log_processor')
|
|
32
33
|
const OtlpHttpLogExporter = require('./otlp_http_log_exporter')
|
|
@@ -77,8 +78,10 @@ function initializeOpenTelemetryLogs (config) {
|
|
|
77
78
|
// Create logger provider with processor for Datadog Agent export
|
|
78
79
|
const loggerProvider = new LoggerProvider({ processor })
|
|
79
80
|
|
|
80
|
-
//
|
|
81
|
+
// Expose this provider to application calls through the OpenTelemetry Logs API.
|
|
81
82
|
loggerProvider.register()
|
|
83
|
+
// Include final log batches in lifecycle retention with trace delivery.
|
|
84
|
+
registerTelemetryFlusher(done => loggerProvider.forceFlush(done))
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
module.exports = {
|
|
@@ -84,12 +84,15 @@ class LoggerProvider {
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Forces a flush of all pending log records.
|
|
87
|
-
* @
|
|
87
|
+
* @param {Function} [done] Called after all pending log exports complete
|
|
88
88
|
*/
|
|
89
|
-
forceFlush () {
|
|
90
|
-
if (!this.
|
|
91
|
-
|
|
89
|
+
forceFlush (done) {
|
|
90
|
+
if (this.isShutdown || !this.processor) {
|
|
91
|
+
done?.()
|
|
92
|
+
return
|
|
92
93
|
}
|
|
94
|
+
|
|
95
|
+
this.processor.forceFlush(done)
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
/**
|
|
@@ -6,12 +6,12 @@ const { metrics } = require('@opentelemetry/api')
|
|
|
6
6
|
|
|
7
7
|
const { VERSION } = require('../../../../../version')
|
|
8
8
|
const processTags = require('../../process-tags')
|
|
9
|
+
const { registerTelemetryFlusher } = require('../../flush')
|
|
9
10
|
const MeterProvider = require('./meter_provider')
|
|
10
11
|
const PeriodicMetricReader = require('./periodic_metric_reader')
|
|
11
12
|
const OtlpHttpMetricExporter = require('./otlp_http_metric_exporter')
|
|
12
13
|
|
|
13
14
|
const RESERVED_TRACER_TAGS = new Set(['service', 'env', 'version', 'runtime_id', 'runtime-id'])
|
|
14
|
-
|
|
15
15
|
/**
|
|
16
16
|
* @typedef {import('../../config')} Config
|
|
17
17
|
*/
|
|
@@ -78,6 +78,8 @@ function initializeOpenTelemetryMetrics (config) {
|
|
|
78
78
|
|
|
79
79
|
const meterProvider = new MeterProvider({ reader })
|
|
80
80
|
metrics.setGlobalMeterProvider(meterProvider)
|
|
81
|
+
// Include the final metric collection and export in lifecycle retention.
|
|
82
|
+
registerTelemetryFlusher(done => meterProvider.forceFlush(done))
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
/**
|
|
@@ -49,6 +49,14 @@ class MeterProvider {
|
|
|
49
49
|
}
|
|
50
50
|
return meter
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {Function} [done] Called after the metric export completes
|
|
55
|
+
*/
|
|
56
|
+
forceFlush (done) {
|
|
57
|
+
if (this.reader) this.reader.forceFlush(done)
|
|
58
|
+
else done?.()
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
module.exports = MeterProvider
|
|
@@ -34,10 +34,11 @@ class OtlpHttpMetricExporter extends OtlpHttpExporterBase {
|
|
|
34
34
|
*
|
|
35
35
|
* @param {Map<string, AggregatedMetric>} metrics - Map of metric data to export
|
|
36
36
|
*
|
|
37
|
-
* @
|
|
37
|
+
* @param {Function} [done] Called after the HTTP export completes
|
|
38
38
|
*/
|
|
39
|
-
export (metrics) {
|
|
39
|
+
export (metrics, done) {
|
|
40
40
|
if (metrics.size === 0) {
|
|
41
|
+
done?.({ code: 0 })
|
|
41
42
|
return
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -56,6 +57,7 @@ class OtlpHttpMetricExporter extends OtlpHttpExporterBase {
|
|
|
56
57
|
if (result.code === 0) {
|
|
57
58
|
this.recordTelemetry('otel.metrics_export_successes', 1, additionalTags)
|
|
58
59
|
}
|
|
60
|
+
done?.(result)
|
|
59
61
|
})
|
|
60
62
|
}
|
|
61
63
|
}
|
|
@@ -22,14 +22,16 @@ class OtlpStatsExporter extends OtlpHttpExporterBase {
|
|
|
22
22
|
/**
|
|
23
23
|
* @param {Array<{timeNs: number, bucket: import('../../span_stats').SpanBuckets}>} drained
|
|
24
24
|
* @param {number} bucketSizeNs
|
|
25
|
+
* @param {Function} [done] Called after the HTTP export completes
|
|
25
26
|
*/
|
|
26
|
-
export (drained, bucketSizeNs) {
|
|
27
|
-
if (drained.length === 0) return
|
|
27
|
+
export (drained, bucketSizeNs, done) {
|
|
28
|
+
if (drained.length === 0) return done?.()
|
|
28
29
|
const payload = this.#transformer.transform(drained, bucketSizeNs)
|
|
29
30
|
this.sendPayload(payload, (result) => {
|
|
30
31
|
if (result.code !== 0) {
|
|
31
32
|
log.error('Failed to export span stats: %s', result.error?.message)
|
|
32
33
|
}
|
|
34
|
+
done?.()
|
|
33
35
|
})
|
|
34
36
|
}
|
|
35
37
|
}
|
|
@@ -197,14 +197,33 @@ class PeriodicMetricReader {
|
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
199
|
* Forces an immediate collection and export of all metrics.
|
|
200
|
-
* @
|
|
200
|
+
* @param {Function} [done] Called after the metric export completes
|
|
201
201
|
*/
|
|
202
|
-
forceFlush () {
|
|
202
|
+
forceFlush (done) {
|
|
203
203
|
if (this.#isShutdown) {
|
|
204
204
|
log.warn('PeriodicMetricReader is shutdown. %d measurement(s) were dropped', this.#droppedCount)
|
|
205
|
+
done?.()
|
|
205
206
|
return
|
|
206
207
|
}
|
|
207
|
-
|
|
208
|
+
let pending = 2
|
|
209
|
+
const complete = () => {
|
|
210
|
+
if (--pending === 0) done?.()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Snapshot requests already active before starting this flush's export.
|
|
214
|
+
try {
|
|
215
|
+
if (typeof this.exporter.flush === 'function') this.exporter.flush(complete)
|
|
216
|
+
else complete()
|
|
217
|
+
} catch (error) {
|
|
218
|
+
log.error('Error flushing OTLP metrics:', error)
|
|
219
|
+
complete()
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
this.#collectAndExport(complete)
|
|
223
|
+
} catch (error) {
|
|
224
|
+
log.error('Error exporting OTLP metrics:', error)
|
|
225
|
+
complete()
|
|
226
|
+
}
|
|
208
227
|
}
|
|
209
228
|
|
|
210
229
|
/**
|
|
@@ -250,7 +269,8 @@ class PeriodicMetricReader {
|
|
|
250
269
|
*
|
|
251
270
|
* @param {Function} [callback] - Called after export completes
|
|
252
271
|
*/
|
|
253
|
-
#collectAndExport (callback
|
|
272
|
+
#collectAndExport (callback) {
|
|
273
|
+
// Observable instruments must be collected even without synchronous measurements.
|
|
254
274
|
// Atomically drain measurements for export. New measurements can be recorded
|
|
255
275
|
// during export without interfering with this batch.
|
|
256
276
|
const allMeasurements = this.#measurements
|
|
@@ -292,7 +312,7 @@ class PeriodicMetricReader {
|
|
|
292
312
|
}
|
|
293
313
|
|
|
294
314
|
if (allMeasurements.length === 0) {
|
|
295
|
-
callback()
|
|
315
|
+
callback?.()
|
|
296
316
|
return
|
|
297
317
|
}
|
|
298
318
|
|
|
@@ -5,6 +5,7 @@ const https = require('node:https')
|
|
|
5
5
|
const { URL } = require('node:url')
|
|
6
6
|
const { storage } = require('../../../../datadog-core')
|
|
7
7
|
const log = require('../../log')
|
|
8
|
+
const { createServerlessDeliveryTracker } = require('../../serverless')
|
|
8
9
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
9
10
|
|
|
10
11
|
const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
|
|
@@ -20,6 +21,7 @@ const legacyStorage = storage('legacy')
|
|
|
20
21
|
*/
|
|
21
22
|
class OtlpHttpExporterBase {
|
|
22
23
|
#transport = https
|
|
24
|
+
#serverlessDeliveryTracker
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Creates a new OtlpHttpExporterBase instance.
|
|
@@ -32,6 +34,7 @@ class OtlpHttpExporterBase {
|
|
|
32
34
|
* @param {string} signalType - Signal type for error messages (e.g., 'logs', 'metrics')
|
|
33
35
|
*/
|
|
34
36
|
constructor (url, headers, timeout, protocol, signalType) {
|
|
37
|
+
this.#serverlessDeliveryTracker = createServerlessDeliveryTracker()
|
|
35
38
|
this.protocol = protocol
|
|
36
39
|
this.signalType = signalType
|
|
37
40
|
|
|
@@ -80,6 +83,13 @@ class OtlpHttpExporterBase {
|
|
|
80
83
|
* @protected
|
|
81
84
|
*/
|
|
82
85
|
sendPayload (payload, resultCallback) {
|
|
86
|
+
if (this.#serverlessDeliveryTracker) {
|
|
87
|
+
return this.#serverlessDeliveryTracker.track(done => this.#sendPayload(payload, resultCallback, done))
|
|
88
|
+
}
|
|
89
|
+
this.#sendPayload(payload, resultCallback)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#sendPayload (payload, resultCallback, done) {
|
|
83
93
|
const options = {
|
|
84
94
|
...this.options,
|
|
85
95
|
headers: {
|
|
@@ -88,39 +98,65 @@ class OtlpHttpExporterBase {
|
|
|
88
98
|
},
|
|
89
99
|
}
|
|
90
100
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
let completed = false
|
|
102
|
+
const complete = result => {
|
|
103
|
+
if (completed) return
|
|
104
|
+
completed = true
|
|
105
|
+
resultCallback(result)
|
|
106
|
+
done?.()
|
|
107
|
+
}
|
|
94
108
|
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
try {
|
|
110
|
+
legacyStorage.run({ noop: true }, () => {
|
|
111
|
+
const req = this.#transport.request(options, (res) => {
|
|
112
|
+
let data = ''
|
|
113
|
+
|
|
114
|
+
res.on('data', (chunk) => {
|
|
115
|
+
data += chunk
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
res.once('error', (error) => {
|
|
119
|
+
complete({ code: 1, error })
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
res.once('end', () => {
|
|
123
|
+
// @ts-expect-error - res.statusCode can be undefined
|
|
124
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
125
|
+
complete({ code: 0 })
|
|
126
|
+
} else {
|
|
127
|
+
const error = new Error(`HTTP ${res.statusCode}: ${data}`)
|
|
128
|
+
complete({ code: 1, error })
|
|
129
|
+
}
|
|
130
|
+
})
|
|
97
131
|
})
|
|
98
132
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
resultCallback({ code: 0 })
|
|
103
|
-
} else {
|
|
104
|
-
const error = new Error(`HTTP ${res.statusCode}: ${data}`)
|
|
105
|
-
resultCallback({ code: 1, error })
|
|
106
|
-
}
|
|
133
|
+
req.on('error', (error) => {
|
|
134
|
+
log.error('Error sending OTLP %s:', this.signalType, error)
|
|
135
|
+
complete({ code: 1, error })
|
|
107
136
|
})
|
|
108
|
-
})
|
|
109
137
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
138
|
+
req.once('timeout', () => {
|
|
139
|
+
req.destroy()
|
|
140
|
+
const error = new Error('Request timeout')
|
|
141
|
+
complete({ code: 1, error })
|
|
142
|
+
})
|
|
114
143
|
|
|
115
|
-
|
|
116
|
-
req.
|
|
117
|
-
const error = new Error('Request timeout')
|
|
118
|
-
resultCallback({ code: 1, error })
|
|
144
|
+
req.write(payload)
|
|
145
|
+
req.end()
|
|
119
146
|
})
|
|
147
|
+
} catch (error) {
|
|
148
|
+
log.error('Error sending OTLP %s:', this.signalType, error)
|
|
149
|
+
complete({ code: 1, error })
|
|
150
|
+
}
|
|
151
|
+
}
|
|
120
152
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Calls back once Vercel-tracked requests active at the flush boundary complete.
|
|
155
|
+
* @param {Function} [done]
|
|
156
|
+
*/
|
|
157
|
+
flush (done) {
|
|
158
|
+
if (this.#serverlessDeliveryTracker) return this.#serverlessDeliveryTracker.waitForIdle(done)
|
|
159
|
+
done?.()
|
|
124
160
|
}
|
|
125
161
|
|
|
126
162
|
/**
|