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.
Files changed (97) hide show
  1. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  2. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  3. package/index.d.ts +128 -26
  4. package/package.json +10 -10
  5. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  6. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  7. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +1 -0
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +28 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  10. package/packages/datadog-instrumentations/src/jest.js +140 -18
  11. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  12. package/packages/datadog-instrumentations/src/mariadb.js +7 -0
  13. package/packages/datadog-instrumentations/src/pino.js +15 -4
  14. package/packages/datadog-instrumentations/src/playwright.js +34 -20
  15. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -4
  16. package/packages/datadog-instrumentations/src/vitest-worker.js +11 -0
  17. package/packages/datadog-instrumentations/src/webdriverio.js +26 -0
  18. package/packages/datadog-instrumentations/src/winston.js +6 -3
  19. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  20. package/packages/datadog-plugin-http/src/client.js +2 -15
  21. package/packages/datadog-plugin-http2/src/client.js +2 -15
  22. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  23. package/packages/datadog-plugin-playwright/src/index.js +9 -1
  24. package/packages/datadog-plugin-undici/src/index.js +2 -15
  25. package/packages/dd-trace/src/aiguard/evaluation.js +94 -20
  26. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  27. package/packages/dd-trace/src/aiguard/messages/anthropic.js +21 -9
  28. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  29. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  30. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  31. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  32. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  33. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  34. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
  35. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
  36. package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
  37. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
  38. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -0
  39. package/packages/dd-trace/src/config/index.js +2 -1
  40. package/packages/dd-trace/src/config/supported-configurations.json +27 -0
  41. package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -3
  42. package/packages/dd-trace/src/dogstatsd.js +45 -18
  43. package/packages/dd-trace/src/encode/agentless-json.js +5 -4
  44. package/packages/dd-trace/src/exporters/agent/index.js +22 -2
  45. package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
  46. package/packages/dd-trace/src/exporters/common/writer.js +24 -2
  47. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  48. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  49. package/packages/dd-trace/src/flush.js +102 -0
  50. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -0
  51. package/packages/dd-trace/src/llmobs/experiments/dataset.js +51 -2
  52. package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
  53. package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
  54. package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
  55. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  56. package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
  57. package/packages/dd-trace/src/llmobs/index.js +80 -17
  58. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  59. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  60. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  61. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  62. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  63. package/packages/dd-trace/src/llmobs/writers/base.js +60 -24
  64. package/packages/dd-trace/src/noop/span.js +5 -0
  65. package/packages/dd-trace/src/openfeature/writers/exposures.js +8 -1
  66. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  67. package/packages/dd-trace/src/opentelemetry/logs/index.js +4 -1
  68. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  69. package/packages/dd-trace/src/opentelemetry/metrics/index.js +3 -1
  70. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  71. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +4 -2
  72. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +4 -2
  73. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  74. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  75. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  76. package/packages/dd-trace/src/opentracing/propagation/text_map.js +410 -342
  77. package/packages/dd-trace/src/opentracing/span.js +28 -0
  78. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  79. package/packages/dd-trace/src/plugin_manager.js +2 -0
  80. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  81. package/packages/dd-trace/src/plugins/util/test.js +74 -21
  82. package/packages/dd-trace/src/plugins/util/web.js +2 -65
  83. package/packages/dd-trace/src/profiling/oom.js +4 -3
  84. package/packages/dd-trace/src/profiling/profiler.js +7 -0
  85. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  86. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  87. package/packages/dd-trace/src/proxy.js +36 -8
  88. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  89. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -0
  90. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -6
  91. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +55 -0
  92. package/packages/dd-trace/src/serverless/vercel.js +122 -0
  93. package/packages/dd-trace/src/serverless.js +37 -20
  94. package/packages/dd-trace/src/span_stats.js +40 -3
  95. package/packages/dd-trace/src/tracer.js +22 -0
  96. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  97. 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
- flush () {
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
- // Flush default buffer
120
- if (this._buffer.events.length > 0) {
121
- const events = this._buffer.events
122
- this._buffer.clear()
123
-
124
- const payload = this._encode(this.makePayload(events))
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
- log.debug('Encoded LLMObs payload: %s', payload)
136
+ for (const request of requests) this.#sendSafely(request)
137
+ this.#serverlessDeliveryTracker.waitForIdle(done)
138
+ }
127
139
 
128
- const options = this._getOptions()
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
- request(payload, options, (err, resp, code) => {
131
- parseResponseAndLog(err, code, events.length, this.url, this._eventType)
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
- log.debug('Encoded LLMObs payload: %s', payload)
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
- destroy () {
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
- return {
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
- * @returns {undefined} Promise that resolves when flush is complete
60
+ * @param {Function} [done] Called after all pending log exports complete
58
61
  */
59
- forceFlush () {
60
- this.#export()
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
- // Register the logger provider globally with OpenTelemetry API
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
- * @returns {undefined} Promise that resolves when flush is n ssue cncomplete
87
+ * @param {Function} [done] Called after all pending log exports complete
88
88
  */
89
- forceFlush () {
90
- if (!this.isShutdown) {
91
- return this.processor?.forceFlush()
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
- * @returns {void}
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
- * @returns {void}
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
- this.#collectAndExport()
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
- legacyStorage.run({ noop: true }, () => {
92
- const req = this.#transport.request(options, (res) => {
93
- let data = ''
101
+ let completed = false
102
+ const complete = result => {
103
+ if (completed) return
104
+ completed = true
105
+ resultCallback(result)
106
+ done?.()
107
+ }
94
108
 
95
- res.on('data', (chunk) => {
96
- data += chunk
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
- res.once('end', () => {
100
- // @ts-expect-error - res.statusCode can be undefined
101
- if (res.statusCode >= 200 && res.statusCode < 300) {
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
- req.on('error', (error) => {
111
- log.error('Error sending OTLP %s:', this.signalType, error)
112
- resultCallback({ code: 1, error })
113
- })
138
+ req.once('timeout', () => {
139
+ req.destroy()
140
+ const error = new Error('Request timeout')
141
+ complete({ code: 1, error })
142
+ })
114
143
 
115
- req.once('timeout', () => {
116
- req.destroy()
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
- req.write(payload)
122
- req.end()
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 = TextMapPropagator._getSamplingPriority(traceFlag, tracestateSamplingPriority, origin)
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
  }