dd-trace 5.87.0 → 5.89.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 (119) hide show
  1. package/LICENSE-3rdparty.csv +60 -32
  2. package/ext/exporters.d.ts +1 -0
  3. package/ext/exporters.js +1 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +234 -4
  6. package/package.json +18 -11
  7. package/packages/datadog-instrumentations/src/ai.js +54 -90
  8. package/packages/datadog-instrumentations/src/helpers/hook.js +17 -11
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +27 -110
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +103 -0
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.js +108 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -1
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/compiler.js +74 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/index.js +43 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/matcher.js +49 -0
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/transformer.js +121 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/{transforms.js → orchestrion/transforms.js} +143 -17
  18. package/packages/datadog-instrumentations/src/jest.js +176 -54
  19. package/packages/datadog-instrumentations/src/kafkajs.js +20 -17
  20. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  21. package/packages/datadog-plugin-amqplib/src/consumer.js +14 -10
  22. package/packages/datadog-plugin-amqplib/src/producer.js +23 -19
  23. package/packages/datadog-plugin-bullmq/src/consumer.js +33 -11
  24. package/packages/datadog-plugin-bullmq/src/producer.js +60 -31
  25. package/packages/datadog-plugin-cucumber/src/index.js +9 -6
  26. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +62 -5
  27. package/packages/datadog-plugin-cypress/src/source-map-utils.js +297 -0
  28. package/packages/datadog-plugin-cypress/src/support.js +52 -9
  29. package/packages/datadog-plugin-jest/src/index.js +12 -2
  30. package/packages/datadog-plugin-jest/src/util.js +2 -1
  31. package/packages/datadog-plugin-kafkajs/src/consumer.js +22 -12
  32. package/packages/datadog-plugin-kafkajs/src/producer.js +33 -22
  33. package/packages/datadog-plugin-mocha/src/index.js +9 -6
  34. package/packages/datadog-plugin-playwright/src/index.js +10 -6
  35. package/packages/datadog-plugin-vitest/src/index.js +13 -8
  36. package/packages/dd-trace/src/aiguard/sdk.js +5 -1
  37. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +1 -1
  38. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +1 -1
  39. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +1 -1
  40. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +4 -5
  41. package/packages/dd-trace/src/appsec/iast/path-line.js +36 -25
  42. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +3 -2
  45. package/packages/dd-trace/src/azure_metadata.js +0 -2
  46. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  47. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +3 -0
  48. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -1
  49. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
  50. package/packages/dd-trace/src/ci-visibility/requests/request.js +236 -0
  51. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  52. package/packages/dd-trace/src/config/defaults.js +148 -197
  53. package/packages/dd-trace/src/config/helper.js +43 -1
  54. package/packages/dd-trace/src/config/index.js +38 -14
  55. package/packages/dd-trace/src/config/supported-configurations.json +4125 -512
  56. package/packages/dd-trace/src/constants.js +0 -2
  57. package/packages/dd-trace/src/crashtracking/crashtracker.js +10 -3
  58. package/packages/dd-trace/src/datastreams/checkpointer.js +13 -0
  59. package/packages/dd-trace/src/datastreams/index.js +3 -0
  60. package/packages/dd-trace/src/datastreams/manager.js +9 -0
  61. package/packages/dd-trace/src/datastreams/pathway.js +22 -3
  62. package/packages/dd-trace/src/datastreams/processor.js +140 -4
  63. package/packages/dd-trace/src/encode/agentless-json.js +155 -0
  64. package/packages/dd-trace/src/exporter.js +2 -0
  65. package/packages/dd-trace/src/exporters/agent/writer.js +21 -8
  66. package/packages/dd-trace/src/exporters/agentless/index.js +89 -0
  67. package/packages/dd-trace/src/exporters/agentless/writer.js +184 -0
  68. package/packages/dd-trace/src/exporters/common/request.js +4 -4
  69. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -3
  70. package/packages/dd-trace/src/opentelemetry/context_manager.js +19 -46
  71. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +3 -4
  72. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -5
  73. package/packages/dd-trace/src/opentracing/span.js +6 -4
  74. package/packages/dd-trace/src/pkg.js +1 -1
  75. package/packages/dd-trace/src/plugins/ci_plugin.js +57 -5
  76. package/packages/dd-trace/src/plugins/database.js +15 -2
  77. package/packages/dd-trace/src/plugins/util/test.js +48 -0
  78. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -0
  79. package/packages/dd-trace/src/propagation-hash/index.js +145 -0
  80. package/packages/dd-trace/src/proxy.js +6 -1
  81. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  82. package/packages/dd-trace/src/startup-log.js +53 -19
  83. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  84. package/vendor/dist/@datadog/source-map/index.js +1 -1
  85. package/vendor/dist/@isaacs/ttlcache/index.js +1 -1
  86. package/vendor/dist/@opentelemetry/core/index.js +1 -1
  87. package/vendor/dist/@opentelemetry/resources/index.js +1 -1
  88. package/vendor/dist/astring/index.js +1 -1
  89. package/vendor/dist/crypto-randomuuid/index.js +1 -1
  90. package/vendor/dist/escape-string-regexp/index.js +1 -1
  91. package/vendor/dist/esquery/index.js +1 -1
  92. package/vendor/dist/ignore/index.js +1 -1
  93. package/vendor/dist/istanbul-lib-coverage/index.js +1 -1
  94. package/vendor/dist/jest-docblock/index.js +1 -1
  95. package/vendor/dist/jsonpath-plus/index.js +1 -1
  96. package/vendor/dist/limiter/index.js +1 -1
  97. package/vendor/dist/lodash.sortby/index.js +1 -1
  98. package/vendor/dist/lru-cache/index.js +1 -1
  99. package/vendor/dist/meriyah/index.js +1 -1
  100. package/vendor/dist/module-details-from-path/index.js +1 -1
  101. package/vendor/dist/mutexify/promise/index.js +1 -1
  102. package/vendor/dist/opentracing/index.js +1 -1
  103. package/vendor/dist/path-to-regexp/index.js +1 -1
  104. package/vendor/dist/pprof-format/index.js +1 -1
  105. package/vendor/dist/protobufjs/index.js +1 -1
  106. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  107. package/vendor/dist/retry/index.js +1 -1
  108. package/vendor/dist/rfdc/index.js +1 -1
  109. package/vendor/dist/semifies/index.js +1 -1
  110. package/vendor/dist/shell-quote/index.js +1 -1
  111. package/vendor/dist/source-map/index.js +1 -1
  112. package/vendor/dist/source-map/lib/util/index.js +1 -1
  113. package/vendor/dist/tlhunter-sorted-set/index.js +1 -1
  114. package/vendor/dist/ttl-set/index.js +1 -1
  115. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +0 -33
  116. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.json +0 -106
  117. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +0 -741
  118. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +0 -11
  119. package/packages/dd-trace/src/scope/noop/scope.js +0 -21
@@ -0,0 +1,184 @@
1
+ 'use strict'
2
+
3
+ const { getValueFromEnvSources } = require('../../config/helper')
4
+ const log = require('../../log')
5
+ const request = require('../common/request')
6
+ const tracerVersion = require('../../../../../package.json').version
7
+
8
+ const BaseWriter = require('../common/writer')
9
+ const { AgentlessJSONEncoder } = require('../../encode/agentless-json')
10
+
11
+ /**
12
+ * Writer for agentless APM span intake.
13
+ * Sends spans directly to the Datadog intake endpoint without an agent.
14
+ */
15
+ class AgentlessWriter extends BaseWriter {
16
+ #apiKeyMissing = false
17
+
18
+ /**
19
+ * @param {object} options - Writer options
20
+ * @param {URL} [options.url] - The intake URL. If not provided, constructed from site.
21
+ * @param {string} [options.site='datadoghq.com'] - The Datadog site
22
+ */
23
+ constructor ({ url, site = 'datadoghq.com' }) {
24
+ super({ url })
25
+ this._encoder = new AgentlessJSONEncoder()
26
+
27
+ if (!url) {
28
+ try {
29
+ this._url = new URL(`https://public-trace-http-intake.logs.${site}`)
30
+ } catch (err) {
31
+ log.error(
32
+ 'Invalid site value for agentless intake: %s. Cannot construct URL. Error: %s',
33
+ site,
34
+ err.message
35
+ )
36
+ this._url = null
37
+ }
38
+ }
39
+
40
+ if (!getValueFromEnvSources('DD_API_KEY')) {
41
+ this.#apiKeyMissing = true
42
+ log.error('DD_API_KEY is required for agentless span intake. Set DD_API_KEY. Spans will not be sent.')
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Flushes the current trace. Since we flush after each trace, this sends
48
+ * a single request.
49
+ * @param {Function} [done] - Callback when send completes
50
+ */
51
+ flush (done = () => {}) {
52
+ if (!request.writable) {
53
+ this._encoder.reset()
54
+ done()
55
+ return
56
+ }
57
+
58
+ const count = this._encoder.count()
59
+
60
+ if (count === 0) {
61
+ done()
62
+ return
63
+ }
64
+
65
+ const payload = this._encoder.makePayload()
66
+
67
+ if (payload.length === 0) {
68
+ log.debug('Skipping send of empty payload')
69
+ done()
70
+ return
71
+ }
72
+
73
+ this._sendPayload(payload, count, done)
74
+ }
75
+
76
+ /**
77
+ * Sends the encoded payload to the intake endpoint.
78
+ * @param {Buffer} data - The encoded JSON payload
79
+ * @param {number} count - Number of spans in the payload
80
+ * @param {Function} done - Callback when complete
81
+ */
82
+ _sendPayload (data, count, done) {
83
+ if (!data || data.length === 0) {
84
+ log.debug('Skipping send of empty payload')
85
+ done()
86
+ return
87
+ }
88
+
89
+ if (!this._url) {
90
+ log.debug('Skipping send due to invalid URL configuration')
91
+ done()
92
+ return
93
+ }
94
+
95
+ const apiKey = getValueFromEnvSources('DD_API_KEY')
96
+ if (!apiKey) {
97
+ if (!this.#apiKeyMissing) {
98
+ this.#apiKeyMissing = true
99
+ log.error('DD_API_KEY is required for agentless span intake. Set DD_API_KEY. Spans will not be sent.')
100
+ }
101
+ log.debug('Dropping %d span(s) due to missing DD_API_KEY', count)
102
+ done()
103
+ return
104
+ }
105
+ this.#apiKeyMissing = false
106
+
107
+ const options = {
108
+ path: '/v1/input',
109
+ method: 'POST',
110
+ headers: {
111
+ 'Content-Type': 'application/json',
112
+ 'dd-api-key': apiKey,
113
+ 'Datadog-Meta-Lang': 'nodejs',
114
+ 'Datadog-Meta-Lang-Version': process.version,
115
+ 'Datadog-Meta-Lang-Interpreter': process.versions.bun ? 'JavaScriptCore' : 'v8',
116
+ 'Datadog-Meta-Tracer-Version': tracerVersion,
117
+ },
118
+ timeout: 15_000,
119
+ url: this._url,
120
+ }
121
+
122
+ log.debug('Request to the agentless intake: %j', options)
123
+
124
+ request(data, options, (err, res, statusCode) => {
125
+ if (err) {
126
+ this._logRequestError(err, statusCode, count)
127
+ done()
128
+ return
129
+ }
130
+
131
+ log.debug('Response from the agentless intake: %s', res)
132
+ done()
133
+ })
134
+ }
135
+
136
+ /**
137
+ * Logs request errors with status-specific guidance.
138
+ * @param {Error} err - The error object
139
+ * @param {number} statusCode - HTTP status code (if available)
140
+ * @param {number} count - Number of spans that were being sent
141
+ */
142
+ _logRequestError (err, statusCode, count) {
143
+ if (statusCode === 401 || statusCode === 403) {
144
+ log.error(
145
+ 'Authentication failed sending %d span(s) (status %s). Verify DD_API_KEY is valid.',
146
+ count,
147
+ statusCode
148
+ )
149
+ } else if (statusCode === 404) {
150
+ log.error(
151
+ 'Span intake endpoint not found (status %s). Verify DD_SITE is correctly configured. %d span(s) dropped.',
152
+ statusCode,
153
+ count
154
+ )
155
+ } else if (statusCode === 429) {
156
+ log.error(
157
+ 'Rate limited by span intake (status 429). %d span(s) dropped.',
158
+ count
159
+ )
160
+ } else if (statusCode >= 500) {
161
+ log.error(
162
+ 'Span intake server error (status %s). %d span(s) dropped. This may be transient.',
163
+ statusCode,
164
+ count
165
+ )
166
+ } else if (statusCode) {
167
+ log.error(
168
+ 'Error sending agentless payload (status %s): %s. %d span(s) dropped.',
169
+ statusCode,
170
+ err.message,
171
+ count
172
+ )
173
+ } else {
174
+ log.error(
175
+ 'Network error sending %d span(s) to %s: %s',
176
+ count,
177
+ this._url?.hostname || 'unknown',
178
+ err.message
179
+ )
180
+ }
181
+ }
182
+ }
183
+
184
+ module.exports = AgentlessWriter
@@ -88,13 +88,13 @@ function request (data, options, callback) {
88
88
  zlib.gunzip(buffer, (err, result) => {
89
89
  if (err) {
90
90
  log.error('Could not gunzip response: %s', err.message)
91
- callback(null, '', res.statusCode)
91
+ callback(null, '', res.statusCode, res.headers)
92
92
  } else {
93
- callback(null, result.toString(), res.statusCode)
93
+ callback(null, result.toString(), res.statusCode, res.headers)
94
94
  }
95
95
  })
96
96
  } else {
97
- callback(null, buffer.toString(), res.statusCode)
97
+ callback(null, buffer.toString(), res.statusCode, res.headers)
98
98
  }
99
99
  } else {
100
100
  let errorMessage = ''
@@ -115,7 +115,7 @@ function request (data, options, callback) {
115
115
  const error = new log.NoTransmitError(errorMessage)
116
116
  error.status = res.statusCode
117
117
 
118
- callback(error, null, res.statusCode)
118
+ callback(error, null, res.statusCode, res.headers)
119
119
  }
120
120
  })
121
121
  }
@@ -4,7 +4,7 @@ const { channel } = require('dc-polyfill')
4
4
  const BaseLLMObsPlugin = require('../base')
5
5
  const { getModelProvider } = require('../../../../../datadog-plugin-ai/src/utils')
6
6
 
7
- const toolCreationCh = channel('dd-trace:vercel-ai:tool')
7
+ const toolCreationCh = channel('tracing:orchestrion:ai:tool:start')
8
8
  const setAttributesCh = channel('dd-trace:vercel-ai:span:setAttributes')
9
9
 
10
10
  const { MODEL_NAME, MODEL_PROVIDER, NAME } = require('../../constants/tags')
@@ -94,8 +94,10 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
94
94
 
95
95
  this.#toolCallIdsToName = {}
96
96
  this.#availableTools = new Set()
97
- toolCreationCh.subscribe(toolArgs => {
98
- this.#availableTools.add(toolArgs)
97
+ toolCreationCh.subscribe(ctx => {
98
+ const toolArgs = ctx.arguments
99
+ const tool = toolArgs[0] ?? {}
100
+ this.#availableTools.add(tool)
99
101
  })
100
102
 
101
103
  setAttributesCh.subscribe(({ ctx, attributes }) => {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { trace, ROOT_CONTEXT, propagation } = require('@opentelemetry/api')
4
4
  const { storage } = require('../../../datadog-core')
5
+ const { getAllBaggageItems, setBaggageItem, removeAllBaggageItems } = require('../baggage')
5
6
 
6
7
  const tracer = require('../../')
7
8
  const SpanContext = require('./span_context')
@@ -19,31 +20,26 @@ class ContextManager {
19
20
 
20
21
  const storedSpan = store ? trace.getSpan(store) : null
21
22
 
23
+ // Convert DD baggage to OTel format
24
+ const baggages = getAllBaggageItems()
25
+ const hasBaggage = Object.keys(baggages).length > 0
26
+ let otelBaggages
27
+ if (hasBaggage) {
28
+ const entries = {}
29
+ for (const [key, value] of Object.entries(baggages)) {
30
+ entries[key] = { value }
31
+ }
32
+ otelBaggages = propagation.createBaggage(entries)
33
+ }
34
+
22
35
  // If stored span wraps the active DD span, prefer the stored context
23
36
  if (storedSpan && storedSpan._ddSpan === activeSpan) {
24
- const baggages = JSON.parse(activeSpan.getAllBaggageItems())
25
- if (Object.keys(baggages).length > 0) {
26
- const entries = {}
27
- for (const [key, value] of Object.entries(baggages)) {
28
- entries[key] = { value }
29
- }
30
- const otelBaggages = propagation.createBaggage(entries)
31
- return propagation.setBaggage(store, otelBaggages)
32
- }
37
+ if (otelBaggages) return propagation.setBaggage(store, otelBaggages)
33
38
  return store
34
39
  }
35
40
 
36
41
  if (!activeSpan) {
37
- const storedBaggageItems = storedSpan?._spanContext?._ddContext?._baggageItems
38
- if (storedBaggageItems) {
39
- const baggages = storedBaggageItems
40
- const entries = {}
41
- for (const [key, value] of Object.entries(baggages)) {
42
- entries[key] = { value }
43
- }
44
- const otelBaggages = propagation.createBaggage(entries)
45
- return propagation.setBaggage(baseContext, otelBaggages)
46
- }
42
+ if (otelBaggages) return propagation.setBaggage(baseContext, otelBaggages)
47
43
  return baseContext
48
44
  }
49
45
 
@@ -53,18 +49,6 @@ class ContextManager {
53
49
  ddContext._otelSpanContext = new SpanContext(ddContext)
54
50
  }
55
51
 
56
- // Convert DD baggage to OTel format
57
- const baggages = JSON.parse(activeSpan.getAllBaggageItems())
58
- const hasBaggage = Object.keys(baggages).length > 0
59
- let otelBaggages
60
- if (hasBaggage) {
61
- const entries = {}
62
- for (const [key, value] of Object.entries(baggages)) {
63
- entries[key] = { value }
64
- }
65
- otelBaggages = propagation.createBaggage(entries)
66
- }
67
-
68
52
  if (store && trace.getSpanContext(store) === ddContext._otelSpanContext) {
69
53
  return otelBaggages ? propagation.setBaggage(store, otelBaggages) : store
70
54
  }
@@ -86,22 +70,11 @@ class ContextManager {
86
70
  if (baggages) {
87
71
  baggageItems = baggages.getAllEntries()
88
72
  }
89
- if (span && span._ddSpan) {
90
- // does otel always override datadog?
91
- span._ddSpan.removeAllBaggageItems()
92
- for (const baggage of baggageItems) {
93
- span._ddSpan.setBaggageItem(baggage[0], baggage[1].value)
94
- }
95
- return ddScope.activate(span._ddSpan, run)
96
- }
97
- // span instanceof NonRecordingSpan
98
- const ddContext = span?._spanContext?._ddContext
99
- if (ddContext && ddContext._baggageItems) {
100
- ddContext._baggageItems = {}
101
- for (const baggage of baggageItems) {
102
- ddContext._baggageItems[baggage[0]] = baggage[1].value
103
- }
73
+ removeAllBaggageItems()
74
+ for (const baggage of baggageItems) {
75
+ setBaggageItem(baggage[0], baggage[1].value)
104
76
  }
77
+ if (span && span._ddSpan) return ddScope.activate(span._ddSpan, run)
105
78
  return run()
106
79
  }
107
80
 
@@ -20,7 +20,7 @@ class OtlpHttpExporterBase {
20
20
  * Creates a new OtlpHttpExporterBase instance.
21
21
  *
22
22
  * @param {string} url - OTLP endpoint URL
23
- * @param {string} headers - Additional HTTP headers as comma-separated key=value string
23
+ * @param {string|undefined} headers - Additional HTTP headers as comma-separated key=value string
24
24
  * @param {number} timeout - Request timeout in milliseconds
25
25
  * @param {string} protocol - OTLP protocol (http/protobuf or http/json)
26
26
  * @param {string} defaultPath - Default path to use if URL has no path
@@ -117,11 +117,10 @@ class OtlpHttpExporterBase {
117
117
 
118
118
  /**
119
119
  * Parses additional HTTP headers from a comma-separated string.
120
- * @param {string} headersString - Comma-separated key=value pairs
120
+ * @param {string} [headersString=''] - Comma-separated key=value pairs
121
121
  * @returns {Record<string, string>} Parsed headers object
122
- * @private
123
122
  */
124
- #parseAdditionalHeaders (headersString) {
123
+ #parseAdditionalHeaders (headersString = '') {
125
124
  const headers = {}
126
125
  let key = ''
127
126
  let value = ''
@@ -670,10 +670,8 @@ class TextMapPropagator {
670
670
  if (!this._hasPropagationStyle('extract', 'baggage')) return
671
671
  if (!carrier?.baggage) return
672
672
  const baggages = carrier.baggage.split(',')
673
- const tagAllKeys = this._config.baggageTagKeys === '*'
674
- const keysToSpanTag = tagAllKeys
675
- ? undefined
676
- : new Set(this._config.baggageTagKeys.split(','))
673
+ const baggageTagKeys = new Set(this._config.baggageTagKeys)
674
+ const tagAllKeys = baggageTagKeys.has('*')
677
675
  for (const keyValue of baggages) {
678
676
  if (!keyValue) continue
679
677
 
@@ -707,7 +705,7 @@ class TextMapPropagator {
707
705
  return
708
706
  }
709
707
 
710
- if (spanContext && (tagAllKeys || keysToSpanTag?.has(key))) {
708
+ if (spanContext && (tagAllKeys || baggageTagKeys.has(key))) {
711
709
  spanContext._trace.tags['baggage.' + key] = value
712
710
  }
713
711
  setBaggageItem(key, value)
@@ -3,7 +3,6 @@
3
3
  // TODO (new internal tracer): use DC events for lifecycle metrics and test them
4
4
  const { performance } = require('perf_hooks')
5
5
  const now = performance.now.bind(performance)
6
- const dateNow = Date.now
7
6
  const util = require('util')
8
7
  const { channel } = require('dc-polyfill')
9
8
  const id = require('../id')
@@ -13,12 +12,15 @@ const log = require('../log')
13
12
  const { storage } = require('../../../datadog-core')
14
13
  const telemetryMetrics = require('../telemetry/metrics')
15
14
  const { getValueFromEnvSources } = require('../config/helper')
15
+ const { isTrue } = require('../util')
16
16
  const SpanContext = require('./span_context')
17
17
 
18
+ const dateNow = Date.now
19
+
18
20
  const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
19
21
 
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
+ const DD_TRACE_EXPERIMENTAL_STATE_TRACKING = isTrue(getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_STATE_TRACKING'))
23
+ const DD_TRACE_EXPERIMENTAL_SPAN_COUNTS = isTrue(getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_SPAN_COUNTS'))
22
24
 
23
25
  const unfinishedRegistry = createRegistry('unfinished')
24
26
  const finishedRegistry = createRegistry('finished')
@@ -251,7 +253,7 @@ class DatadogSpan {
251
253
  return
252
254
  }
253
255
 
254
- if (DD_TRACE_EXPERIMENTAL_STATE_TRACKING === 'true' && !this._spanContext._tags['service.name']) {
256
+ if (DD_TRACE_EXPERIMENTAL_STATE_TRACKING && !this._spanContext._tags['service.name']) {
255
257
  log.error('Finishing invalid span: %s', this)
256
258
  }
257
259
 
@@ -23,7 +23,7 @@ function findPkg () {
23
23
  if (filePath === undefined) return {}
24
24
 
25
25
  try {
26
- return require(filePath)
26
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
27
27
  } catch {
28
28
  return {}
29
29
  }
@@ -63,6 +63,8 @@ const {
63
63
  getPullRequestDiff,
64
64
  getModifiedFilesFromDiff,
65
65
  getPullRequestBaseBranch,
66
+ getSessionRequestErrorTags,
67
+ DD_CI_LIBRARY_CONFIGURATION_ERROR,
66
68
  TEST_IS_TEST_FRAMEWORK_WORKER,
67
69
  TEST_IS_NEW,
68
70
  TEST_IS_RUM_ACTIVE,
@@ -120,6 +122,7 @@ module.exports = class CiPlugin extends Plugin {
120
122
  this.fileLineToProbeId = new Map()
121
123
  this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
122
124
  this._testSuiteSpansByTestSuite = new Map()
125
+ this._pendingRequestErrorTags = []
123
126
 
124
127
  this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => {
125
128
  const { onDone, isParallel, frameworkVersion } = ctx
@@ -131,10 +134,15 @@ module.exports = class CiPlugin extends Plugin {
131
134
  this.tracer._exporter.getLibraryConfiguration(this.testConfiguration, (err, libraryConfig) => {
132
135
  if (err) {
133
136
  log.error('Library configuration could not be fetched. %s', err.message)
137
+ this._addRequestErrorTag(DD_CI_LIBRARY_CONFIGURATION_ERROR, err)
134
138
  } else {
135
139
  this.libraryConfig = libraryConfig
136
140
  }
137
141
 
142
+ const requestErrorTags = this.testSessionSpan
143
+ ? getSessionRequestErrorTags(this.testSessionSpan)
144
+ : Object.fromEntries(this._pendingRequestErrorTags.map(({ tag, value }) => [tag, value]))
145
+
138
146
  const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, isParallel, frameworkVersion)
139
147
  const metadataTags = {
140
148
  test: {
@@ -142,7 +150,7 @@ module.exports = class CiPlugin extends Plugin {
142
150
  },
143
151
  }
144
152
  this.tracer._exporter.addMetadataTags(metadataTags)
145
- onDone({ err, libraryConfig })
153
+ onDone({ err, libraryConfig, requestErrorTags })
146
154
  })
147
155
  })
148
156
 
@@ -200,6 +208,10 @@ module.exports = class CiPlugin extends Plugin {
200
208
  },
201
209
  integrationName: this.constructor.id,
202
210
  })
211
+ for (const { tag, value } of this._pendingRequestErrorTags) {
212
+ this.testSessionSpan.setTag(tag, value)
213
+ }
214
+ this._pendingRequestErrorTags = []
203
215
  // TODO: add telemetry tag when we can add `is_agentless_log_submission_enabled` for agentless log submission
204
216
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'session')
205
217
 
@@ -209,6 +221,7 @@ module.exports = class CiPlugin extends Plugin {
209
221
  [COMPONENT]: this.constructor.id,
210
222
  ...this.testEnvironmentMetadata,
211
223
  ...testModuleSpanMetadata,
224
+ ...getSessionRequestErrorTags(this.testSessionSpan),
212
225
  },
213
226
  integrationName: this.constructor.id,
214
227
  })
@@ -230,7 +243,10 @@ module.exports = class CiPlugin extends Plugin {
230
243
  this.addSub(`ci:${this.constructor.id}:itr:skipped-suites`, ({ skippedSuites, frameworkVersion }) => {
231
244
  const testCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
232
245
  for (const testSuite of skippedSuites) {
233
- const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, this.constructor.id)
246
+ const testSuiteMetadata = {
247
+ ...getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, this.constructor.id),
248
+ ...getSessionRequestErrorTags(this.testSessionSpan),
249
+ }
234
250
  if (this.itrCorrelationId) {
235
251
  testSuiteMetadata[ITR_CORRELATION_ID] = this.itrCorrelationId
236
252
  }
@@ -261,8 +277,10 @@ module.exports = class CiPlugin extends Plugin {
261
277
  this.tracer._exporter.getKnownTests(this.testConfiguration, (err, knownTests) => {
262
278
  if (err) {
263
279
  log.error('Known tests could not be fetched. %s', err.message)
264
- this.libraryConfig.isEarlyFlakeDetectionEnabled = false
265
- this.libraryConfig.isKnownTestsEnabled = false
280
+ if (this.libraryConfig) {
281
+ this.libraryConfig.isEarlyFlakeDetectionEnabled = false
282
+ this.libraryConfig.isKnownTestsEnabled = false
283
+ }
266
284
  }
267
285
  onDone({ err, knownTests })
268
286
  })
@@ -279,7 +297,9 @@ module.exports = class CiPlugin extends Plugin {
279
297
  this.tracer._exporter.getTestManagementTests(this.testConfiguration, (err, testManagementTests) => {
280
298
  if (err) {
281
299
  log.error('Test management tests could not be fetched. %s', err.message)
282
- this.libraryConfig.isTestManagementEnabled = false
300
+ if (this.libraryConfig) {
301
+ this.libraryConfig.isTestManagementEnabled = false
302
+ }
283
303
  }
284
304
  onDone({ err, testManagementTests })
285
305
  })
@@ -346,8 +366,15 @@ module.exports = class CiPlugin extends Plugin {
346
366
  span.meta = {
347
367
  ...span.meta,
348
368
  ...testSuiteTags,
369
+ ...getSessionRequestErrorTags(this.testSessionSpan),
349
370
  }
350
371
  }
372
+
373
+ // Jest and Vitest worker test spans are serialized in the worker and may not include
374
+ // request error tags; add them from the session span in the main process.
375
+ if ((span.name === 'jest.test' || span.name === 'vitest.test') && this.testSessionSpan) {
376
+ Object.assign(span.meta, getSessionRequestErrorTags(this.testSessionSpan))
377
+ }
351
378
  }
352
379
  this.tracer._exporter.export(trace)
353
380
  }
@@ -418,6 +445,30 @@ module.exports = class CiPlugin extends Plugin {
418
445
  }
419
446
  }
420
447
 
448
+ /**
449
+ * Adds a hidden _dd tag to the test session span when a test-optimization request fails.
450
+ * If the session span does not exist yet (e.g. library-configuration failed before session:start),
451
+ * the tag is queued and applied when the span is created.
452
+ * @param {string} tag - Tag name (e.g. DD_CI_LIBRARY_CONFIGURATION_ERROR)
453
+ * @param {Error} err - Request error
454
+ */
455
+ _addRequestErrorTag (tag, err) {
456
+ const value = 'true'
457
+ if (this.testSessionSpan) {
458
+ this.testSessionSpan.setTag(tag, value)
459
+ } else {
460
+ this._pendingRequestErrorTags.push({ tag, value })
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Returns request error tags from the test session span for propagation to module, suite and test spans.
466
+ * @returns {Record<string, string>}
467
+ */
468
+ getSessionRequestErrorTags () {
469
+ return getSessionRequestErrorTags(this.testSessionSpan)
470
+ }
471
+
421
472
  configure (config, shouldGetEnvironmentData = true) {
422
473
  super.configure(config)
423
474
 
@@ -529,6 +580,7 @@ module.exports = class CiPlugin extends Plugin {
529
580
  [TEST_SESSION_ID]: testSuiteSpan.context().toTraceId(),
530
581
  [TEST_COMMAND]: testSuiteSpan.context()._tags[TEST_COMMAND],
531
582
  [TEST_MODULE]: this.constructor.id,
583
+ ...getSessionRequestErrorTags(this.testSessionSpan),
532
584
  }
533
585
  if (testSuiteSpan.context()._parentId) {
534
586
  suiteTags[TEST_MODULE_ID] = testSuiteSpan.context()._parentId.toString(10)
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { PEER_SERVICE_KEY, PEER_SERVICE_SOURCE_KEY } = require('../constants')
4
+ const propagationHash = require('../propagation-hash')
4
5
  const StoragePlugin = require('./storage')
5
6
 
6
7
  class DatabasePlugin extends StoragePlugin {
@@ -59,13 +60,25 @@ class DatabasePlugin extends StoragePlugin {
59
60
  const dbmService = this.#getDbmServiceName(serviceName, peerData)
60
61
  const servicePropagation = this.#createDBMPropagationCommentService(dbmService, span, peerData)
61
62
 
63
+ let dbmComment = servicePropagation
64
+
65
+ // Add propagation hash if both process tags and SQL base hash injection are enabled
66
+ if (propagationHash.isEnabled() && this.config['dbm.injectSqlBaseHash']) {
67
+ const hashBase64 = propagationHash.getHashBase64()
68
+ if (hashBase64) {
69
+ dbmComment += `,ddsh='${hashBase64}'`
70
+ // Add hash to span meta as a tag
71
+ span.setTag('_dd.dbm.propagation_hash', hashBase64)
72
+ }
73
+ }
74
+
62
75
  if (disableFullMode || mode === 'service') {
63
- return servicePropagation
76
+ return dbmComment
64
77
  } else if (mode === 'full') {
65
78
  span.setTag('_dd.dbm_trace_injected', 'true')
66
79
  span._processor.sample(span)
67
80
  const traceparent = span._spanContext.toTraceparent()
68
- return `${servicePropagation},traceparent='${traceparent}'`
81
+ return `${dbmComment},traceparent='${traceparent}'`
69
82
  }
70
83
  }
71
84