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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { fetchAgentInfo } = require('../agent/info')
|
|
4
|
+
const log = require('../log')
|
|
5
|
+
|
|
6
|
+
const TRAILING_SLASHES = /\/+$/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Receiver discovery contract
|
|
10
|
+
*
|
|
11
|
+
* The tracer sends `GET /info` to its configured local Agent URL. An
|
|
12
|
+
* Agent-compatible trace receiver produces the response. The tracer does not.
|
|
13
|
+
*
|
|
14
|
+
* The full Agent and serverless-init embed the same trace receiver. A future
|
|
15
|
+
* in-process receiver can expose the same contract. Callers can therefore
|
|
16
|
+
* select capabilities without detecting the receiver implementation.
|
|
17
|
+
*
|
|
18
|
+
* For EVP discovery, `endpoints` advertises registered proxy paths.
|
|
19
|
+
* `evp_proxy_allowed_headers` advertises headers that the proxy forwards to
|
|
20
|
+
* intake. It does not include routing headers that the proxy consumes, such as
|
|
21
|
+
* `X-Datadog-EVP-Subdomain`.
|
|
22
|
+
*
|
|
23
|
+
* An advertised route is not a health check. The receiver can register an EVP
|
|
24
|
+
* route while configuration disables its handler. The request then returns
|
|
25
|
+
* `405`. The `/info` version also identifies the embedded Agent code, not a
|
|
26
|
+
* serverless-init image or deployment type.
|
|
27
|
+
*
|
|
28
|
+
* This module only discovers a candidate route. A missing or unresponsive
|
|
29
|
+
* `/info` endpoint returns an error through the shared request timeout and
|
|
30
|
+
* retry policy. A valid response without a compatible path returns no route.
|
|
31
|
+
* Discovery sends no events, so the caller can safely select direct intake
|
|
32
|
+
* after either result. The caller also owns later delivery failures. Exposure
|
|
33
|
+
* delivery uses retries and can therefore produce duplicates. After local
|
|
34
|
+
* retries fail, the caller can retry through direct intake and use that route
|
|
35
|
+
* for future batches.
|
|
36
|
+
*
|
|
37
|
+
* Reference implementations:
|
|
38
|
+
*
|
|
39
|
+
* Agent `/info` and EVP proxy:
|
|
40
|
+
* https://github.com/DataDog/datadog-agent/tree/main/pkg/trace/api
|
|
41
|
+
*
|
|
42
|
+
* serverless-init entry point and embedded trace receiver:
|
|
43
|
+
* https://github.com/DataDog/datadog-agent/blob/main/cmd/serverless-init/main.go
|
|
44
|
+
* https://github.com/DataDog/datadog-agent/blob/main/pkg/serverless/trace/trace.go
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Selects the first advertised EVP proxy path that the caller supports.
|
|
49
|
+
*
|
|
50
|
+
* @param {object} agentInfo - Agent `/info` response
|
|
51
|
+
* @param {object} options - Selection options
|
|
52
|
+
* @param {string[]} options.supportedPaths - Supported paths in preference order
|
|
53
|
+
* @param {string[]} [options.requiredHeaders] - Headers that the proxy must forward unchanged to intake. Each
|
|
54
|
+
* header must appear in `evp_proxy_allowed_headers`. Do not include routing headers that the Agent consumes.
|
|
55
|
+
* @returns {string|undefined} Selected normalized path
|
|
56
|
+
*/
|
|
57
|
+
function selectEVPProxyPath (agentInfo, { supportedPaths, requiredHeaders = [] } = {}) {
|
|
58
|
+
if (!Array.isArray(agentInfo?.endpoints) ||
|
|
59
|
+
!Array.isArray(supportedPaths) ||
|
|
60
|
+
!Array.isArray(requiredHeaders) ||
|
|
61
|
+
requiredHeaders.some(header => typeof header !== 'string')) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const allowedHeaders = agentInfo.evp_proxy_allowed_headers
|
|
66
|
+
if (allowedHeaders !== undefined) {
|
|
67
|
+
if (!Array.isArray(allowedHeaders)) return
|
|
68
|
+
|
|
69
|
+
const normalizedHeaders = new Set()
|
|
70
|
+
for (const header of allowedHeaders) {
|
|
71
|
+
if (typeof header === 'string') {
|
|
72
|
+
normalizedHeaders.add(header.toLowerCase())
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (requiredHeaders.some(header => !normalizedHeaders.has(header.toLowerCase()))) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const advertisedPaths = new Set()
|
|
82
|
+
for (const endpoint of agentInfo.endpoints) {
|
|
83
|
+
if (typeof endpoint === 'string') {
|
|
84
|
+
advertisedPaths.add(endpoint.replace(TRAILING_SLASHES, ''))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const supportedPath of supportedPaths) {
|
|
89
|
+
if (typeof supportedPath !== 'string') continue
|
|
90
|
+
|
|
91
|
+
const normalizedPath = supportedPath.replace(TRAILING_SLASHES, '')
|
|
92
|
+
if (advertisedPaths.has(normalizedPath)) {
|
|
93
|
+
return normalizedPath
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Discovers an EVP proxy route through the configured Agent URL.
|
|
100
|
+
*
|
|
101
|
+
* This function performs discovery only when the caller invokes it. It stores
|
|
102
|
+
* no state. The Agent information client owns its existing response cache.
|
|
103
|
+
*
|
|
104
|
+
* @param {URL} url - Configured Agent URL
|
|
105
|
+
* @param {object} options - Selection options
|
|
106
|
+
* @param {string[]} options.supportedPaths - Supported paths in preference order
|
|
107
|
+
* @param {string[]} [options.requiredHeaders] - Headers that the proxy must forward unchanged to intake. Each
|
|
108
|
+
* header must appear in `evp_proxy_allowed_headers`. Do not include routing headers that the Agent consumes.
|
|
109
|
+
* @param {(error: Error|null, route?: {url: URL, basePath: string}) => void} callback - Result callback
|
|
110
|
+
* @returns {void}
|
|
111
|
+
*/
|
|
112
|
+
function discoverEVPProxy (url, options, callback) {
|
|
113
|
+
fetchAgentInfo(url, (error, agentInfo) => {
|
|
114
|
+
if (error) {
|
|
115
|
+
callback(error)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const basePath = selectEVPProxyPath(agentInfo, options)
|
|
120
|
+
if (basePath === undefined) {
|
|
121
|
+
callback(null)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
log.debug('EVP proxy route %s discovered through the configured local receiver', basePath)
|
|
126
|
+
callback(null, { url, basePath })
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
discoverEVPProxy,
|
|
132
|
+
selectEVPProxyPath,
|
|
133
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LEADING_SLASHES = /^\/+/
|
|
4
|
+
const TRAILING_SLASHES = /\/+$/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Joins a caller-supplied EVP proxy path and product endpoint.
|
|
8
|
+
*
|
|
9
|
+
* This utility does not perform EVP proxy discovery.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} basePath - EVP proxy base path
|
|
12
|
+
* @param {string} endpoint - Product intake endpoint
|
|
13
|
+
* @returns {string} Joined request path
|
|
14
|
+
*/
|
|
15
|
+
function joinEVPProxyPath (basePath, endpoint) {
|
|
16
|
+
const normalizedBasePath = basePath.replace(TRAILING_SLASHES, '')
|
|
17
|
+
const normalizedEndpoint = endpoint.replace(LEADING_SLASHES, '')
|
|
18
|
+
|
|
19
|
+
return `${normalizedBasePath}/${normalizedEndpoint}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { joinEVPProxyPath }
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const { URL } = require('url')
|
|
4
4
|
const log = require('../../log')
|
|
5
|
+
const { createServerlessDeliveryTracker } = require('../../serverless')
|
|
5
6
|
const Writer = require('./writer')
|
|
6
7
|
|
|
7
8
|
class AgentExporter {
|
|
8
9
|
#timer
|
|
10
|
+
#serverlessDeliveryTracker
|
|
9
11
|
|
|
10
12
|
constructor (config, prioritySampler) {
|
|
13
|
+
this.#serverlessDeliveryTracker = createServerlessDeliveryTracker()
|
|
11
14
|
this._config = config
|
|
12
15
|
const { lookup, protocolVersion, stats = {}, apmTracingEnabled } = config
|
|
13
16
|
this._url = config.url
|
|
@@ -23,6 +26,7 @@ class AgentExporter {
|
|
|
23
26
|
lookup,
|
|
24
27
|
protocolVersion,
|
|
25
28
|
headers,
|
|
29
|
+
deliveryTracker: this.#serverlessDeliveryTracker,
|
|
26
30
|
})
|
|
27
31
|
|
|
28
32
|
globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(this.flush.bind(this))
|
|
@@ -54,10 +58,26 @@ class AgentExporter {
|
|
|
54
58
|
}
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
flush (done
|
|
61
|
+
flush (done) {
|
|
58
62
|
clearTimeout(this.#timer)
|
|
59
63
|
this.#timer = undefined
|
|
60
|
-
|
|
64
|
+
|
|
65
|
+
if (!this.#serverlessDeliveryTracker) {
|
|
66
|
+
try {
|
|
67
|
+
return this._writer.flush(done)
|
|
68
|
+
} catch (error) {
|
|
69
|
+
log.error('Failed to flush traces: %s', error.message)
|
|
70
|
+
done?.()
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
this._writer.flush()
|
|
77
|
+
} catch (error) {
|
|
78
|
+
log.error('Failed to flush traces: %s', error.message)
|
|
79
|
+
}
|
|
80
|
+
this.#serverlessDeliveryTracker.waitForIdle(done)
|
|
61
81
|
}
|
|
62
82
|
}
|
|
63
83
|
|
|
@@ -39,18 +39,18 @@ class AgentWriter extends BaseWriter {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Performs the writer flush without registering a serverless delivery.
|
|
43
|
+
* Test Optimization owns its own request lifecycle tracking.
|
|
44
44
|
* @param {(error?: Error) => void} [done]
|
|
45
45
|
* @param {{ deadline?: number }} [options]
|
|
46
46
|
* @returns {void}
|
|
47
47
|
*/
|
|
48
|
-
|
|
48
|
+
flushDirect (done, options) {
|
|
49
49
|
if (this.#requestTracker) {
|
|
50
50
|
this.#requestTracker.flush(done, options)
|
|
51
51
|
return
|
|
52
52
|
}
|
|
53
|
-
super.
|
|
53
|
+
super.flushDirect(done, options)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
_sendPayload (data, count, done, flushOptions) {
|
|
@@ -93,7 +93,10 @@ function request (data, options, callback) {
|
|
|
93
93
|
|
|
94
94
|
docker.inject(options.headers)
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
const connectionOptions = {
|
|
97
|
+
...options,
|
|
98
|
+
agent: options.agent ?? (isSecure ? httpsAgent : httpAgent),
|
|
99
|
+
}
|
|
97
100
|
|
|
98
101
|
/**
|
|
99
102
|
* @param {import('node:http').IncomingMessage} res
|
|
@@ -218,7 +221,7 @@ function request (data, options, callback) {
|
|
|
218
221
|
}
|
|
219
222
|
}
|
|
220
223
|
|
|
221
|
-
const req = client.request(
|
|
224
|
+
const req = client.request(connectionOptions, (res) => onResponse(res, complete, handleError))
|
|
222
225
|
|
|
223
226
|
req.once('close', finalize)
|
|
224
227
|
req.once('timeout', finalize)
|
|
@@ -10,14 +10,36 @@ const { safeJSONStringify } = require('./util')
|
|
|
10
10
|
const firstFlushChannel = channel('dd-trace:exporter:first-flush')
|
|
11
11
|
|
|
12
12
|
class Writer {
|
|
13
|
-
|
|
13
|
+
#deliveryTracker
|
|
14
|
+
|
|
15
|
+
constructor ({ url, beforeFirstFlush, deliveryTracker }) {
|
|
14
16
|
this._url = url
|
|
15
17
|
this._beforeFirstFlush = beforeFirstFlush
|
|
18
|
+
this.#deliveryTracker = deliveryTracker
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
#isFirstFlush = true
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Flushes queued telemetry, retaining delivery on supported serverless platforms.
|
|
25
|
+
* @param {(error?: Error) => void} [done]
|
|
26
|
+
* @param {{ deadline?: number }} [options]
|
|
27
|
+
* @returns {void}
|
|
28
|
+
*/
|
|
29
|
+
flush (done, options) {
|
|
30
|
+
if (this.#deliveryTracker) {
|
|
31
|
+
return this.#deliveryTracker.track(callback => this.flushDirect(callback, options), done)
|
|
32
|
+
}
|
|
33
|
+
this.flushDirect(done, options)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Flushes queued telemetry without registering serverless delivery retention.
|
|
38
|
+
* @param {(error?: Error) => void} [done]
|
|
39
|
+
* @param {{ deadline?: number }} [options]
|
|
40
|
+
* @returns {void}
|
|
41
|
+
*/
|
|
42
|
+
flushDirect (done = () => {}, options) {
|
|
21
43
|
const count = this._encoder.count()
|
|
22
44
|
|
|
23
45
|
if (!request.writable && options?.deadline === undefined) {
|
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../../log')
|
|
4
|
+
const { createServerlessDeliveryTracker } = require('../../serverless')
|
|
3
5
|
const { Writer } = require('./writer')
|
|
4
6
|
|
|
5
7
|
class SpanStatsExporter {
|
|
8
|
+
#serverlessDeliveryTracker
|
|
9
|
+
|
|
6
10
|
constructor (config) {
|
|
11
|
+
this.#serverlessDeliveryTracker = createServerlessDeliveryTracker()
|
|
7
12
|
this._url = config.url
|
|
8
|
-
this._writer = new Writer({
|
|
13
|
+
this._writer = new Writer({
|
|
14
|
+
url: this._url,
|
|
15
|
+
deliveryTracker: this.#serverlessDeliveryTracker,
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export (payload, done) {
|
|
20
|
+
try {
|
|
21
|
+
this._writer.append(payload)
|
|
22
|
+
this._writer.flush(this.#serverlessDeliveryTracker ? undefined : done)
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (!done) throw error
|
|
25
|
+
log.error('Failed to flush span stats: %s', error.message)
|
|
26
|
+
}
|
|
27
|
+
this.#serverlessDeliveryTracker?.waitForIdle(done)
|
|
9
28
|
}
|
|
10
29
|
|
|
11
|
-
|
|
12
|
-
this._writer.
|
|
13
|
-
this
|
|
30
|
+
flush (done) {
|
|
31
|
+
this._writer.flush(this.#serverlessDeliveryTracker ? undefined : done)
|
|
32
|
+
this.#serverlessDeliveryTracker?.waitForIdle(done)
|
|
14
33
|
}
|
|
15
34
|
}
|
|
16
35
|
|
|
@@ -33,11 +33,12 @@ class ExternalLogger {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
static tagString (tags) {
|
|
36
|
-
|
|
36
|
+
let tagString = ''
|
|
37
37
|
for (const key in tags) {
|
|
38
|
-
|
|
38
|
+
if (tagString) tagString += ','
|
|
39
|
+
tagString += key + ':' + tags[key]
|
|
39
40
|
}
|
|
40
|
-
return
|
|
41
|
+
return tagString
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
// Parses and enqueues a log
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('./log')
|
|
4
|
+
const { supportsServerlessTelemetryRetention } = require('./serverless')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {(done: () => void) => void} TelemetryFlusher
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** @type {Set<TelemetryFlusher>} */
|
|
11
|
+
const telemetryFlushers = new Set()
|
|
12
|
+
const postTraceTelemetryFlushers = new Set()
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {{
|
|
16
|
+
* trace?: TelemetryFlusher,
|
|
17
|
+
* spanStats?: TelemetryFlusher
|
|
18
|
+
* }} TraceFlushers
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Registers a configured telemetry pipeline so serverless lifecycle retention
|
|
23
|
+
* waits for its final export alongside trace delivery.
|
|
24
|
+
* @param {TelemetryFlusher} flusher
|
|
25
|
+
* @param {{ afterTrace?: boolean }} [options]
|
|
26
|
+
* @returns {() => void} Removes this pipeline when its provider is replaced.
|
|
27
|
+
*/
|
|
28
|
+
function registerTelemetryFlusher (flusher, options) {
|
|
29
|
+
if (!supportsServerlessTelemetryRetention()) return () => {}
|
|
30
|
+
|
|
31
|
+
const flushers = options?.afterTrace ? postTraceTelemetryFlushers : telemetryFlushers
|
|
32
|
+
flushers.add(flusher)
|
|
33
|
+
// Avoid retaining a replaced provider or flushing it alongside the new one.
|
|
34
|
+
return () => flushers.delete(flusher)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Coordinates the configured telemetry flushers for a serverless lifecycle.
|
|
39
|
+
*
|
|
40
|
+
* Trace-owned flushers are supplied by DatadogTracer so this module does not
|
|
41
|
+
* depend on its private implementation details.
|
|
42
|
+
* @param {() => void} [done]
|
|
43
|
+
* @param {{ timeout?: number }} [options]
|
|
44
|
+
* @param {TraceFlushers} [traceFlushers]
|
|
45
|
+
*/
|
|
46
|
+
function flushServerlessTelemetry (done, options, traceFlushers = {}) {
|
|
47
|
+
const { trace: traceFlusher, spanStats: spanStatsFlusher } = traceFlushers
|
|
48
|
+
// TODO: Include DSM after DataStreamsProcessor exposes a completion-aware flush API.
|
|
49
|
+
let pending = telemetryFlushers.size + postTraceTelemetryFlushers.size +
|
|
50
|
+
(typeof traceFlusher === 'function' ? 1 : 0) +
|
|
51
|
+
(typeof spanStatsFlusher === 'function' ? 1 : 0)
|
|
52
|
+
let completed = false
|
|
53
|
+
let timeout
|
|
54
|
+
|
|
55
|
+
const finish = () => {
|
|
56
|
+
if (completed) return
|
|
57
|
+
completed = true
|
|
58
|
+
clearTimeout(timeout)
|
|
59
|
+
done?.()
|
|
60
|
+
}
|
|
61
|
+
const complete = () => {
|
|
62
|
+
if (--pending === 0) finish()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (pending === 0) return finish()
|
|
66
|
+
if (options?.timeout) {
|
|
67
|
+
timeout = setTimeout(() => {
|
|
68
|
+
log.warn('Timed out waiting for telemetry flush after %dms', options.timeout)
|
|
69
|
+
finish()
|
|
70
|
+
}, options.timeout)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const flush = (flusher, afterFlushed) => {
|
|
74
|
+
let flushed = false
|
|
75
|
+
const onFlushed = error => {
|
|
76
|
+
if (flushed) return
|
|
77
|
+
flushed = true
|
|
78
|
+
if (error) log.error('Error flushing telemetry pipeline:', error)
|
|
79
|
+
afterFlushed?.()
|
|
80
|
+
complete()
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
flusher(onFlushed)
|
|
84
|
+
} catch (error) {
|
|
85
|
+
onFlushed(error)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (typeof traceFlusher === 'function') {
|
|
90
|
+
flush(traceFlusher, () => {
|
|
91
|
+
for (const flusher of postTraceTelemetryFlushers) flush(flusher)
|
|
92
|
+
})
|
|
93
|
+
} else {
|
|
94
|
+
for (const flusher of postTraceTelemetryFlushers) flush(flusher)
|
|
95
|
+
}
|
|
96
|
+
if (typeof spanStatsFlusher === 'function') {
|
|
97
|
+
flush(spanStatsFlusher)
|
|
98
|
+
}
|
|
99
|
+
for (const flusher of telemetryFlushers) flush(flusher)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { flushServerlessTelemetry, registerTelemetryFlusher }
|
|
@@ -33,7 +33,8 @@ function datasetRecordFromResource (resource) {
|
|
|
33
33
|
attrs.input ?? null,
|
|
34
34
|
attrs.expected_output ?? null,
|
|
35
35
|
attrs.metadata ?? {},
|
|
36
|
-
id
|
|
36
|
+
id,
|
|
37
|
+
attrs.tags ?? []
|
|
37
38
|
)
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -107,6 +108,10 @@ class ExperimentsClient {
|
|
|
107
108
|
return this.#site
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
get projectName () {
|
|
112
|
+
return this.#projectName
|
|
113
|
+
}
|
|
114
|
+
|
|
110
115
|
// Dashboard URL base for the configured site, e.g. https://app.datadoghq.com
|
|
111
116
|
get appBase () {
|
|
112
117
|
return `https://${appHost(this.#site)}`
|
|
@@ -195,7 +200,7 @@ class ExperimentsClient {
|
|
|
195
200
|
const resources = Array.isArray(response?.records)
|
|
196
201
|
? response.records
|
|
197
202
|
: (Array.isArray(response?.data) ? response.data : [])
|
|
198
|
-
return resources
|
|
203
|
+
return datasetMutationResultFromResources(resources)
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
async batchUpdateDatasetRecords (projectId, datasetId, attributes) {
|
|
@@ -226,6 +231,9 @@ class ExperimentsClient {
|
|
|
226
231
|
const query = new URLSearchParams()
|
|
227
232
|
if (options.cursor) query.set('page[cursor]', options.cursor)
|
|
228
233
|
if (options.version !== undefined && options.version !== null) query.set('filter[version]', String(options.version))
|
|
234
|
+
if (Array.isArray(options.tags)) {
|
|
235
|
+
for (const tag of options.tags) query.append('filter[tags]', tag)
|
|
236
|
+
}
|
|
229
237
|
const response = await this.request(
|
|
230
238
|
'GET',
|
|
231
239
|
`${API_BASE_PATH}/${projectId}/datasets/${datasetId}/records?${query.toString()}`
|