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
@@ -2,12 +2,15 @@
2
2
 
3
3
  const {
4
4
  EXPOSURES_ENDPOINT,
5
- EVP_PROXY_AGENT_BASE_PATH,
6
- EVP_SUBDOMAIN_HEADER_NAME,
7
- EVP_SUBDOMAIN_VALUE,
8
5
  EVP_PAYLOAD_SIZE_LIMIT,
9
6
  EVP_EVENT_SIZE_LIMIT,
10
7
  } = require('../constants/constants')
8
+ const {
9
+ EVP_EVENT_PLATFORM_SUBDOMAIN,
10
+ EVP_PROXY_PATH_V2,
11
+ EVP_SUBDOMAIN_HEADER_NAME,
12
+ } = require('../../evp_proxy/constants')
13
+ const { joinEVPProxyPath } = require('../../evp_proxy/path')
11
14
  const log = require('../../log')
12
15
  const BaseFFEWriter = require('./base')
13
16
 
@@ -16,6 +19,15 @@ const BaseFFEWriter = require('./base')
16
19
  // drop emits a warning and `droppedEventCount` accumulates the cumulative loss.
17
20
  const PENDING_MAX_EVENTS = 1000
18
21
 
22
+ /**
23
+ * @typedef {object} ExposureRoute
24
+ * @property {URL} url - Route base URL
25
+ * @property {string} basePath - EVP base path
26
+ * @property {object} [headers] - Route-specific headers
27
+ * @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
28
+ * @property {ExposureRoute} [fallback] - Optional direct fallback route
29
+ */
30
+
19
31
  /**
20
32
  * @typedef {object} ExposureEvent
21
33
  * @property {number} timestamp - Unix timestamp in milliseconds
@@ -25,6 +37,7 @@ const PENDING_MAX_EVENTS = 1000
25
37
  * @property {string} flag.key - Flag key
26
38
  * @property {object} variant - Variant information
27
39
  * @property {string} variant.key - Variant key
40
+ * @property {number} [serial_id] - Serial id of the split the subject landed in
28
41
  * @property {object} subject - Subject (user/entity) information
29
42
  * @property {string} subject.id - Subject identifier
30
43
  * @property {string} [subject.type] - Subject type
@@ -45,10 +58,10 @@ const PENDING_MAX_EVENTS = 1000
45
58
  */
46
59
 
47
60
  /**
48
- * ExposuresWriter is responsible for sending exposure events to the Datadog Agent.
61
+ * Sends exposure events through the selected local EVP proxy or direct intake route.
49
62
  */
50
63
  class ExposuresWriter extends BaseFFEWriter {
51
- // Disabled until the agent strategy probe resolves.
64
+ // Disabled until route selection resolves.
52
65
  #enabled = false
53
66
 
54
67
  /** @type {ExposureEvent[]} */
@@ -61,22 +74,27 @@ class ExposuresWriter extends BaseFFEWriter {
61
74
 
62
75
  /**
63
76
  * @param {import('../../config/config-base')} config - Tracer configuration object
77
+ * @param {ExposureRoute} [route] - Caller-supplied route
64
78
  */
65
- constructor (config) {
66
- const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/$/, '')
67
- const endpoint = EXPOSURES_ENDPOINT.replace(/^\/+/, '')
68
- const fullEndpoint = `${basePath}/${endpoint}`
79
+ constructor (config, route) {
80
+ route ??= { url: config.url, basePath: EVP_PROXY_PATH_V2 }
81
+ const headers = route.headers ?? {
82
+ [EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
83
+ }
69
84
 
70
85
  super({
71
86
  config,
72
- endpoint: fullEndpoint,
87
+ agentUrl: route.url,
88
+ endpoint: joinEVPProxyPath(route.basePath, EXPOSURES_ENDPOINT),
73
89
  payloadSizeLimit: EVP_PAYLOAD_SIZE_LIMIT,
74
90
  eventSizeLimit: EVP_EVENT_SIZE_LIMIT,
75
- headers: {
76
- [EVP_SUBDOMAIN_HEADER_NAME]: EVP_SUBDOMAIN_VALUE,
77
- },
91
+ headers,
78
92
  })
79
93
 
94
+ if (route.agent || route.fallback) {
95
+ this.#setRoute({ ...route, headers })
96
+ }
97
+
80
98
  /** @type {ExposureContext} */
81
99
  const context = {
82
100
  service: config.service,
@@ -95,8 +113,14 @@ class ExposuresWriter extends BaseFFEWriter {
95
113
 
96
114
  /**
97
115
  * @param {boolean} enabled - Whether to enable the writer
116
+ * @param {ExposureRoute} [route] - Selected EVP route
117
+ * @returns {void}
98
118
  */
99
- setEnabled (enabled) {
119
+ setEnabled (enabled, route) {
120
+ if (route) {
121
+ this.#setRoute(route)
122
+ }
123
+
100
124
  this.#enabled = enabled
101
125
 
102
126
  if (enabled && this.#pendingEvents.length > 0) {
@@ -106,6 +130,31 @@ class ExposuresWriter extends BaseFFEWriter {
106
130
  }
107
131
  }
108
132
 
133
+ /**
134
+ * Applies caller-supplied route data without performing discovery.
135
+ *
136
+ * @param {ExposureRoute} route - Selected EVP route
137
+ * @returns {void}
138
+ */
139
+ #setRoute (route) {
140
+ const fallbackRoute = route.fallback && {
141
+ url: route.fallback.url,
142
+ endpoint: joinEVPProxyPath(route.fallback.basePath, EXPOSURES_ENDPOINT),
143
+ headers: route.fallback.headers ?? {},
144
+ agent: route.fallback.agent,
145
+ }
146
+ const headers = route.headers ?? {
147
+ [EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
148
+ }
149
+
150
+ this._setRoutes({
151
+ url: route.url,
152
+ endpoint: joinEVPProxyPath(route.basePath, EXPOSURES_ENDPOINT),
153
+ headers,
154
+ agent: route.agent,
155
+ }, fallbackRoute)
156
+ }
157
+
109
158
  /**
110
159
  * Appends exposure event(s) to the buffer
111
160
  * @param {ExposureEvent|ExposureEvent[]} events - Exposure event(s) to append
@@ -156,7 +205,7 @@ class ExposuresWriter extends BaseFFEWriter {
156
205
  makePayload (events) {
157
206
  const formattedEvents = events.map(event => {
158
207
  /** @type {ExposureEvent} */
159
- return {
208
+ const formatted = {
160
209
  timestamp: event.timestamp || Date.now(),
161
210
  allocation: {
162
211
  key: event.allocation?.key || event['allocation.key'],
@@ -173,6 +222,12 @@ class ExposuresWriter extends BaseFFEWriter {
173
222
  attributes: event.subject?.attributes,
174
223
  },
175
224
  }
225
+
226
+ if (typeof event.serial_id === 'number') {
227
+ formatted.serial_id = event.serial_id
228
+ }
229
+
230
+ return formatted
176
231
  })
177
232
 
178
233
  return {
@@ -1,30 +1,51 @@
1
1
  'use strict'
2
2
 
3
+ const {
4
+ EVP_EVENT_PLATFORM_SUBDOMAIN,
5
+ EVP_PROXY_PATH_V2,
6
+ EVP_PROXY_PATH_V4,
7
+ EVP_SUBDOMAIN_HEADER_NAME,
8
+ } = require('../../evp_proxy/constants')
9
+ const { createDirectEVPRoute } = require('../../evp_proxy/direct')
10
+ const { discoverEVPProxy } = require('../../evp_proxy/discovery')
3
11
  const logger = require('../../log')
4
- const { EVP_PROXY_AGENT_BASE_PATH } = require('../constants/constants')
5
- const { fetchAgentInfo } = require('../../agent/info')
12
+
13
+ let missingRouteWarningLogged = false
6
14
 
7
15
  /**
8
- * Determines if the agent supports EVP proxy and sets the writer enabled state accordingly
16
+ * Logs the unavailable exposure-delivery warning once.
17
+ *
18
+ * @returns {void}
19
+ */
20
+ function warnExposureDeliveryUnavailable () {
21
+ if (missingRouteWarningLogged) return
22
+ missingRouteWarningLogged = true
23
+ logger.warn(
24
+ 'Feature Flags exposure delivery is disabled because no compatible local EVP route or direct intake ' +
25
+ 'credentials are available.'
26
+ )
27
+ }
28
+
29
+ /**
30
+ * Preserves Agent exposure delivery for the Remote Configuration source.
31
+ *
9
32
  * @param {import('../../config')} config - Tracer configuration object
10
33
  * @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
34
+ * @returns {void}
11
35
  */
12
36
  function setAgentStrategy (config, setWriterEnabledValue) {
13
- fetchAgentInfo(config.url, (err, agentInfo) => {
14
- if (err) {
15
- logger.debug('FFE Writer disabled - error getting agent info:', err.message)
37
+ discoverEVPProxy(config.url, {
38
+ supportedPaths: [EVP_PROXY_PATH_V2],
39
+ }, (error, route) => {
40
+ if (error) {
41
+ logger.debug('FFE Writer disabled - error getting agent info: %s', error.message)
16
42
  setWriterEnabledValue(false)
17
43
  return
18
44
  }
19
45
 
20
- const endpoints = agentInfo.endpoints
21
- const normalizedPath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/$/, '')
22
- const hasEndpoint = Array.isArray(endpoints) &&
23
- endpoints.includes(normalizedPath) || endpoints.includes(normalizedPath + '/')
24
-
25
- if (hasEndpoint) {
46
+ if (route) {
26
47
  logger.debug('FFE Writer enabled - agent has EVP proxy support')
27
- setWriterEnabledValue(true)
48
+ setWriterEnabledValue(true, route)
28
49
  } else {
29
50
  logger.debug('FFE Writer disabled - agent does not have EVP proxy support')
30
51
  setWriterEnabledValue(false)
@@ -32,6 +53,67 @@ function setAgentStrategy (config, setWriterEnabledValue) {
32
53
  })
33
54
  }
34
55
 
35
- module.exports = {
36
- setAgentStrategy,
56
+ /**
57
+ * Selects a local serverless receiver or authenticated direct intake.
58
+ *
59
+ * Local discovery is optional for delivery. A missing listener, discovery
60
+ * error, or incompatible receiver selects direct intake when credentials exist.
61
+ *
62
+ * @param {import('../../config')} config - Tracer configuration object
63
+ * @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
64
+ * @returns {void}
65
+ */
66
+ function setAgentlessStrategy (config, setWriterEnabledValue) {
67
+ const directRoute = createDirectEVPRoute(config, EVP_EVENT_PLATFORM_SUBDOMAIN)
68
+
69
+ discoverEVPProxy(config.url, {
70
+ supportedPaths: [EVP_PROXY_PATH_V4, EVP_PROXY_PATH_V2],
71
+ }, (error, localRoute) => {
72
+ if (localRoute) {
73
+ const route = {
74
+ ...localRoute,
75
+ headers: {
76
+ [EVP_SUBDOMAIN_HEADER_NAME]: EVP_EVENT_PLATFORM_SUBDOMAIN,
77
+ },
78
+ ...(directRoute && { fallback: directRoute }),
79
+ }
80
+ logger.debug('FFE Writer enabled with local EVP route %s', route.basePath)
81
+ setWriterEnabledValue(true, route)
82
+ return
83
+ }
84
+
85
+ if (directRoute) {
86
+ if (error) {
87
+ logger.debug('FFE Writer using direct EVP intake after local discovery failed: %s', error.message)
88
+ } else {
89
+ logger.debug('FFE Writer using direct EVP intake because no compatible local route was advertised')
90
+ }
91
+ setWriterEnabledValue(true, directRoute)
92
+ return
93
+ }
94
+
95
+ if (error) {
96
+ logger.debug('FFE Writer disabled - error getting local receiver info: %s', error.message)
97
+ }
98
+ warnExposureDeliveryUnavailable()
99
+ setWriterEnabledValue(false)
100
+ })
37
101
  }
102
+
103
+ /**
104
+ * Applies the exposure-delivery strategy for the configured Feature Flags source.
105
+ *
106
+ * @param {import('../../config')} config - Tracer configuration object
107
+ * @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
108
+ * @returns {void}
109
+ */
110
+ function setExposureDeliveryStrategy (config, setWriterEnabledValue) {
111
+ if (config.featureFlags?.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE === 'agentless') {
112
+ setAgentlessStrategy(config, setWriterEnabledValue)
113
+ return
114
+ }
115
+
116
+ setAgentStrategy(config, setWriterEnabledValue)
117
+ }
118
+
119
+ module.exports = { setExposureDeliveryStrategy }
@@ -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
  /**