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.
Files changed (139) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  3. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  4. package/index.d.ts +143 -25
  5. package/package.json +26 -25
  6. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  7. package/packages/datadog-instrumentations/src/cucumber.js +4 -4
  8. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  9. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +1 -0
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +28 -0
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  12. package/packages/datadog-instrumentations/src/jest.js +140 -18
  13. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  14. package/packages/datadog-instrumentations/src/mariadb.js +7 -0
  15. package/packages/datadog-instrumentations/src/mocha/utils.js +5 -2
  16. package/packages/datadog-instrumentations/src/pino.js +15 -4
  17. package/packages/datadog-instrumentations/src/playwright-reporter.js +149 -1
  18. package/packages/datadog-instrumentations/src/playwright.js +67 -53
  19. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +13 -12
  20. package/packages/datadog-instrumentations/src/vitest-worker.js +11 -0
  21. package/packages/datadog-instrumentations/src/webdriverio.js +26 -0
  22. package/packages/datadog-instrumentations/src/winston.js +6 -3
  23. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  24. package/packages/datadog-plugin-http/src/client.js +2 -15
  25. package/packages/datadog-plugin-http2/src/client.js +2 -15
  26. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  27. package/packages/datadog-plugin-playwright/src/index.js +13 -1
  28. package/packages/datadog-plugin-undici/src/index.js +2 -15
  29. package/packages/dd-trace/src/aiguard/evaluation.js +94 -20
  30. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  31. package/packages/dd-trace/src/aiguard/messages/anthropic.js +21 -9
  32. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  33. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  34. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  35. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  36. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  37. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  38. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  39. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  40. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  41. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  42. package/packages/dd-trace/src/carrier.js +107 -48
  43. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  44. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +5 -3
  46. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
  47. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -2
  48. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
  49. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  50. package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
  51. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +5 -3
  52. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
  53. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  54. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  55. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +4 -2
  56. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  57. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -0
  58. package/packages/dd-trace/src/config/index.js +2 -1
  59. package/packages/dd-trace/src/config/supported-configurations.json +27 -0
  60. package/packages/dd-trace/src/debugger/constants.js +1 -0
  61. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  62. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -10
  63. package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -3
  64. package/packages/dd-trace/src/debugger/index.js +4 -2
  65. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  66. package/packages/dd-trace/src/dogstatsd.js +45 -18
  67. package/packages/dd-trace/src/encode/agentless-json.js +5 -4
  68. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  69. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  70. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  71. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  72. package/packages/dd-trace/src/exporters/agent/index.js +22 -2
  73. package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
  74. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  75. package/packages/dd-trace/src/exporters/common/writer.js +24 -2
  76. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  77. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  78. package/packages/dd-trace/src/flush.js +102 -0
  79. package/packages/dd-trace/src/llmobs/experiments/client.js +10 -2
  80. package/packages/dd-trace/src/llmobs/experiments/dataset.js +301 -14
  81. package/packages/dd-trace/src/llmobs/experiments/experiment.js +440 -112
  82. package/packages/dd-trace/src/llmobs/experiments/index.js +68 -44
  83. package/packages/dd-trace/src/llmobs/experiments/noop.js +103 -12
  84. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  85. package/packages/dd-trace/src/llmobs/experiments/util.js +81 -4
  86. package/packages/dd-trace/src/llmobs/index.js +80 -17
  87. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  88. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  89. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  90. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  91. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  92. package/packages/dd-trace/src/llmobs/writers/base.js +60 -24
  93. package/packages/dd-trace/src/noop/span.js +5 -0
  94. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  95. package/packages/dd-trace/src/openfeature/index.js +7 -4
  96. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  97. package/packages/dd-trace/src/openfeature/writers/exposures.js +70 -15
  98. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  99. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  100. package/packages/dd-trace/src/opentelemetry/logs/index.js +4 -1
  101. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  102. package/packages/dd-trace/src/opentelemetry/metrics/index.js +3 -1
  103. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  104. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +4 -2
  105. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +4 -2
  106. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  107. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  108. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  109. package/packages/dd-trace/src/opentracing/propagation/text_map.js +429 -352
  110. package/packages/dd-trace/src/opentracing/span.js +28 -0
  111. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  112. package/packages/dd-trace/src/plugin_manager.js +2 -0
  113. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  114. package/packages/dd-trace/src/plugins/util/test.js +74 -21
  115. package/packages/dd-trace/src/plugins/util/web.js +2 -65
  116. package/packages/dd-trace/src/profiling/oom.js +4 -3
  117. package/packages/dd-trace/src/profiling/profiler.js +7 -0
  118. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  119. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  120. package/packages/dd-trace/src/proxy.js +36 -8
  121. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  122. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -0
  123. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -6
  124. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +55 -0
  125. package/packages/dd-trace/src/serverless/vercel.js +122 -0
  126. package/packages/dd-trace/src/serverless.js +37 -20
  127. package/packages/dd-trace/src/span_stats.js +40 -3
  128. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  129. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  130. package/packages/dd-trace/src/tracer.js +22 -0
  131. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  132. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  133. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  134. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  135. package/vendor/dist/https-proxy-agent/index.js +4 -0
  136. package/vendor/dist/protobufjs/index.js +1 -1
  137. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  138. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  139. 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
- this._writer.flush(done)
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
- * Flushes payloads, including requests already in flight during Test Optimization finalization.
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
- flush (done, options) {
48
+ flushDirect (done, options) {
49
49
  if (this.#requestTracker) {
50
50
  this.#requestTracker.flush(done, options)
51
51
  return
52
52
  }
53
- super.flush(done, options)
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
- options.agent = isSecure ? httpsAgent : httpAgent
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(options, (res) => onResponse(res, complete, handleError))
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
- constructor ({ url, beforeFirstFlush }) {
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
- flush (done = () => {}, options) {
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({ url: this._url })
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
- export (payload) {
12
- this._writer.append(payload)
13
- this._writer.flush()
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
- const tagArray = []
36
+ let tagString = ''
37
37
  for (const key in tags) {
38
- tagArray.push(key + ':' + tags[key])
38
+ if (tagString) tagString += ','
39
+ tagString += key + ':' + tags[key]
39
40
  }
40
- return tagArray.join(',')
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.map(datasetRecordFromResource)
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()}`