dd-trace 5.123.0 → 5.124.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 +128 -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
|
@@ -4,6 +4,7 @@ const { URL, format } = require('node:url')
|
|
|
4
4
|
const path = require('node:path')
|
|
5
5
|
const request = require('../../exporters/common/request')
|
|
6
6
|
const { getEnvironmentVariable } = require('../../config/helper')
|
|
7
|
+
const { createServerlessDeliveryTracker } = require('../../serverless')
|
|
7
8
|
|
|
8
9
|
const logger = require('../../log')
|
|
9
10
|
|
|
@@ -33,6 +34,9 @@ class LLMObsBuffer {
|
|
|
33
34
|
|
|
34
35
|
class BaseLLMObsWriter {
|
|
35
36
|
#destroyer
|
|
37
|
+
/** @type {Function[]} */
|
|
38
|
+
#pendingFlushes = []
|
|
39
|
+
#serverlessDeliveryTracker = createServerlessDeliveryTracker()
|
|
36
40
|
/** @type {Map<string, LLMObsBuffer>} */
|
|
37
41
|
#multiTenantBuffers = new Map()
|
|
38
42
|
|
|
@@ -111,35 +115,52 @@ class BaseLLMObsWriter {
|
|
|
111
115
|
return true
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Drains buffered events and joins requests active at this flush boundary.
|
|
120
|
+
* @param {Function} [done]
|
|
121
|
+
*/
|
|
122
|
+
flush (done) {
|
|
115
123
|
if (this._agentless == null) {
|
|
124
|
+
if (done) this.#pendingFlushes.push(done)
|
|
116
125
|
return
|
|
117
126
|
}
|
|
118
127
|
|
|
119
|
-
|
|
120
|
-
if (this
|
|
121
|
-
|
|
122
|
-
this
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
const requests = this.#drainBuffers()
|
|
129
|
+
if (!this.#serverlessDeliveryTracker) {
|
|
130
|
+
// Only invocation-retaining platforms need completion-aware delivery.
|
|
131
|
+
for (const request of requests) this.#sendSafely(request)
|
|
132
|
+
done?.()
|
|
133
|
+
return
|
|
134
|
+
}
|
|
125
135
|
|
|
126
|
-
|
|
136
|
+
for (const request of requests) this.#sendSafely(request)
|
|
137
|
+
this.#serverlessDeliveryTracker.waitForIdle(done)
|
|
138
|
+
}
|
|
127
139
|
|
|
128
|
-
|
|
140
|
+
#sendSafely (requestToSend) {
|
|
141
|
+
try {
|
|
142
|
+
if (this.#serverlessDeliveryTracker) {
|
|
143
|
+
this.#serverlessDeliveryTracker.track(done => this.#send(requestToSend, done))
|
|
144
|
+
} else {
|
|
145
|
+
this.#send(requestToSend)
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logger.error('Failed to send LLMObs %s events: %s', this._eventType, error.message)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
129
151
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
152
|
+
#drainBuffers () {
|
|
153
|
+
const requests = []
|
|
154
|
+
if (this._buffer.events.length > 0) {
|
|
155
|
+
const events = this._buffer.events
|
|
156
|
+
this._buffer.clear()
|
|
157
|
+
requests.push({ events, options: this._getOptions(), url: this.url })
|
|
133
158
|
}
|
|
134
159
|
|
|
135
|
-
// Flush multi-tenant buffers
|
|
136
160
|
for (const [apiKey, buffer] of this.#multiTenantBuffers) {
|
|
137
161
|
if (buffer.events.length === 0) continue
|
|
138
|
-
|
|
139
162
|
const events = buffer.events
|
|
140
163
|
buffer.clear()
|
|
141
|
-
|
|
142
|
-
const payload = this._encode(this.makePayload(events))
|
|
143
164
|
const site = buffer.routing.site || this._config.site
|
|
144
165
|
const options = {
|
|
145
166
|
headers: {
|
|
@@ -156,16 +177,21 @@ class BaseLLMObsWriter {
|
|
|
156
177
|
}
|
|
157
178
|
const url = this.#buildUrl(options.url.href, options.path)
|
|
158
179
|
const maskedApiKey = apiKey ? `****${apiKey.slice(-4)}` : ''
|
|
159
|
-
|
|
160
180
|
log.debug('Encoding and flushing multi-tenant buffer for %s', maskedApiKey)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
request(payload, options, (err, resp, code) => {
|
|
164
|
-
parseResponseAndLog(err, code, events.length, url, this._eventType)
|
|
165
|
-
})
|
|
181
|
+
requests.push({ events, options, url })
|
|
166
182
|
}
|
|
167
183
|
|
|
168
184
|
this.#cleanupEmptyBuffers()
|
|
185
|
+
return requests
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#send ({ events, options, url }, done) {
|
|
189
|
+
const payload = this._encode(this.makePayload(events))
|
|
190
|
+
log.debug('Encoded LLMObs payload: %s', payload)
|
|
191
|
+
request(payload, options, (err, resp, code) => {
|
|
192
|
+
parseResponseAndLog(err, code, events.length, url, this._eventType)
|
|
193
|
+
done?.()
|
|
194
|
+
})
|
|
169
195
|
}
|
|
170
196
|
|
|
171
197
|
#cleanupEmptyBuffers () {
|
|
@@ -178,13 +204,19 @@ class BaseLLMObsWriter {
|
|
|
178
204
|
|
|
179
205
|
makePayload (events) {}
|
|
180
206
|
|
|
181
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Stops periodic flushing and drains buffered events.
|
|
209
|
+
* @param {Function} [done] Called after queued and active deliveries complete.
|
|
210
|
+
*/
|
|
211
|
+
destroy (done) {
|
|
182
212
|
if (this.#destroyer) {
|
|
183
213
|
logger.debug(`Stopping ${this.constructor.name}`)
|
|
184
214
|
clearInterval(this._periodic)
|
|
185
215
|
globalThis[Symbol.for('dd-trace')].beforeExitHandlers.delete(this.#destroyer)
|
|
186
|
-
this.flush()
|
|
216
|
+
this.flush(done)
|
|
187
217
|
this.#destroyer = undefined
|
|
218
|
+
} else {
|
|
219
|
+
done?.()
|
|
188
220
|
}
|
|
189
221
|
}
|
|
190
222
|
|
|
@@ -196,6 +228,10 @@ class BaseLLMObsWriter {
|
|
|
196
228
|
this._endpoint = endpoint
|
|
197
229
|
|
|
198
230
|
logger.debug(`Configuring ${this.constructor.name} to ${this.url}`)
|
|
231
|
+
|
|
232
|
+
const pendingFlushes = this.#pendingFlushes
|
|
233
|
+
this.#pendingFlushes = []
|
|
234
|
+
for (const done of pendingFlushes) this.flush(done)
|
|
199
235
|
}
|
|
200
236
|
|
|
201
237
|
_getUrlAndPath () {
|
|
@@ -26,6 +26,11 @@ class NoopSpan {
|
|
|
26
26
|
addLink (link) { return this }
|
|
27
27
|
addLinks (links) { return this }
|
|
28
28
|
addSpanPointer (ptrKind, ptrDir, ptrHash) { return this }
|
|
29
|
+
/**
|
|
30
|
+
* @param {import('../../../../index').Exception} exception
|
|
31
|
+
* @param {import('../../../../index').SpanEventAttributes} [attributes]
|
|
32
|
+
*/
|
|
33
|
+
recordException (exception, attributes) {}
|
|
29
34
|
log () { return this }
|
|
30
35
|
logEvent () {}
|
|
31
36
|
finish (finishTime) {}
|
|
@@ -37,6 +37,7 @@ const PENDING_MAX_EVENTS = 1000
|
|
|
37
37
|
* @property {string} flag.key - Flag key
|
|
38
38
|
* @property {object} variant - Variant information
|
|
39
39
|
* @property {string} variant.key - Variant key
|
|
40
|
+
* @property {number} [serial_id] - Serial id of the split the subject landed in
|
|
40
41
|
* @property {object} subject - Subject (user/entity) information
|
|
41
42
|
* @property {string} subject.id - Subject identifier
|
|
42
43
|
* @property {string} [subject.type] - Subject type
|
|
@@ -204,7 +205,7 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
204
205
|
makePayload (events) {
|
|
205
206
|
const formattedEvents = events.map(event => {
|
|
206
207
|
/** @type {ExposureEvent} */
|
|
207
|
-
|
|
208
|
+
const formatted = {
|
|
208
209
|
timestamp: event.timestamp || Date.now(),
|
|
209
210
|
allocation: {
|
|
210
211
|
key: event.allocation?.key || event['allocation.key'],
|
|
@@ -221,6 +222,12 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
221
222
|
attributes: event.subject?.attributes,
|
|
222
223
|
},
|
|
223
224
|
}
|
|
225
|
+
|
|
226
|
+
if (typeof event.serial_id === 'number') {
|
|
227
|
+
formatted.serial_id = event.serial_id
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return formatted
|
|
224
231
|
})
|
|
225
232
|
|
|
226
233
|
return {
|
|
@@ -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
|
/**
|
|
@@ -3,17 +3,41 @@
|
|
|
3
3
|
const api = require('@opentelemetry/api')
|
|
4
4
|
const { sanitizeAttributes } = require('../../../../vendor/dist/@opentelemetry/core')
|
|
5
5
|
|
|
6
|
+
const { AUTO_KEEP, AUTO_REJECT } = require('../../../../ext/priority')
|
|
6
7
|
const tracer = require('../../')
|
|
7
8
|
|
|
8
9
|
const id = require('../id')
|
|
9
10
|
const log = require('../log')
|
|
10
|
-
const TextMapPropagator = require('../opentracing/propagation/text_map')
|
|
11
11
|
const TraceState = require('../opentracing/propagation/tracestate')
|
|
12
12
|
const SpanContext = require('./span_context')
|
|
13
13
|
const Span = require('./span')
|
|
14
14
|
const Sampler = require('./sampler')
|
|
15
15
|
const { normalizeLinkContext } = require('./span-helpers')
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* @param {number} traceparentSampled
|
|
19
|
+
* @param {number | undefined} tracestateSamplingPriority
|
|
20
|
+
* @param {string | null} origin
|
|
21
|
+
* @returns {import('../priority_sampler').SamplingPriority}
|
|
22
|
+
*/
|
|
23
|
+
function getSamplingPriority (traceparentSampled, tracestateSamplingPriority, origin) {
|
|
24
|
+
const fromRumWithoutPriority = tracestateSamplingPriority === undefined && origin === 'rum'
|
|
25
|
+
|
|
26
|
+
let samplingPriority =
|
|
27
|
+
/** @type {import('../priority_sampler').SamplingPriority} */ (tracestateSamplingPriority ?? AUTO_KEEP)
|
|
28
|
+
if (!fromRumWithoutPriority) {
|
|
29
|
+
if (traceparentSampled === 0 &&
|
|
30
|
+
(!tracestateSamplingPriority || tracestateSamplingPriority >= 0)) {
|
|
31
|
+
samplingPriority = AUTO_REJECT
|
|
32
|
+
} else if (traceparentSampled === 1 &&
|
|
33
|
+
(!tracestateSamplingPriority || tracestateSamplingPriority < 0)) {
|
|
34
|
+
samplingPriority = AUTO_KEEP
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return samplingPriority
|
|
39
|
+
}
|
|
40
|
+
|
|
17
41
|
class Tracer {
|
|
18
42
|
constructor (library, config, tracerProvider) {
|
|
19
43
|
this._sampler = new Sampler()
|
|
@@ -76,7 +100,7 @@ class Tracer {
|
|
|
76
100
|
Object.assign(meta, otherPropagatedTags)
|
|
77
101
|
// Guard against an undefined/empty `s:` field that would result in NaN.
|
|
78
102
|
const tracestateSamplingPriority = samplingPriorityTs ? Math.trunc(samplingPriorityTs) : undefined
|
|
79
|
-
samplingPriority =
|
|
103
|
+
samplingPriority = getSamplingPriority(traceFlag, tracestateSamplingPriority, origin)
|
|
80
104
|
} else {
|
|
81
105
|
log.debug('No dd list member in tracestate from incoming request:', ts)
|
|
82
106
|
}
|