dd-trace 6.12.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/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 +127 -26
- package/package.json +10 -10
- package/packages/datadog-instrumentations/src/bunyan.js +4 -2
- 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/pino.js +15 -4
- package/packages/datadog-instrumentations/src/playwright.js +34 -20
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -4
- 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 +9 -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/rule_manager.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
- package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
- 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/devtools_client/send.js +4 -3
- 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/exporters/agent/index.js +22 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
- 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 +4 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +51 -2
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
- package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
- package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
- 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/writers/exposures.js +8 -1
- 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 +410 -342
- 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/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
|
@@ -8,6 +8,8 @@ const request = require('./exporters/common/request')
|
|
|
8
8
|
const log = require('./log')
|
|
9
9
|
const Histogram = require('./histogram')
|
|
10
10
|
const { entityId } = require('./exporters/common/docker')
|
|
11
|
+
const { registerTelemetryFlusher } = require('./flush')
|
|
12
|
+
const { createServerlessDeliveryTracker } = require('./serverless')
|
|
11
13
|
|
|
12
14
|
const legacyStorage = storage('legacy')
|
|
13
15
|
|
|
@@ -25,6 +27,8 @@ const TYPE_HISTOGRAM = 'h'
|
|
|
25
27
|
class DogStatsDClient {
|
|
26
28
|
#lookup
|
|
27
29
|
#tagsPrefix
|
|
30
|
+
#serverlessDeliveryTracker
|
|
31
|
+
|
|
28
32
|
constructor (options) {
|
|
29
33
|
this.#lookup = options.lookup
|
|
30
34
|
if (options.metricsProxyUrl) {
|
|
@@ -41,6 +45,7 @@ class DogStatsDClient {
|
|
|
41
45
|
this._tags = options.tags
|
|
42
46
|
this.#tagsPrefix = this._tags?.length ? `|#${this._tags.join(',')}` : ''
|
|
43
47
|
this._queue = []
|
|
48
|
+
this.#serverlessDeliveryTracker = createServerlessDeliveryTracker()
|
|
44
49
|
this._buffer = ''
|
|
45
50
|
this._offset = 0
|
|
46
51
|
this._udp4 = this._socket('udp4')
|
|
@@ -67,23 +72,30 @@ class DogStatsDClient {
|
|
|
67
72
|
this._add(stat, value, TYPE_HISTOGRAM, tags)
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
flush () {
|
|
75
|
+
flush (done) {
|
|
71
76
|
const queue = this._enqueue()
|
|
72
77
|
|
|
73
|
-
if (queue.length === 0)
|
|
78
|
+
if (queue.length === 0) {
|
|
79
|
+
if (this.#serverlessDeliveryTracker) return this.#serverlessDeliveryTracker.waitForIdle(done)
|
|
80
|
+
return done?.()
|
|
81
|
+
}
|
|
74
82
|
|
|
75
83
|
log.debug('Flushing %s metrics via %s', queue.length, this._httpOptions ? 'HTTP' : 'UDP')
|
|
76
84
|
|
|
77
85
|
this._queue = []
|
|
78
86
|
|
|
79
|
-
|
|
80
|
-
this._sendHttp(queue)
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
const send = complete => {
|
|
88
|
+
if (this._httpOptions) this._sendHttp(queue, complete)
|
|
89
|
+
else this._sendUdp(queue, complete)
|
|
90
|
+
}
|
|
91
|
+
if (this.#serverlessDeliveryTracker) {
|
|
92
|
+
this.#serverlessDeliveryTracker.track(send)
|
|
93
|
+
return this.#serverlessDeliveryTracker.waitForIdle(done)
|
|
83
94
|
}
|
|
95
|
+
send(done)
|
|
84
96
|
}
|
|
85
97
|
|
|
86
|
-
_sendHttp (queue) {
|
|
98
|
+
_sendHttp (queue, done) {
|
|
87
99
|
const buffer = Buffer.concat(queue)
|
|
88
100
|
request(buffer, this._httpOptions, (err) => {
|
|
89
101
|
if (err) {
|
|
@@ -95,32 +107,46 @@ class DogStatsDClient {
|
|
|
95
107
|
// options. Either way, we can give UDP a try.
|
|
96
108
|
this._httpOptions = undefined
|
|
97
109
|
}
|
|
98
|
-
this._sendUdp(queue)
|
|
110
|
+
this._sendUdp(queue, done)
|
|
111
|
+
} else {
|
|
112
|
+
done?.()
|
|
99
113
|
}
|
|
100
114
|
})
|
|
101
115
|
}
|
|
102
116
|
|
|
103
|
-
_sendUdp (queue) {
|
|
117
|
+
_sendUdp (queue, done) {
|
|
104
118
|
// dgram resolves the local address via the instrumented dns.lookup when it
|
|
105
119
|
// binds on first send; the noop store keeps that self-traffic off the trace.
|
|
106
120
|
legacyStorage.run({ noop: true }, () => {
|
|
107
121
|
if (this._family === 0) {
|
|
108
122
|
this.#lookup(this._host, (error, address, family) => {
|
|
109
|
-
if (error)
|
|
110
|
-
|
|
123
|
+
if (error) {
|
|
124
|
+
log.error('DogStatsDClient: Host not found', error)
|
|
125
|
+
return done?.()
|
|
126
|
+
}
|
|
127
|
+
this._sendUdpFromQueue(queue, address, family, done)
|
|
111
128
|
})
|
|
112
129
|
} else {
|
|
113
|
-
this._sendUdpFromQueue(queue, this._host, this._family)
|
|
130
|
+
this._sendUdpFromQueue(queue, this._host, this._family, done)
|
|
114
131
|
}
|
|
115
132
|
})
|
|
116
133
|
}
|
|
117
134
|
|
|
118
|
-
_sendUdpFromQueue (queue, address, family) {
|
|
135
|
+
_sendUdpFromQueue (queue, address, family, done) {
|
|
119
136
|
const socket = family === 6 ? this._udp6 : this._udp4
|
|
137
|
+
let pending = queue.length
|
|
138
|
+
const complete = () => {
|
|
139
|
+
if (--pending === 0) done?.()
|
|
140
|
+
}
|
|
120
141
|
|
|
121
142
|
for (const buffer of queue) {
|
|
122
143
|
log.debug('Sending to DogStatsD: %s', buffer)
|
|
123
|
-
|
|
144
|
+
try {
|
|
145
|
+
socket.send(buffer, 0, buffer.length, this._port, address, complete)
|
|
146
|
+
} catch (error) {
|
|
147
|
+
log.error('DogStatsDClient: UDP error sending metrics', error)
|
|
148
|
+
complete()
|
|
149
|
+
}
|
|
124
150
|
}
|
|
125
151
|
}
|
|
126
152
|
|
|
@@ -212,12 +238,12 @@ class MetricsAggregationClient {
|
|
|
212
238
|
this.reset()
|
|
213
239
|
}
|
|
214
240
|
|
|
215
|
-
flush () {
|
|
241
|
+
flush (done) {
|
|
216
242
|
this._captureCounters()
|
|
217
243
|
this._captureGauges()
|
|
218
244
|
this._captureHistograms()
|
|
219
245
|
|
|
220
|
-
this._client.flush()
|
|
246
|
+
this._client.flush(done)
|
|
221
247
|
}
|
|
222
248
|
|
|
223
249
|
reset () {
|
|
@@ -370,6 +396,7 @@ class CustomMetrics {
|
|
|
370
396
|
setInterval(flush, 10 * 1000).unref?.()
|
|
371
397
|
|
|
372
398
|
globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(flush)
|
|
399
|
+
registerTelemetryFlusher(done => this.flush(done))
|
|
373
400
|
}
|
|
374
401
|
|
|
375
402
|
increment (stat, value = 1, tags) {
|
|
@@ -392,8 +419,8 @@ class CustomMetrics {
|
|
|
392
419
|
this.#client.histogram(stat, value, CustomMetrics.tagTranslator(tags))
|
|
393
420
|
}
|
|
394
421
|
|
|
395
|
-
flush () {
|
|
396
|
-
return this.#client.flush()
|
|
422
|
+
flush (done) {
|
|
423
|
+
return this.#client.flush(done)
|
|
397
424
|
}
|
|
398
425
|
|
|
399
426
|
/**
|
|
@@ -167,17 +167,18 @@ class AgentlessJSONEncoder {
|
|
|
167
167
|
const metadataPrefix = metadataJson.slice(0, -1)
|
|
168
168
|
const hasMetadata = metadataPrefix.length > 1 // more than just '{'
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
let tracesJson = ''
|
|
171
171
|
for (const spanStrings of this._traces) {
|
|
172
172
|
const spansJson = '[' + spanStrings.join(',') + ']'
|
|
173
|
+
if (tracesJson) tracesJson += ','
|
|
173
174
|
if (hasMetadata) {
|
|
174
|
-
|
|
175
|
+
tracesJson += metadataPrefix + ',"spans":' + spansJson + '}'
|
|
175
176
|
} else {
|
|
176
|
-
|
|
177
|
+
tracesJson += '{"spans":' + spansJson + '}'
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
const payload = '{"traces":[' +
|
|
181
|
+
const payload = '{"traces":[' + tracesJson + ']}'
|
|
181
182
|
this._reset()
|
|
182
183
|
return Buffer.from(payload, 'utf8')
|
|
183
184
|
} catch (err) {
|
|
@@ -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) {
|
|
@@ -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 }
|
|
@@ -108,6 +108,10 @@ class ExperimentsClient {
|
|
|
108
108
|
return this.#site
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
get projectName () {
|
|
112
|
+
return this.#projectName
|
|
113
|
+
}
|
|
114
|
+
|
|
111
115
|
// Dashboard URL base for the configured site, e.g. https://app.datadoghq.com
|
|
112
116
|
get appBase () {
|
|
113
117
|
return `https://${appHost(this.#site)}`
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { randomUUID } = require('node:crypto')
|
|
4
|
+
const snapshotPayload = require('../../../../../vendor/dist/rfdc')({ proto: false, circles: false })
|
|
4
5
|
|
|
5
6
|
/** @typedef {{add?: string[], remove?: string[], replace?: string[]}} TagOperations */
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} DatasetRecordNew
|
|
9
|
+
* @property {string} [id]
|
|
10
|
+
* @property {unknown} inputData
|
|
11
|
+
* @property {unknown} [expectedOutput]
|
|
12
|
+
* @property {Record<string, unknown>} [metadata]
|
|
13
|
+
* @property {string[]} [tags]
|
|
14
|
+
*/
|
|
6
15
|
/**
|
|
7
16
|
* @typedef {object} PendingBatch
|
|
8
17
|
* @property {object} attributes
|
|
@@ -114,6 +123,9 @@ function updateFromInsertedRecord (recordId, record, payload) {
|
|
|
114
123
|
update.expectedOutput = record.expectedOutput
|
|
115
124
|
}
|
|
116
125
|
if (!valuesAreEqual(record.metadata, payload.metadata)) update.metadata = record.metadata
|
|
126
|
+
if (!valuesAreEqual(record.tags, payload.tags ?? [])) {
|
|
127
|
+
update.tagOperations = { replace: [...record.tags] }
|
|
128
|
+
}
|
|
117
129
|
return update
|
|
118
130
|
}
|
|
119
131
|
|
|
@@ -173,6 +185,36 @@ class Dataset {
|
|
|
173
185
|
return this
|
|
174
186
|
}
|
|
175
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Add multiple records to a dataset.
|
|
190
|
+
* @param {DatasetRecordNew[]} records
|
|
191
|
+
* @returns {Dataset} This dataset for chaining.
|
|
192
|
+
*/
|
|
193
|
+
addRecords (records) {
|
|
194
|
+
const newRecords = []
|
|
195
|
+
const recordIds = new Set(this.#recordsById.keys())
|
|
196
|
+
|
|
197
|
+
// Construct and validate the entire batch before mutating the dataset.
|
|
198
|
+
for (const record of records) {
|
|
199
|
+
if (record.id !== undefined && (typeof record.id !== 'string' || record.id.length === 0)) {
|
|
200
|
+
throw new Error('record id must be a non-empty string')
|
|
201
|
+
}
|
|
202
|
+
const newRecord = new DatasetRecord(
|
|
203
|
+
record.inputData,
|
|
204
|
+
record.expectedOutput,
|
|
205
|
+
record.metadata,
|
|
206
|
+
record.id,
|
|
207
|
+
record.tags
|
|
208
|
+
)
|
|
209
|
+
if (recordIds.has(newRecord.id)) throw new Error(`Duplicate record id '${newRecord.id}'`)
|
|
210
|
+
recordIds.add(newRecord.id)
|
|
211
|
+
newRecords.push(newRecord)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const record of newRecords) this.#addRecord(record)
|
|
215
|
+
return this
|
|
216
|
+
}
|
|
217
|
+
|
|
176
218
|
/**
|
|
177
219
|
* Add tags to a dataset record.
|
|
178
220
|
* @param {number} index Dataset record index.
|
|
@@ -336,6 +378,10 @@ class Dataset {
|
|
|
336
378
|
return this.#projectId
|
|
337
379
|
}
|
|
338
380
|
|
|
381
|
+
projectName () {
|
|
382
|
+
return this.#client.projectName
|
|
383
|
+
}
|
|
384
|
+
|
|
339
385
|
version () {
|
|
340
386
|
return this.#version
|
|
341
387
|
}
|
|
@@ -416,7 +462,7 @@ class Dataset {
|
|
|
416
462
|
const insertRecords = []
|
|
417
463
|
const insertPayloads = new Map()
|
|
418
464
|
for (const [recordId, record] of this.#newRecordsById) {
|
|
419
|
-
const payload = serializedRecord(record)
|
|
465
|
+
const payload = snapshotPayload(serializedRecord(record))
|
|
420
466
|
insertRecords.push(payload)
|
|
421
467
|
insertPayloads.set(recordId, payload)
|
|
422
468
|
}
|
|
@@ -427,7 +473,7 @@ class Dataset {
|
|
|
427
473
|
const tagOperations = this.#pendingTagOperations.get(recordId)
|
|
428
474
|
if (tagOperations) update.tagOperations = tagOperations
|
|
429
475
|
else delete update.tagOperations
|
|
430
|
-
const payload = serializedRecordUpdate(update)
|
|
476
|
+
const payload = snapshotPayload(serializedRecordUpdate(update))
|
|
431
477
|
updateRecords.push(payload)
|
|
432
478
|
updatePayloads.set(recordId, payload)
|
|
433
479
|
}
|
|
@@ -522,6 +568,9 @@ class Dataset {
|
|
|
522
568
|
updateFromInsertedRecord(recordId, current, payload)
|
|
523
569
|
const queuedOperations = this.#pendingTagOperations.get(recordId)
|
|
524
570
|
if (queuedOperations) update.tagOperations = queuedOperations
|
|
571
|
+
if (update.tagOperations) {
|
|
572
|
+
this.#pendingTagOperations.set(recordId, copyTagOperations(update.tagOperations))
|
|
573
|
+
}
|
|
525
574
|
this.#updatedRecordsById.set(recordId, update)
|
|
526
575
|
}
|
|
527
576
|
|