dd-trace 5.82.0 → 5.83.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 (134) hide show
  1. package/LICENSE-3rdparty.csv +78 -79
  2. package/ci/init.js +6 -6
  3. package/index.d.ts +152 -3
  4. package/loader-hook.mjs +1 -1
  5. package/package.json +58 -55
  6. package/packages/datadog-core/src/storage.js +7 -7
  7. package/packages/datadog-esbuild/index.js +6 -0
  8. package/packages/datadog-instrumentations/src/ai.js +7 -3
  9. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  10. package/packages/datadog-instrumentations/src/cucumber.js +1 -1
  11. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  12. package/packages/datadog-instrumentations/src/helpers/instrumentations.js +4 -3
  13. package/packages/datadog-instrumentations/src/helpers/register.js +3 -7
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -1
  15. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  16. package/packages/datadog-instrumentations/src/jest.js +35 -14
  17. package/packages/datadog-instrumentations/src/koa.js +2 -1
  18. package/packages/datadog-instrumentations/src/light-my-request.js +2 -2
  19. package/packages/datadog-instrumentations/src/mocha/main.js +2 -2
  20. package/packages/datadog-instrumentations/src/mocha/worker.js +1 -1
  21. package/packages/datadog-instrumentations/src/mocha.js +1 -1
  22. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  23. package/packages/datadog-instrumentations/src/mysql2.js +2 -2
  24. package/packages/datadog-instrumentations/src/net.js +13 -5
  25. package/packages/datadog-instrumentations/src/nyc.js +1 -1
  26. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +4 -4
  27. package/packages/datadog-instrumentations/src/pg.js +4 -2
  28. package/packages/datadog-instrumentations/src/playwright.js +3 -3
  29. package/packages/datadog-instrumentations/src/selenium.js +2 -2
  30. package/packages/datadog-instrumentations/src/undici.js +12 -1
  31. package/packages/datadog-plugin-aws-sdk/src/base.js +4 -4
  32. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +2 -2
  33. package/packages/datadog-plugin-azure-service-bus/src/producer.js +2 -2
  34. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  35. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +2 -2
  36. package/packages/datadog-plugin-dd-trace-api/src/index.js +2 -2
  37. package/packages/datadog-plugin-express/src/code_origin.js +21 -15
  38. package/packages/datadog-plugin-fastify/src/code_origin.js +17 -4
  39. package/packages/datadog-plugin-jest/src/index.js +2 -2
  40. package/packages/datadog-plugin-mocha/src/index.js +2 -2
  41. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -2
  42. package/packages/datadog-plugin-playwright/src/index.js +3 -3
  43. package/packages/datadog-plugin-undici/src/index.js +305 -2
  44. package/packages/datadog-plugin-vitest/src/index.js +5 -5
  45. package/packages/dd-trace/index.js +19 -0
  46. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
  47. package/packages/dd-trace/src/appsec/rasp/index.js +2 -4
  48. package/packages/dd-trace/src/azure_metadata.js +8 -3
  49. package/packages/dd-trace/src/baggage.js +36 -11
  50. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +5 -1
  51. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -2
  52. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -2
  53. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +2 -2
  54. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -2
  55. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +3 -2
  56. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +3 -3
  57. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -4
  58. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +1 -1
  59. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -2
  60. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +4 -4
  61. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -4
  62. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +2 -2
  63. package/packages/dd-trace/src/{config_defaults.js → config/defaults.js} +3 -3
  64. package/packages/dd-trace/src/{config-helper.js → config/helper.js} +88 -15
  65. package/packages/dd-trace/src/{config.js → config/index.js} +92 -45
  66. package/packages/dd-trace/src/config/remote_config.js +187 -19
  67. package/packages/dd-trace/src/{config_stable.js → config/stable.js} +20 -32
  68. package/packages/dd-trace/src/{supported-configurations.json → config/supported-configurations.json} +2 -0
  69. package/packages/dd-trace/src/crashtracking/crashtracker.js +1 -1
  70. package/packages/dd-trace/src/datastreams/processor.js +1 -1
  71. package/packages/dd-trace/src/datastreams/writer.js +1 -1
  72. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  73. package/packages/dd-trace/src/debugger/devtools_client/config.js +1 -1
  74. package/packages/dd-trace/src/debugger/devtools_client/send.js +3 -3
  75. package/packages/dd-trace/src/debugger/devtools_client/snapshot/constants.js +1 -1
  76. package/packages/dd-trace/src/debugger/index.js +83 -15
  77. package/packages/dd-trace/src/dogstatsd.js +2 -2
  78. package/packages/dd-trace/src/encode/0.4.js +2 -2
  79. package/packages/dd-trace/src/exporter.js +1 -1
  80. package/packages/dd-trace/src/exporters/agent/index.js +2 -4
  81. package/packages/dd-trace/src/exporters/agent/writer.js +9 -14
  82. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +1 -1
  83. package/packages/dd-trace/src/exporters/common/docker.js +2 -2
  84. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  85. package/packages/dd-trace/src/exporters/common/util.js +2 -2
  86. package/packages/dd-trace/src/exporters/span-stats/index.js +1 -1
  87. package/packages/dd-trace/src/flare/index.js +1 -1
  88. package/packages/dd-trace/src/guardrails/telemetry.js +1 -1
  89. package/packages/dd-trace/src/index.js +4 -4
  90. package/packages/dd-trace/src/lambda/handler.js +2 -2
  91. package/packages/dd-trace/src/lambda/index.js +2 -2
  92. package/packages/dd-trace/src/lambda/runtime/patch.js +2 -2
  93. package/packages/dd-trace/src/lambda/runtime/ritm.js +2 -2
  94. package/packages/dd-trace/src/llmobs/constants/tags.js +8 -1
  95. package/packages/dd-trace/src/llmobs/index.js +2 -2
  96. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  97. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +3 -4
  98. package/packages/dd-trace/src/llmobs/sdk.js +33 -6
  99. package/packages/dd-trace/src/llmobs/span_processor.js +17 -7
  100. package/packages/dd-trace/src/llmobs/tagger.js +175 -1
  101. package/packages/dd-trace/src/llmobs/writers/base.js +116 -37
  102. package/packages/dd-trace/src/llmobs/writers/spans.js +4 -3
  103. package/packages/dd-trace/src/log/index.js +5 -5
  104. package/packages/dd-trace/src/noop/proxy.js +3 -3
  105. package/packages/dd-trace/src/openfeature/writers/base.js +7 -8
  106. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +2 -2
  107. package/packages/dd-trace/src/opentelemetry/tracer.js +48 -6
  108. package/packages/dd-trace/src/opentracing/propagation/text_map.js +45 -21
  109. package/packages/dd-trace/src/opentracing/span.js +4 -4
  110. package/packages/dd-trace/src/plugin_manager.js +8 -6
  111. package/packages/dd-trace/src/plugins/util/ci.js +5 -8
  112. package/packages/dd-trace/src/plugins/util/git-cache.js +3 -3
  113. package/packages/dd-trace/src/plugins/util/test.js +1 -1
  114. package/packages/dd-trace/src/plugins/util/user-provided-git.js +41 -43
  115. package/packages/dd-trace/src/profiler.js +4 -39
  116. package/packages/dd-trace/src/profiling/config.js +74 -31
  117. package/packages/dd-trace/src/profiling/exporter_cli.js +5 -5
  118. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  119. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +9 -2
  120. package/packages/dd-trace/src/profiling/index.js +1 -1
  121. package/packages/dd-trace/src/profiling/libuv-size.js +1 -1
  122. package/packages/dd-trace/src/profiling/profiler.js +57 -2
  123. package/packages/dd-trace/src/proxy.js +34 -5
  124. package/packages/dd-trace/src/remote_config/capabilities.js +3 -0
  125. package/packages/dd-trace/src/remote_config/index.js +1 -1
  126. package/packages/dd-trace/src/ritm.js +8 -4
  127. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -2
  128. package/packages/dd-trace/src/serverless.js +2 -2
  129. package/packages/dd-trace/src/span_processor.js +2 -2
  130. package/packages/dd-trace/src/startup-log.js +6 -15
  131. package/packages/dd-trace/src/telemetry/endpoints.js +67 -5
  132. package/packages/dd-trace/src/telemetry/send-data.js +103 -4
  133. package/packages/dd-trace/src/telemetry/telemetry.js +229 -110
  134. /package/packages/dd-trace/src/{git_properties.js → config/git_properties.js} +0 -0
@@ -3,7 +3,7 @@
3
3
  const { URL, format } = require('node:url')
4
4
  const path = require('node:path')
5
5
  const request = require('../../exporters/common/request')
6
- const { getEnvironmentVariable } = require('../../config-helper')
6
+ const { getEnvironmentVariable } = require('../../config/helper')
7
7
 
8
8
  const logger = require('../../log')
9
9
 
@@ -16,85 +16,163 @@ const {
16
16
  } = require('../constants/writers')
17
17
  const { parseResponseAndLog } = require('./util')
18
18
 
19
+ class LLMObsBuffer {
20
+ constructor ({ events, size, routing = {}, isDefault = false, limit = 1000 }) {
21
+ this.events = events
22
+ this.size = size
23
+ this.routing = routing
24
+ this.isDefault = isDefault
25
+ this.limit = limit
26
+ }
27
+
28
+ clear () {
29
+ this.events = []
30
+ this.size = 0
31
+ }
32
+ }
33
+
19
34
  class BaseLLMObsWriter {
35
+ #destroyer
36
+ /** @type {Map<string, LLMObsBuffer>} */
37
+ #multiTenantBuffers = new Map()
38
+
20
39
  constructor ({ interval, timeout, eventType, config, endpoint, intake }) {
21
40
  this._interval = interval ?? getEnvironmentVariable('_DD_LLMOBS_FLUSH_INTERVAL') ?? 1000 // 1s
22
41
  this._timeout = timeout ?? getEnvironmentVariable('_DD_LLMOBS_TIMEOUT') ?? 5000 // 5s
23
42
  this._eventType = eventType
24
43
 
25
- this._buffer = []
26
- this._bufferLimit = 1000
27
- this._bufferSize = 0
44
+ /** @type {LLMObsBuffer} */
45
+ this._buffer = new LLMObsBuffer({ events: [], size: 0, isDefault: true })
28
46
 
29
47
  this._config = config
30
48
  this._endpoint = endpoint
49
+ this._baseEndpoint = endpoint // should not be unset
31
50
  this._intake = intake
32
51
 
33
52
  this._periodic = setInterval(() => {
34
53
  this.flush()
35
54
  }, this._interval).unref()
36
55
 
37
- this._beforeExitHandler = () => {
38
- this.destroy()
39
- }
40
- process.once('beforeExit', this._beforeExitHandler)
56
+ const destroyer = this.destroy.bind(this)
57
+ globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(destroyer)
58
+
59
+ this.#destroyer = destroyer
60
+ }
41
61
 
42
- this._destroyed = false
62
+ // Split on protocol separator to preserve it
63
+ // path.join will remove some slashes unnecessarily
64
+ #buildUrl (baseUrl, endpoint) {
65
+ const [protocol, rest] = baseUrl.split('://')
66
+ return protocol + '://' + path.join(rest, endpoint)
43
67
  }
44
68
 
45
69
  get url () {
46
70
  if (this._agentless == null) return null
71
+ return this.#buildUrl(this._baseUrl.href, this._endpoint)
72
+ }
47
73
 
48
- const baseUrl = this._baseUrl.href
49
- const endpoint = this._endpoint
50
-
51
- // Split on protocol separator to preserve it
52
- // path.join will remove some slashes unnecessarily
53
- const [protocol, rest] = baseUrl.split('://')
54
- return protocol + '://' + path.join(rest, endpoint)
74
+ _getBuffer (routing) {
75
+ if (!routing?.apiKey) {
76
+ return this._buffer
77
+ }
78
+ const apiKey = routing.apiKey
79
+ let buffer = this.#multiTenantBuffers.get(apiKey)
80
+ if (!buffer) {
81
+ buffer = new LLMObsBuffer({ events: [], size: 0, routing })
82
+ this.#multiTenantBuffers.set(apiKey, buffer)
83
+ }
84
+ return buffer
55
85
  }
56
86
 
57
- append (event, byteLength) {
58
- if (this._buffer.length >= this._bufferLimit) {
59
- logger.warn(`${this.constructor.name} event buffer full (limit is ${this._bufferLimit}), dropping event`)
87
+ append (event, routing, byteLength) {
88
+ const buffer = this._getBuffer(routing)
89
+
90
+ if (buffer.events.length >= buffer.limit) {
91
+ logger.warn(`${this.constructor.name} event buffer full (limit is ${buffer.limit}), dropping event`)
60
92
  telemetry.recordDroppedPayload(1, this._eventType, 'buffer_full')
61
93
  return
62
94
  }
63
95
 
64
- this._bufferSize += byteLength || Buffer.byteLength(JSON.stringify(event))
65
- this._buffer.push(event)
96
+ const eventSize = byteLength || Buffer.byteLength(JSON.stringify(event))
97
+
98
+ buffer.size += eventSize
99
+ buffer.events.push(event)
66
100
  }
67
101
 
68
102
  flush () {
69
- const noAgentStrategy = this._agentless == null
70
-
71
- if (this._buffer.length === 0 || noAgentStrategy) {
103
+ if (this._agentless == null) {
72
104
  return
73
105
  }
74
106
 
75
- const events = this._buffer
76
- this._buffer = []
77
- this._bufferSize = 0
78
- const payload = this._encode(this.makePayload(events))
107
+ // Flush default buffer
108
+ if (this._buffer.events.length > 0) {
109
+ const events = this._buffer.events
110
+ this._buffer.clear()
111
+
112
+ const payload = this._encode(this.makePayload(events))
79
113
 
80
- log.debug('Encoded LLMObs payload: %s', payload)
114
+ log.debug('Encoded LLMObs payload: %s', payload)
81
115
 
82
- const options = this._getOptions()
116
+ const options = this._getOptions()
117
+
118
+ request(payload, options, (err, resp, code) => {
119
+ parseResponseAndLog(err, code, events.length, this.url, this._eventType)
120
+ })
121
+ }
83
122
 
84
- request(payload, options, (err, resp, code) => {
85
- parseResponseAndLog(err, code, events.length, this.url, this._eventType)
86
- })
123
+ // Flush multi-tenant buffers
124
+ for (const [apiKey, buffer] of this.#multiTenantBuffers) {
125
+ if (buffer.events.length === 0) continue
126
+
127
+ const events = buffer.events
128
+ buffer.clear()
129
+
130
+ const payload = this._encode(this.makePayload(events))
131
+ const site = buffer.routing.site || this._config.site
132
+ const options = {
133
+ headers: {
134
+ 'Content-Type': 'application/json',
135
+ 'DD-API-KEY': apiKey
136
+ },
137
+ method: 'POST',
138
+ timeout: this._timeout,
139
+ url: new URL(format({
140
+ protocol: 'https:',
141
+ hostname: `${this._intake}.${site}`
142
+ })),
143
+ path: this._baseEndpoint
144
+ }
145
+ const url = this.#buildUrl(options.url.href, options.path)
146
+ const maskedApiKey = apiKey ? `****${apiKey.slice(-4)}` : ''
147
+
148
+ log.debug('Encoding and flushing multi-tenant buffer for %s', maskedApiKey)
149
+ log.debug('Encoded LLMObs payload: %s', payload)
150
+
151
+ request(payload, options, (err, resp, code) => {
152
+ parseResponseAndLog(err, code, events.length, url, this._eventType)
153
+ })
154
+ }
155
+
156
+ this.#cleanupEmptyBuffers()
157
+ }
158
+
159
+ #cleanupEmptyBuffers () {
160
+ for (const [key, buffer] of this.#multiTenantBuffers) {
161
+ if (buffer.events.length === 0) {
162
+ this.#multiTenantBuffers.delete(key)
163
+ }
164
+ }
87
165
  }
88
166
 
89
167
  makePayload (events) {}
90
168
 
91
169
  destroy () {
92
- if (!this._destroyed) {
170
+ if (this.#destroyer) {
93
171
  logger.debug(`Stopping ${this.constructor.name}`)
94
172
  clearInterval(this._periodic)
95
- process.removeListener('beforeExit', this._beforeExitHandler)
173
+ globalThis[Symbol.for('dd-trace')].beforeExitHandlers.delete(this.#destroyer)
96
174
  this.flush()
97
- this._destroyed = true
175
+ this.#destroyer = undefined
98
176
  }
99
177
  }
100
178
 
@@ -110,10 +188,11 @@ class BaseLLMObsWriter {
110
188
 
111
189
  _getUrlAndPath () {
112
190
  if (this._agentless) {
191
+ const site = this._config.site
113
192
  return {
114
193
  url: new URL(format({
115
194
  protocol: 'https:',
116
- hostname: `${this._intake}.${this._config.site}`
195
+ hostname: `${this._intake}.${site}`
117
196
  })),
118
197
  endpoint: this._endpoint
119
198
  }
@@ -24,7 +24,7 @@ class LLMObsSpanWriter extends BaseWriter {
24
24
  })
25
25
  }
26
26
 
27
- append (event) {
27
+ append (event, routing) {
28
28
  const eventSizeBytes = Buffer.byteLength(JSON.stringify(event))
29
29
  telemetry.recordLLMObsRawSpanSize(event, eventSizeBytes)
30
30
 
@@ -39,12 +39,13 @@ class LLMObsSpanWriter extends BaseWriter {
39
39
 
40
40
  telemetry.recordLLMObsSpanSize(event, processedEventSizeBytes, shouldTruncate)
41
41
 
42
- if (this._bufferSize + eventSizeBytes > EVP_PAYLOAD_SIZE_LIMIT) {
42
+ const buffer = this._getBuffer(routing)
43
+ if (buffer.size + processedEventSizeBytes > EVP_PAYLOAD_SIZE_LIMIT) {
43
44
  logger.debug('Flushing queue because queuing next event will exceed EvP payload limit')
44
45
  this.flush()
45
46
  }
46
47
 
47
- super.append(event, processedEventSizeBytes)
48
+ super.append(event, routing, processedEventSizeBytes)
48
49
  }
49
50
 
50
51
  makePayload (events) {
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
  const { inspect } = require('util')
3
3
  const { isTrue } = require('../util')
4
- const { getEnvironmentVariable } = require('../config-helper')
4
+ const { getValueFromEnvSources } = require('../config/helper')
5
5
  const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
6
6
  const logWriter = require('./writer')
7
7
  const { Log, LogConfig, NoTransmitError } = require('./log')
@@ -112,8 +112,8 @@ const log = {
112
112
  isEnabled (fleetStableConfigValue, localStableConfigValue) {
113
113
  return isTrue(
114
114
  fleetStableConfigValue ??
115
- getEnvironmentVariable('DD_TRACE_DEBUG') ??
116
- (getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug' || undefined) ??
115
+ getValueFromEnvSources('DD_TRACE_DEBUG') ??
116
+ (getValueFromEnvSources('OTEL_LOG_LEVEL') === 'debug' || undefined) ??
117
117
  localStableConfigValue ??
118
118
  config.enabled
119
119
  )
@@ -126,8 +126,8 @@ const log = {
126
126
  ) {
127
127
  return optionsValue ??
128
128
  fleetStableConfigValue ??
129
- getEnvironmentVariable('DD_TRACE_LOG_LEVEL') ??
130
- getEnvironmentVariable('OTEL_LOG_LEVEL') ??
129
+ getValueFromEnvSources('DD_TRACE_LOG_LEVEL') ??
130
+ getValueFromEnvSources('OTEL_LOG_LEVEL') ??
131
131
  localStableConfigValue ??
132
132
  config.logLevel
133
133
  }
@@ -23,10 +23,10 @@ class NoopProxy {
23
23
  this.llmobs = noopLLMObs
24
24
  this.openfeature = noopOpenFeatureProvider
25
25
  this.aiguard = noopAIGuard
26
- this.setBaggageItem = () => {}
27
- this.getBaggageItem = () => {}
26
+ this.setBaggageItem = (key, value) => {}
27
+ this.getBaggageItem = (key) => {}
28
28
  this.getAllBaggageItems = () => {}
29
- this.removeBaggageItem = () => {}
29
+ this.removeBaggageItem = (keyToRemove) => {}
30
30
  this.removeAllBaggageItems = () => {}
31
31
  }
32
32
 
@@ -23,6 +23,7 @@ const log = require('../../log')
23
23
  * @class BaseFFEWriter
24
24
  */
25
25
  class BaseFFEWriter {
26
+ #destroyer
26
27
  /**
27
28
  * @param {BaseFFEWriterOptions} options - Writer configuration options
28
29
  */
@@ -56,12 +57,10 @@ class BaseFFEWriter {
56
57
  this.flush()
57
58
  }, this._interval).unref()
58
59
 
59
- this._beforeExitHandler = () => {
60
- this.destroy()
61
- }
62
- process.once('beforeExit', this._beforeExitHandler)
60
+ const destroyer = this.destroy.bind(this)
61
+ globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(destroyer)
63
62
 
64
- this._destroyed = false
63
+ this.#destroyer = destroyer
65
64
  this._droppedEvents = 0
66
65
  }
67
66
 
@@ -141,12 +140,12 @@ class BaseFFEWriter {
141
140
  * Cleans up resources and flushes remaining events
142
141
  */
143
142
  destroy () {
144
- if (!this._destroyed) {
143
+ if (this.#destroyer) {
145
144
  log.debug(() => `Stopping ${this.constructor.name}`)
146
145
  clearInterval(this._periodic)
147
- process.removeListener('beforeExit', this._beforeExitHandler)
148
146
  this.flush()
149
- this._destroyed = true
147
+ globalThis[Symbol.for('dd-trace')].beforeExitHandlers.delete(this.#destroyer)
148
+ this.#destroyer = undefined
150
149
 
151
150
  if (this._droppedEvents > 0) {
152
151
  log.warn(`${this.constructor.name} dropped ${this._droppedEvents} events due to buffer overflow`)
@@ -90,7 +90,7 @@ class OtlpHttpExporterBase {
90
90
  data += chunk
91
91
  })
92
92
 
93
- res.on('end', () => {
93
+ res.once('end', () => {
94
94
  if (res.statusCode >= 200 && res.statusCode < 300) {
95
95
  resultCallback({ code: 0 })
96
96
  } else {
@@ -105,7 +105,7 @@ class OtlpHttpExporterBase {
105
105
  resultCallback({ code: 1, error })
106
106
  })
107
107
 
108
- req.on('timeout', () => {
108
+ req.once('timeout', () => {
109
109
  req.destroy()
110
110
  const error = new Error('Request timeout')
111
111
  resultCallback({ code: 1, error })
@@ -5,12 +5,48 @@ const { sanitizeAttributes } = require('../../../../vendor/dist/@opentelemetry/c
5
5
 
6
6
  const id = require('../id')
7
7
  const log = require('../log')
8
+ const DatadogSpanContext = require('../opentracing/span_context')
8
9
  const TextMapPropagator = require('../opentracing/propagation/text_map')
9
10
  const TraceState = require('../opentracing/propagation/tracestate')
10
11
  const SpanContext = require('./span_context')
11
12
  const Span = require('./span')
12
13
  const Sampler = require('./sampler')
13
14
 
15
+ function normalizeLinkContext (context) {
16
+ if (!context) return
17
+
18
+ // OTel API bridge SpanContext wrapper
19
+ if (context._ddContext) return context._ddContext
20
+
21
+ // Datadog span context
22
+ if (typeof context.toTraceId === 'function' && typeof context.toSpanId === 'function') {
23
+ return context
24
+ }
25
+
26
+ // Standard OTel SpanContext (traceId/spanId)
27
+ if (typeof context.traceId !== 'string' || typeof context.spanId !== 'string') {
28
+ // Invalid
29
+ return
30
+ }
31
+
32
+ let sampling
33
+ if (typeof context.traceFlags === 'number') {
34
+ sampling = { priority: context.traceFlags & 1 }
35
+ }
36
+
37
+ let tracestate
38
+ if (context.traceState?.serialize) {
39
+ tracestate = TraceState.fromString(context.traceState.serialize())
40
+ }
41
+
42
+ return new DatadogSpanContext({
43
+ traceId: id(context.traceId, 16),
44
+ spanId: id(context.spanId, 16),
45
+ sampling,
46
+ tracestate
47
+ })
48
+ }
49
+
14
50
  class Tracer {
15
51
  constructor (library, config, tracerProvider) {
16
52
  this._sampler = new Sampler()
@@ -96,7 +132,7 @@ class Tracer {
96
132
  context = api.trace.deleteSpan(context)
97
133
  }
98
134
  const parentSpan = api.trace.getSpan(context)
99
- const parentSpanContext = parentSpan && parentSpan.spanContext()
135
+ const parentSpanContext = parentSpan?.spanContext()
100
136
  let spanContext
101
137
  if (parentSpanContext && api.trace.isSpanContextValid(parentSpanContext)) {
102
138
  spanContext = parentSpanContext._ddContext
@@ -107,12 +143,18 @@ class Tracer {
107
143
  }
108
144
 
109
145
  const spanKind = options.kind || api.SpanKind.INTERNAL
110
- const links = (options.links || []).map(link => {
111
- return {
112
- context: link.context,
113
- attributes: sanitizeAttributes(link.attributes)
146
+ const links = []
147
+ if (options.links?.length) {
148
+ for (const link of options.links) {
149
+ const ddContext = normalizeLinkContext(link?.context)
150
+ if (!ddContext) continue
151
+
152
+ links.push({
153
+ context: ddContext,
154
+ attributes: sanitizeAttributes(link.attributes)
155
+ })
114
156
  }
115
- })
157
+ }
116
158
  const attributes = sanitizeAttributes(options.attributes)
117
159
 
118
160
  // TODO: sampling API is not yet supported
@@ -34,11 +34,19 @@ const b3HeaderKey = 'b3'
34
34
  const sqsdHeaderHey = 'x-aws-sqsd-attr-_datadog'
35
35
  const b3HeaderExpr = /^(([0-9a-f]{16}){1,2}-[0-9a-f]{16}(-[01d](-[0-9a-f]{16})?)?|[01d])$/i
36
36
  const baggageExpr = new RegExp(`^${baggagePrefix}(.+)$`)
37
+ // W3C Baggage key grammar: key = token (RFC 7230).
38
+ // Spec (up-to-date): "Propagation format for distributed context: Baggage" §3.3.1
39
+ // https://www.w3.org/TR/baggage/#header-content
40
+ // https://www.rfc-editor.org/rfc/rfc7230#section-3.2.6
41
+ const baggageTokenExpr = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/
37
42
  const tagKeyExpr = /^_dd\.p\.[\x21-\x2B\x2D-\x7E]+$/ // ASCII minus spaces and commas
38
43
  const tagValueExpr = /^[\x20-\x2B\x2D-\x7E]*$/ // ASCII minus commas
39
44
  // RFC7230 token (used by HTTP header field-name) and compatible with Node's header name validation.
40
45
  // See https://www.rfc-editor.org/rfc/rfc7230#section-3.2.6
41
46
  const httpHeaderNameExpr = /^[0-9A-Za-z!#$%&'*+\-.^_`|~]+$/
47
+ // Compatible with Node's internal header value validation (allows HTAB, SP-~, and \x80-\xFF only)
48
+ // https://github.com/nodejs/node/blob/main/lib/_http_common.js
49
+ const invalidHeaderValueCharExpr = /[^\t\x20-\x7E\x80-\xFF]/
42
50
  const traceparentExpr = /^([a-f0-9]{2})-([a-f0-9]{32})-([a-f0-9]{16})-([a-f0-9]{2})(-.*)?$/i
43
51
  const traceparentKey = 'traceparent'
44
52
  const tracestateKey = 'tracestate'
@@ -124,13 +132,6 @@ class TextMapPropagator {
124
132
  }
125
133
  }
126
134
 
127
- _encodeOtelBaggageKey (key) {
128
- let encoded = encodeURIComponent(key)
129
- encoded = encoded.replaceAll('(', '%28')
130
- encoded = encoded.replaceAll(')', '%29')
131
- return encoded
132
- }
133
-
134
135
  _injectBaggageItems (spanContext, carrier) {
135
136
  if (this._config.legacyBaggageEnabled) {
136
137
  const baggageItems = spanContext?._baggageItems
@@ -145,7 +146,12 @@ class TextMapPropagator {
145
146
  continue
146
147
  }
147
148
 
148
- carrier[headerName] = String(baggageItems[key])
149
+ let headerValue = String(baggageItems[key])
150
+ // Avoid Node throwing ERR_INVALID_CHAR when setting header values (e.g. newline from decoded OTEL baggage).
151
+ if (invalidHeaderValueCharExpr.test(headerValue)) {
152
+ headerValue = encodeURIComponent(headerValue)
153
+ }
154
+ carrier[headerName] = headerValue
149
155
  }
150
156
  }
151
157
  }
@@ -157,7 +163,13 @@ class TextMapPropagator {
157
163
  const baggageItems = getAllBaggageItems()
158
164
  if (!baggageItems) return
159
165
  for (const [key, value] of Object.entries(baggageItems)) {
160
- const item = `${this._encodeOtelBaggageKey(String(key).trim())}=${encodeURIComponent(String(value).trim())},`
166
+ const baggageKey = String(key).trim()
167
+ if (!baggageKey || !baggageTokenExpr.test(baggageKey)) continue
168
+
169
+ // Do not trim values. If callers include leading/trailing whitespace, it must be percent-encoded.
170
+ // W3C list-member allows optional properties after ';'.
171
+ // https://www.w3.org/TR/baggage/#header-content
172
+ const item = `${baggageKey}=${encodeURIComponent(String(value))},`
161
173
  itemCounter += 1
162
174
  byteCounter += Buffer.byteLength(item)
163
175
 
@@ -640,13 +652,6 @@ class TextMapPropagator {
640
652
  }
641
653
  }
642
654
 
643
- _decodeOtelBaggageKey (key) {
644
- let decoded = decodeURIComponent(key)
645
- decoded = decoded.replaceAll('%28', '(')
646
- decoded = decoded.replaceAll('%29', ')')
647
- return decoded
648
- }
649
-
650
655
  _extractLegacyBaggageItems (carrier, spanContext) {
651
656
  if (this._config.legacyBaggageEnabled) {
652
657
  Object.keys(carrier).forEach(key => {
@@ -668,19 +673,38 @@ class TextMapPropagator {
668
673
  ? undefined
669
674
  : new Set(this._config.baggageTagKeys.split(','))
670
675
  for (const keyValue of baggages) {
671
- if (!keyValue.includes('=')) {
676
+ if (!keyValue) continue
677
+
678
+ // Per W3C baggage, list-members can contain optional properties after `;`.
679
+ // Example: key=value;prop=1;prop2
680
+ // https://www.w3.org/TR/baggage/#header-content
681
+ const semicolonIdx = keyValue.indexOf(';')
682
+ const member = (semicolonIdx === -1 ? keyValue : keyValue.slice(0, semicolonIdx)).trim()
683
+ if (!member) continue
684
+
685
+ const eqIdx = member.indexOf('=')
686
+ if (eqIdx === -1) {
672
687
  tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
673
688
  removeAllBaggageItems()
674
689
  return
675
690
  }
676
- let [key, value] = keyValue.split('=')
677
- key = this._decodeOtelBaggageKey(key.trim())
678
- value = decodeURIComponent(value.trim())
679
- if (!key || !value) {
691
+
692
+ const key = member.slice(0, eqIdx).trim()
693
+ let value = member.slice(eqIdx + 1).trim()
694
+
695
+ if (!baggageTokenExpr.test(key) || !value) {
680
696
  tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
681
697
  removeAllBaggageItems()
682
698
  return
683
699
  }
700
+ try {
701
+ value = decodeURIComponent(value)
702
+ } catch {
703
+ tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
704
+ removeAllBaggageItems()
705
+ return
706
+ }
707
+
684
708
  if (spanContext && (tagAllKeys || keysToSpanTag?.has(key))) {
685
709
  spanContext._trace.tags['baggage.' + key] = value
686
710
  }
@@ -12,18 +12,18 @@ const runtimeMetrics = require('../runtime_metrics')
12
12
  const log = require('../log')
13
13
  const { storage } = require('../../../datadog-core')
14
14
  const telemetryMetrics = require('../telemetry/metrics')
15
- const { getEnvironmentVariable } = require('../config-helper')
15
+ const { getValueFromEnvSources } = require('../config/helper')
16
16
  const SpanContext = require('./span_context')
17
17
 
18
18
  const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
19
19
 
20
- const DD_TRACE_EXPERIMENTAL_STATE_TRACKING = getEnvironmentVariable('DD_TRACE_EXPERIMENTAL_STATE_TRACKING')
21
- const DD_TRACE_EXPERIMENTAL_SPAN_COUNTS = getEnvironmentVariable('DD_TRACE_EXPERIMENTAL_SPAN_COUNTS')
20
+ const DD_TRACE_EXPERIMENTAL_STATE_TRACKING = getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_STATE_TRACKING')
21
+ const DD_TRACE_EXPERIMENTAL_SPAN_COUNTS = getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_SPAN_COUNTS')
22
22
 
23
23
  const unfinishedRegistry = createRegistry('unfinished')
24
24
  const finishedRegistry = createRegistry('finished')
25
25
 
26
- const OTEL_ENABLED = !!getEnvironmentVariable('DD_TRACE_OTEL_ENABLED')
26
+ const OTEL_ENABLED = !!getValueFromEnvSources('DD_TRACE_OTEL_ENABLED')
27
27
  const ALLOWED = new Set(['string', 'number', 'boolean'])
28
28
 
29
29
  const integrationCounters = {
@@ -1,7 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
- const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper')
4
+
5
+ const { getEnvironmentVariable, getValueFromEnvSources } = require('./config/helper')
5
6
  const { isFalse, isTrue, normalizePluginEnvName } = require('./util')
6
7
  const plugins = require('./plugins')
7
8
  const log = require('./log')
@@ -24,7 +25,7 @@ if (getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined) {
24
25
  require('./lambda')
25
26
  }
26
27
 
27
- const DD_TRACE_DISABLED_PLUGINS = getEnvironmentVariable('DD_TRACE_DISABLED_PLUGINS')
28
+ const DD_TRACE_DISABLED_PLUGINS = getValueFromEnvSources('DD_TRACE_DISABLED_PLUGINS')
28
29
 
29
30
  const disabledPlugins = new Set(
30
31
  DD_TRACE_DISABLED_PLUGINS && DD_TRACE_DISABLED_PLUGINS.split(',').map(plugin => plugin.trim())
@@ -41,7 +42,7 @@ loadChannel.subscribe(({ name }) => {
41
42
  function maybeEnable (Plugin) {
42
43
  if (!Plugin || typeof Plugin !== 'function') return
43
44
  if (!pluginClasses[Plugin.id]) {
44
- const enabled = getEnvEnabled(Plugin)
45
+ const enabled = getEnabled(Plugin)
45
46
 
46
47
  // TODO: remove the need to load the plugin class in order to disable the plugin
47
48
  if (isFalse(enabled) || disabledPlugins.has(Plugin.id)) {
@@ -54,9 +55,9 @@ function maybeEnable (Plugin) {
54
55
  }
55
56
  }
56
57
 
57
- function getEnvEnabled (Plugin) {
58
+ function getEnabled (Plugin) {
58
59
  const envName = `DD_TRACE_${Plugin.id.toUpperCase()}_ENABLED`
59
- return getEnvironmentVariable(normalizePluginEnvName(envName))
60
+ return getValueFromEnvSources(normalizePluginEnvName(envName))
60
61
  }
61
62
 
62
63
  // TODO this must always be a singleton.
@@ -94,7 +95,8 @@ module.exports = class PluginManager {
94
95
  this._pluginsByName[name] = new Plugin(this._tracer, this._tracerConfig)
95
96
  }
96
97
  const pluginConfig = this._configsByName[name] || {
97
- enabled: this._tracerConfig.plugins !== false && (!Plugin.experimental || isTrue(getEnvEnabled(Plugin)))
98
+ enabled: this._tracerConfig.plugins !== false &&
99
+ (!Plugin.experimental || isTrue(getEnabled(Plugin)))
98
100
  }
99
101
 
100
102
  // extracts predetermined configuration from tracer and combines it with plugin-specific config
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { readFileSync } = require('fs')
4
- const { getEnvironmentVariable, getEnvironmentVariables } = require('../../config-helper')
4
+ const { getEnvironmentVariable, getEnvironmentVariables, getValueFromEnvSources } = require('../../config/helper')
5
5
  const {
6
6
  GIT_BRANCH,
7
7
  GIT_COMMIT_SHA,
@@ -120,12 +120,12 @@ module.exports = {
120
120
  GIT_COMMIT: JENKINS_GIT_COMMIT,
121
121
  GIT_URL: JENKINS_GIT_REPOSITORY_URL,
122
122
  GIT_URL_1: JENKINS_GIT_REPOSITORY_URL_1,
123
- DD_CUSTOM_TRACE_ID,
124
123
  NODE_NAME,
125
124
  NODE_LABELS,
126
125
  CHANGE_ID,
127
126
  CHANGE_TARGET
128
127
  } = env
128
+ const DD_CUSTOM_TRACE_ID = getValueFromEnvSources('DD_CUSTOM_TRACE_ID')
129
129
 
130
130
  tags = {
131
131
  [CI_PIPELINE_ID]: BUILD_TAG,
@@ -689,16 +689,13 @@ module.exports = {
689
689
  }
690
690
 
691
691
  if (env.CODEBUILD_INITIATOR?.startsWith('codepipeline/')) {
692
- const {
693
- CODEBUILD_BUILD_ARN,
694
- DD_ACTION_EXECUTION_ID,
695
- DD_PIPELINE_EXECUTION_ID
696
- } = env
692
+ const DD_ACTION_EXECUTION_ID = getValueFromEnvSources('DD_ACTION_EXECUTION_ID')
693
+ const DD_PIPELINE_EXECUTION_ID = getValueFromEnvSources('DD_PIPELINE_EXECUTION_ID')
697
694
  tags = {
698
695
  [CI_PROVIDER_NAME]: 'awscodepipeline',
699
696
  [CI_PIPELINE_ID]: DD_PIPELINE_EXECUTION_ID,
700
697
  [CI_ENV_VARS]: JSON.stringify({
701
- CODEBUILD_BUILD_ARN,
698
+ CODEBUILD_BUILD_ARN: env.CODEBUILD_BUILD_ARN,
702
699
  DD_PIPELINE_EXECUTION_ID,
703
700
  DD_ACTION_EXECUTION_ID
704
701
  }),