dd-trace 5.103.0 → 5.104.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 (90) hide show
  1. package/index.d.ts +25 -3
  2. package/package.json +4 -3
  3. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
  4. package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
  5. package/packages/datadog-instrumentations/src/cucumber.js +103 -30
  6. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  7. package/packages/datadog-instrumentations/src/graphql.js +0 -5
  8. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  9. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  10. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -5
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +14 -13
  14. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  15. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  16. package/packages/datadog-instrumentations/src/jest.js +33 -36
  17. package/packages/datadog-instrumentations/src/kafkajs.js +25 -6
  18. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  19. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  20. package/packages/datadog-instrumentations/src/mocha/main.js +272 -91
  21. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  22. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  23. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  24. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  25. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  26. package/packages/datadog-instrumentations/src/pg.js +1 -1
  27. package/packages/datadog-instrumentations/src/playwright.js +22 -5
  28. package/packages/datadog-instrumentations/src/router.js +4 -2
  29. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  30. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +26 -19
  31. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  32. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  33. package/packages/datadog-plugin-kafkajs/src/producer.js +32 -0
  34. package/packages/datadog-plugin-mongodb-core/src/index.js +54 -19
  35. package/packages/datadog-plugin-redis/src/index.js +37 -2
  36. package/packages/datadog-plugin-undici/src/index.js +19 -0
  37. package/packages/datadog-plugin-vitest/src/index.js +19 -7
  38. package/packages/datadog-shimmer/src/shimmer.js +35 -0
  39. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  40. package/packages/dd-trace/src/appsec/index.js +10 -3
  41. package/packages/dd-trace/src/appsec/reporter.js +19 -5
  42. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  43. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  44. package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -0
  45. package/packages/dd-trace/src/config/supported-configurations.json +9 -0
  46. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  47. package/packages/dd-trace/src/datastreams/context.js +4 -2
  48. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +26 -19
  49. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  50. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  51. package/packages/dd-trace/src/id.js +17 -4
  52. package/packages/dd-trace/src/lambda/handler.js +2 -4
  53. package/packages/dd-trace/src/llmobs/sdk.js +10 -0
  54. package/packages/dd-trace/src/log/writer.js +3 -1
  55. package/packages/dd-trace/src/noop/span.js +3 -1
  56. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  57. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  58. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  59. package/packages/dd-trace/src/plugins/ci_plugin.js +3 -13
  60. package/packages/dd-trace/src/plugins/log_plugin.js +3 -1
  61. package/packages/dd-trace/src/plugins/tracing.js +5 -3
  62. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  63. package/packages/dd-trace/src/plugins/util/test.js +82 -0
  64. package/packages/dd-trace/src/plugins/util/web.js +11 -0
  65. package/packages/dd-trace/src/scope.js +7 -5
  66. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  67. package/vendor/dist/opentracing/LICENSE +0 -201
  68. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  69. package/vendor/dist/opentracing/constants.d.ts +0 -61
  70. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  71. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  72. package/vendor/dist/opentracing/functions.d.ts +0 -20
  73. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  74. package/vendor/dist/opentracing/index.d.ts +0 -12
  75. package/vendor/dist/opentracing/index.js +0 -1
  76. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  77. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  78. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  79. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  80. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  81. package/vendor/dist/opentracing/noop.d.ts +0 -8
  82. package/vendor/dist/opentracing/reference.d.ts +0 -33
  83. package/vendor/dist/opentracing/span.d.ts +0 -147
  84. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  85. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  86. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  87. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  88. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  89. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  90. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -7,6 +7,7 @@ const {
7
7
  TELEMETRY_ENDPOINT_PAYLOAD_SERIALIZATION_MS,
8
8
  TELEMETRY_ENDPOINT_PAYLOAD_EVENTS_COUNT,
9
9
  } = require('../ci-visibility/telemetry')
10
+ const { MsgpackChunk } = require('../msgpack')
10
11
  const { AgentEncoder } = require('./0.4')
11
12
  const { truncateSpan, normalizeSpan } = require('./tags-processors')
12
13
 
@@ -20,6 +21,9 @@ const TEST_AND_SPAN_KEYS_LENGTH = 11
20
21
 
21
22
  const INTAKE_SOFT_LIMIT = 2 * 1024 * 1024 // 2MB
22
23
 
24
+ // Prefix is ~1 KB in practice; `MsgpackChunk` resizes on overflow.
25
+ const PREFIX_CHUNK_INITIAL_SIZE = 2048
26
+
23
27
  function formatSpan (span) {
24
28
  let encodingVersion = ENCODING_VERSION
25
29
  if (span.type === 'test' && span.meta && span.meta.test_session_id) {
@@ -259,10 +263,6 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
259
263
  }
260
264
 
261
265
  _encode (bytes, trace) {
262
- if (this._isReset) {
263
- this._encodePayloadStart(bytes)
264
- this._isReset = false
265
- }
266
266
  const startTime = Date.now()
267
267
 
268
268
  const events = trace.map(formatSpan)
@@ -281,20 +281,28 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
281
281
 
282
282
  makePayload () {
283
283
  distributionMetric(TELEMETRY_ENDPOINT_PAYLOAD_EVENTS_COUNT, { endpoint: 'test_cycle' }, this._eventCount)
284
- const bytes = this._traceBytes
285
- const eventsOffset = this._eventsOffset
286
- const eventsCount = this._eventCount
287
284
 
288
- bytes.buffer[eventsOffset] = 0xDD
289
- bytes.buffer[eventsOffset + 1] = eventsCount >> 24
290
- bytes.buffer[eventsOffset + 2] = eventsCount >> 16
291
- bytes.buffer[eventsOffset + 3] = eventsCount >> 8
292
- bytes.buffer[eventsOffset + 4] = eventsCount
285
+ // Encode the payload prefix (version + metadata + events-array header) at flush time,
286
+ // not on the first `_encode`. The CI Visibility flow adds metadata across multiple
287
+ // diagnostic channels (`session:start` adds `test_session.name`, the async
288
+ // `library-configuration` callback adds capability tags). Any span finished between
289
+ // those calls would otherwise freeze the prefix with stale metadata.
290
+ const prefixBytes = new MsgpackChunk(PREFIX_CHUNK_INITIAL_SIZE)
291
+ this._encodePayloadStart(prefixBytes)
293
292
 
294
- const traceSize = bytes.length
295
- const buffer = Buffer.allocUnsafe(traceSize)
296
-
297
- bytes.buffer.copy(buffer, 0, 0, traceSize)
293
+ const eventsOffset = this._eventsOffset
294
+ const eventsCount = this._eventCount
295
+ prefixBytes.buffer[eventsOffset] = 0xDD
296
+ prefixBytes.buffer[eventsOffset + 1] = eventsCount >> 24
297
+ prefixBytes.buffer[eventsOffset + 2] = eventsCount >> 16
298
+ prefixBytes.buffer[eventsOffset + 3] = eventsCount >> 8
299
+ prefixBytes.buffer[eventsOffset + 4] = eventsCount
300
+
301
+ const eventsBytes = this._traceBytes
302
+ const totalSize = prefixBytes.length + eventsBytes.length
303
+ const buffer = Buffer.allocUnsafe(totalSize)
304
+ prefixBytes.buffer.copy(buffer, 0, 0, prefixBytes.length)
305
+ eventsBytes.buffer.copy(buffer, prefixBytes.length, 0, eventsBytes.length)
298
306
 
299
307
  this.reset()
300
308
 
@@ -302,7 +310,8 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
302
310
  }
303
311
 
304
312
  _encodePayloadStart (bytes) {
305
- // encodes the payload up to `events`. `events` will be encoded via _encode
313
+ // Encodes the payload up to (and including) the `events` array prefix. The 5 reserved
314
+ // bytes for the array length are patched in `makePayload`.
306
315
  const payload = {
307
316
  version: ENCODING_VERSION,
308
317
  metadata: {
@@ -346,7 +355,6 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
346
355
  this._encodeMap(bytes, payload.metadata.test_session_end)
347
356
  }
348
357
  this._encodeString(bytes, 'events')
349
- // Get offset of the events list to update the length of the array when calling `makePayload`
350
358
  this._eventsOffset = bytes.length
351
359
  bytes.reserve(5)
352
360
  }
@@ -354,7 +362,6 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
354
362
  reset () {
355
363
  this._reset()
356
364
  this._eventCount = 0
357
- this._isReset = true
358
365
  }
359
366
  }
360
367
 
@@ -4,6 +4,8 @@ const http = require('http')
4
4
  const https = require('https')
5
5
  const { storage } = require('../../../../datadog-core')
6
6
 
7
+ const legacyStorage = storage('legacy')
8
+
7
9
  const keepAlive = true
8
10
  const maxSockets = 1
9
11
 
@@ -26,7 +28,7 @@ function createAgentClass (BaseAgent) {
26
28
  }
27
29
 
28
30
  _noop (callback) {
29
- return storage('legacy').run({ noop: true }, callback)
31
+ return legacyStorage.run({ noop: true }, callback)
30
32
  }
31
33
  }
32
34
 
@@ -20,6 +20,8 @@ const {
20
20
  markEndpointReached,
21
21
  } = require('./retry')
22
22
 
23
+ const legacyStorage = storage('legacy')
24
+
23
25
  const maxActiveBufferSize = 1024 * 1024 * 64
24
26
 
25
27
  let activeBufferSize = 0
@@ -161,7 +163,7 @@ function request (data, options, callback) {
161
163
 
162
164
  activeBufferSize += options.headers['Content-Length'] ?? 0
163
165
 
164
- storage('legacy').run({ noop: true }, () => {
166
+ legacyStorage.run({ noop: true }, () => {
165
167
  let finished = false
166
168
  const finalize = () => {
167
169
  if (finished) return
@@ -13,6 +13,12 @@ let batch = 0
13
13
  class Identifier {
14
14
  /** @type {number[] | Uint8Array} */
15
15
  #buffer
16
+ /** @type {bigint | undefined} */
17
+ #bigInt
18
+ /** @type {string | undefined} */
19
+ #stringHex
20
+ /** @type {string | undefined} */
21
+ #stringDecimal
16
22
 
17
23
  /**
18
24
  * @param {string} value
@@ -29,16 +35,23 @@ class Identifier {
29
35
  * @returns {string}
30
36
  */
31
37
  toString (radix = 16) {
32
- return radix === 16
33
- ? Buffer.from(this.#buffer).toString('hex')
34
- : toNumberString(this.#buffer, radix)
38
+ if (radix === 16) {
39
+ this.#stringHex ??= Buffer.from(this.#buffer).toString('hex')
40
+ return this.#stringHex
41
+ }
42
+ if (radix === 10) {
43
+ this.#stringDecimal ??= toNumberString(this.#buffer, 10)
44
+ return this.#stringDecimal
45
+ }
46
+ return toNumberString(this.#buffer, radix)
35
47
  }
36
48
 
37
49
  /**
38
50
  * @returns {bigint}
39
51
  */
40
52
  toBigInt () {
41
- return Buffer.from(this.#buffer).readBigUInt64BE(0)
53
+ this.#bigInt ??= Buffer.from(this.#buffer).readBigUInt64BE(0)
54
+ return this.#bigInt
42
55
  }
43
56
 
44
57
  /**
@@ -6,8 +6,6 @@ const { ERROR_MESSAGE, ERROR_TYPE } = require('../constants')
6
6
  const { ImpendingTimeout } = require('./runtime/errors')
7
7
  const { extractContext } = require('./context')
8
8
 
9
- const globalTracer = global._ddtrace
10
- const tracer = globalTracer._tracer
11
9
  const timeoutChannel = channel('apm:aws:lambda:timeout')
12
10
  // Always crash the flushes when a message is received
13
11
  // from this channel.
@@ -25,8 +23,7 @@ let __lambdaTimeout
25
23
  */
26
24
  function checkTimeout (context) {
27
25
  const remainingTimeInMillis = context.getRemainingTimeInMillis()
28
-
29
- const apmFlushDeadline = tracer._config.DD_APM_FLUSH_DEADLINE_MILLISECONDS
26
+ const apmFlushDeadline = global._ddtrace._tracer._config.DD_APM_FLUSH_DEADLINE_MILLISECONDS
30
27
 
31
28
  __lambdaTimeout = setTimeout(() => {
32
29
  timeoutChannel.publish()
@@ -42,6 +39,7 @@ function checkTimeout (context) {
42
39
  * Once that is done, it finishes the last span.
43
40
  */
44
41
  function crashFlush () {
42
+ const tracer = global._ddtrace._tracer
45
43
  const activeSpan = tracer.scope().active()
46
44
  if (activeSpan === null) {
47
45
  log.debug('An impending timeout was reached, but no root span was found. No error will be tagged.')
@@ -55,6 +55,11 @@ class LLMObs extends NoopLLMObs {
55
55
  }
56
56
 
57
57
  enable (options = {}) {
58
+ logger.warn(
59
+ 'Enabling LLM Observability via `llmobs.enable()` is deprecated and will be removed in dd-trace@7.0.0. ' +
60
+ 'Please instantiate LLM Observability via DD_LLMOBS_ENABLED or `tracer.init({ llmobs: ...options })`.'
61
+ )
62
+
58
63
  if (this.enabled) {
59
64
  logger.debug('LLMObs is already enabled.')
60
65
  return
@@ -79,6 +84,11 @@ class LLMObs extends NoopLLMObs {
79
84
  }
80
85
 
81
86
  disable () {
87
+ logger.warn(
88
+ 'Disabling LLM Observability via `llmobs.disable()` is deprecated and will be removed in dd-trace@7.0.0. ' +
89
+ 'Set DD_LLMOBS_ENABLED=false to disable LLM Observability.'
90
+ )
91
+
82
92
  if (!this.enabled) {
83
93
  logger.debug('LLMObs is already disabled.')
84
94
  return
@@ -3,6 +3,8 @@
3
3
  const { storage } = require('../../../datadog-core')
4
4
  const { LogChannel } = require('./channels')
5
5
 
6
+ const legacyStorage = storage('legacy')
7
+
6
8
  const defaultLogger = {
7
9
  debug: msg => console.debug(msg), /* eslint-disable-line no-console */
8
10
  info: msg => console.info(msg), /* eslint-disable-line no-console */
@@ -15,7 +17,7 @@ let logger = defaultLogger
15
17
  let logChannel = new LogChannel()
16
18
 
17
19
  function withNoop (fn) {
18
- storage('legacy').run({ noop: true }, fn)
20
+ legacyStorage.run({ noop: true }, fn)
19
21
  }
20
22
 
21
23
  function toggleSubscription (enable, level) {
@@ -4,9 +4,11 @@ const id = require('../id')
4
4
  const { storage } = require('../../../datadog-core') // TODO: noop storage?
5
5
  const NoopSpanContext = require('./span_context')
6
6
 
7
+ const legacyStorage = storage('legacy')
8
+
7
9
  class NoopSpan {
8
10
  constructor (tracer, parent) {
9
- this._store = storage('legacy').getHandle()
11
+ this._store = legacyStorage.getHandle()
10
12
  this._noopTracer = tracer
11
13
  this._noopContext = this._createContext(parent)
12
14
  }
@@ -8,8 +8,14 @@ const {
8
8
  EVP_PAYLOAD_SIZE_LIMIT,
9
9
  EVP_EVENT_SIZE_LIMIT,
10
10
  } = require('../constants/constants')
11
+ const log = require('../../log')
11
12
  const BaseFFEWriter = require('./base')
12
13
 
14
+ // Disabled-state cap. Drops invalidate experiment results because the provider's
15
+ // exposure dedupe cache keeps masking dropped events after recovery. The first
16
+ // drop emits a warning and `droppedEventCount` accumulates the cumulative loss.
17
+ const PENDING_MAX_EVENTS = 1000
18
+
13
19
  /**
14
20
  * @typedef {object} ExposureEvent
15
21
  * @property {number} timestamp - Unix timestamp in milliseconds
@@ -42,11 +48,21 @@ const BaseFFEWriter = require('./base')
42
48
  * ExposuresWriter is responsible for sending exposure events to the Datadog Agent.
43
49
  */
44
50
  class ExposuresWriter extends BaseFFEWriter {
51
+ // Disabled until the agent strategy probe resolves.
52
+ #enabled = false
53
+
54
+ /** @type {ExposureEvent[]} */
55
+ #pendingEvents = []
56
+
57
+ /** @type {ExposureContext} */
58
+ #context
59
+
60
+ #dropWarned = false
61
+
45
62
  /**
46
63
  * @param {import('../../config/config-base')} config - Tracer configuration object
47
64
  */
48
65
  constructor (config) {
49
- // Build full EVP endpoint path
50
66
  const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/+$/, '')
51
67
  const endpoint = EXPOSURES_ENDPOINT.replace(/^\/+/, '')
52
68
  const fullEndpoint = `${basePath}/${endpoint}`
@@ -60,33 +76,33 @@ class ExposuresWriter extends BaseFFEWriter {
60
76
  [EVP_SUBDOMAIN_HEADER_NAME]: EVP_SUBDOMAIN_VALUE,
61
77
  },
62
78
  })
63
- this._enabled = false // Start disabled until agent strategy is set
64
- this._pendingEvents = [] // Buffer events until enabled
65
79
 
80
+ /** @type {ExposureContext} */
66
81
  const context = {
67
82
  service: config.service,
68
83
  }
69
- // Only include version and env if they are defined
84
+
70
85
  if (config.version !== undefined) {
71
86
  context.version = config.version
72
87
  }
88
+
73
89
  if (config.env !== undefined) {
74
90
  context.env = config.env
75
91
  }
76
92
 
77
- this._context = context
93
+ this.#context = context
78
94
  }
79
95
 
80
96
  /**
81
97
  * @param {boolean} enabled - Whether to enable the writer
82
98
  */
83
99
  setEnabled (enabled) {
84
- this._enabled = enabled
100
+ this.#enabled = enabled
85
101
 
86
- if (enabled && this._pendingEvents.length > 0) {
102
+ if (enabled && this.#pendingEvents.length > 0) {
87
103
  // Flush all pending events as a batch
88
- super.append(this._pendingEvents)
89
- this._pendingEvents = []
104
+ super.append(this.#pendingEvents)
105
+ this.#pendingEvents = []
90
106
  }
91
107
  }
92
108
 
@@ -95,24 +111,38 @@ class ExposuresWriter extends BaseFFEWriter {
95
111
  * @param {ExposureEvent|ExposureEvent[]} events - Exposure event(s) to append
96
112
  */
97
113
  append (events) {
98
- if (!this._enabled) {
99
- // Buffer events until writer is ready
100
- if (Array.isArray(events)) {
101
- this._pendingEvents.push(...events)
102
- } else {
103
- this._pendingEvents.push(events)
104
- }
114
+ if (this.#enabled) {
115
+ super.append(events)
105
116
  return
106
117
  }
107
- super.append(events)
118
+
119
+ const eventArray = Array.isArray(events) ? events : [events]
120
+ this.#pendingEvents.push(...eventArray)
121
+ if (this.#pendingEvents.length > PENDING_MAX_EVENTS) {
122
+ const dropped = this.#pendingEvents.length - PENDING_MAX_EVENTS
123
+ this.#pendingEvents.splice(0, dropped)
124
+ this._droppedEvents += dropped
125
+ if (!this.#dropWarned) {
126
+ this.#dropWarned = true
127
+ log.warn(
128
+ '%s dropped exposure event(s) at cap %d. This may invalidate experiment results.',
129
+ this.constructor.name, PENDING_MAX_EVENTS)
130
+ }
131
+ }
132
+ }
133
+
134
+ /**
135
+ * @returns {number} Cumulative number of exposure events dropped due to buffer overflow.
136
+ */
137
+ get droppedEventCount () {
138
+ return this._droppedEvents
108
139
  }
109
140
 
110
141
  /**
111
142
  * Flushes buffered exposure events to the agent
112
143
  */
113
144
  flush () {
114
- if (!this._enabled) {
115
- // Don't flush when disabled
145
+ if (!this.#enabled) {
116
146
  return
117
147
  }
118
148
  super.flush()
@@ -125,6 +155,7 @@ class ExposuresWriter extends BaseFFEWriter {
125
155
  */
126
156
  makePayload (events) {
127
157
  const formattedEvents = events.map(event => {
158
+ /** @type {ExposureEvent} */
128
159
  return {
129
160
  timestamp: event.timestamp || Date.now(),
130
161
  allocation: {
@@ -145,7 +176,7 @@ class ExposuresWriter extends BaseFFEWriter {
145
176
  })
146
177
 
147
178
  return {
148
- context: this._context,
179
+ context: this.#context,
149
180
  exposures: formattedEvents,
150
181
  }
151
182
  }
@@ -336,7 +336,7 @@ class MetricAggregator {
336
336
  }
337
337
  }
338
338
 
339
- this.#applyDeltaTemporality(metricsMap, lastExportedState)
339
+ this.#applyDeltaTemporality(metricsMap.values(), lastExportedState)
340
340
  return metricsMap
341
341
  }
342
342
 
@@ -3,13 +3,15 @@
3
3
  const { storage } = require('../../../datadog-core')
4
4
  const TracingPlugin = require('./tracing')
5
5
 
6
+ const legacyStorage = storage('legacy')
7
+
6
8
  class ApolloBasePlugin extends TracingPlugin {
7
9
  static id = 'apollo.gateway'
8
10
  static type = 'web'
9
11
  static kind = 'server'
10
12
 
11
13
  bindStart (ctx) {
12
- const store = storage('legacy').getStore()
14
+ const store = legacyStorage.getStore()
13
15
  const childOf = store ? /** @type {import('../opentracing/span') | undefined} */ (store.span) : null
14
16
 
15
17
  const span = this.startSpan(this.getOperationName(), {
@@ -82,6 +82,8 @@ const {
82
82
  DD_CAPABILITIES_TEST_IMPACT_ANALYSIS,
83
83
  } = require('./util/test')
84
84
 
85
+ const legacyStorage = storage('legacy')
86
+
85
87
  const FRAMEWORK_TO_TRIMMED_COMMAND = {
86
88
  vitest: 'vitest run',
87
89
  mocha: 'mocha',
@@ -147,7 +149,7 @@ module.exports = class CiPlugin extends Plugin {
147
149
 
148
150
  this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => {
149
151
  const { onDone, frameworkVersion } = ctx
150
- ctx.currentStore = storage('legacy').getStore()
152
+ ctx.currentStore = legacyStorage.getStore()
151
153
 
152
154
  if (!this.tracer._exporter || !this.tracer._exporter.getLibraryConfiguration) {
153
155
  return onDone({ err: new Error('Test optimization was not initialized correctly') })
@@ -249,18 +251,6 @@ module.exports = class CiPlugin extends Plugin {
249
251
  integrationName: this.constructor.id,
250
252
  })
251
253
  setItrSkippingEnabledTagFromLibraryConfig(this, frameworkVersion)
252
- // only for vitest
253
- // These are added for the worker threads to use
254
- if (this.constructor.id === 'vitest') {
255
- // TODO: Figure out alternative ways to pass this information to the worker threads
256
- // eslint-disable-next-line eslint-rules/eslint-process-env
257
- process.env.DD_CIVISIBILITY_TEST_SESSION_ID = this.testSessionSpan.context().toTraceId()
258
- // eslint-disable-next-line eslint-rules/eslint-process-env
259
- process.env.DD_CIVISIBILITY_TEST_MODULE_ID = this.testModuleSpan.context().toSpanId()
260
- // eslint-disable-next-line eslint-rules/eslint-process-env
261
- process.env.DD_CIVISIBILITY_TEST_COMMAND = this.command
262
- }
263
-
264
254
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'module')
265
255
  })
266
256
 
@@ -4,6 +4,8 @@ const { LOG } = require('../../../../ext/formats')
4
4
  const { storage } = require('../../../datadog-core')
5
5
  const Plugin = require('./plugin')
6
6
 
7
+ const legacyStorage = storage('legacy')
8
+
7
9
  function messageProxy (message, holder) {
8
10
  return new Proxy(message, {
9
11
  get (target, key) {
@@ -38,7 +40,7 @@ module.exports = class LogPlugin extends Plugin {
38
40
  super(...args)
39
41
 
40
42
  this.addSub(`apm:${this.constructor.id}:log`, (arg) => {
41
- const span = storage('legacy').getStore()?.span
43
+ const span = legacyStorage.getStore()?.span
42
44
 
43
45
  // NOTE: This needs to run whether or not there is a span
44
46
  // so service, version, and env will always get injected.
@@ -5,6 +5,8 @@ const analyticsSampler = require('../analytics_sampler')
5
5
  const { COMPONENT, SVC_SRC_KEY } = require('../constants')
6
6
  const Plugin = require('./plugin')
7
7
 
8
+ const legacyStorage = storage('legacy')
9
+
8
10
  class TracingPlugin extends Plugin {
9
11
  constructor (...args) {
10
12
  super(...args)
@@ -16,7 +18,7 @@ class TracingPlugin extends Plugin {
16
18
  }
17
19
 
18
20
  get activeSpan () {
19
- const store = /** @type {{ span?: import('../../../..').Span }} */ (storage('legacy').getStore())
21
+ const store = /** @type {{ span?: import('../../../..').Span }} */ (legacyStorage.getStore())
20
22
 
21
23
  return store?.span
22
24
  }
@@ -193,7 +195,7 @@ class TracingPlugin extends Plugin {
193
195
  serviceSource = service ? 'opt.plugin' : undefined
194
196
  }
195
197
 
196
- const store = storage('legacy').getStore()
198
+ const store = legacyStorage.getStore()
197
199
  if (store && childOf === undefined) {
198
200
  childOf = /** @type {import('../opentracing/span') | undefined} */ (store.span)
199
201
  }
@@ -226,7 +228,7 @@ class TracingPlugin extends Plugin {
226
228
 
227
229
  // TODO: Remove this after migration to TracingChannel is done.
228
230
  if (enterOrCtx === true) {
229
- storage('legacy').enterWith({ ...store, span })
231
+ legacyStorage.enterWith({ ...store, span })
230
232
  } else if (enterOrCtx) {
231
233
  enterOrCtx.parentStore = store
232
234
  enterOrCtx.currentStore = { ...store, span }
@@ -37,6 +37,8 @@ const {
37
37
  const { filterSensitiveInfoFromRepository } = require('./url')
38
38
  const { cachedExec } = require('./git-cache')
39
39
 
40
+ const legacyStorage = storage('legacy')
41
+
40
42
  const GIT_REV_LIST_MAX_BUFFER = 12 * 1024 * 1024 // 12MB
41
43
 
42
44
  function sanitizedExec (
@@ -47,7 +49,7 @@ function sanitizedExec (
47
49
  errorMetric,
48
50
  shouldTrim = true
49
51
  ) {
50
- return storage('legacy').run({ noop: true }, () => {
52
+ return legacyStorage.run({ noop: true }, () => {
51
53
  let startTime
52
54
  if (operationMetric) {
53
55
  incrementCountMetric(operationMetric.name, operationMetric.tags)
@@ -267,6 +267,87 @@ function getSessionItrSkippingEnabledTags (sessionSpan) {
267
267
  return {}
268
268
  }
269
269
 
270
+ /**
271
+ * Starts supported test optimization requests together when each feature is enabled.
272
+ *
273
+ * @param {{
274
+ * isKnownTestsEnabled: boolean,
275
+ * isTestManagementTestsEnabled: boolean,
276
+ * isSuitesSkippingEnabled?: boolean,
277
+ * getKnownTests: () => Promise<object>,
278
+ * getTestManagementTests: () => Promise<object>,
279
+ * getSkippableSuites?: () => Promise<object>
280
+ * }} options - Test optimization request factories.
281
+ * @returns {Promise<{
282
+ * knownTestsResponse?: object,
283
+ * testManagementTestsResponse?: object,
284
+ * skippableSuitesResponse?: object
285
+ * }>}
286
+ */
287
+ function getTestOptimizationRequestResults ({
288
+ isKnownTestsEnabled,
289
+ isTestManagementTestsEnabled,
290
+ isSuitesSkippingEnabled,
291
+ getKnownTests,
292
+ getTestManagementTests,
293
+ getSkippableSuites,
294
+ }) {
295
+ const requestPromises = []
296
+ const responseNames = []
297
+
298
+ if (isKnownTestsEnabled) {
299
+ addTestOptimizationRequest(requestPromises, responseNames, 'knownTestsResponse', getKnownTests)
300
+ }
301
+
302
+ if (isTestManagementTestsEnabled) {
303
+ addTestOptimizationRequest(
304
+ requestPromises,
305
+ responseNames,
306
+ 'testManagementTestsResponse',
307
+ getTestManagementTests
308
+ )
309
+ }
310
+
311
+ if (isSuitesSkippingEnabled && getSkippableSuites) {
312
+ addTestOptimizationRequest(requestPromises, responseNames, 'skippableSuitesResponse', getSkippableSuites)
313
+ }
314
+
315
+ if (!requestPromises.length) {
316
+ return Promise.resolve({})
317
+ }
318
+
319
+ return Promise.allSettled(requestPromises).then(requestResults => {
320
+ const responses = {}
321
+
322
+ for (let index = 0; index < requestResults.length; index++) {
323
+ const requestResult = requestResults[index]
324
+ responses[responseNames[index]] = requestResult.status === 'fulfilled'
325
+ ? requestResult.value
326
+ : { err: requestResult.reason }
327
+ }
328
+
329
+ return responses
330
+ })
331
+ }
332
+
333
+ /**
334
+ * Starts a test optimization request.
335
+ *
336
+ * @param {Promise<object>[]} requestPromises - Test optimization request promises.
337
+ * @param {string[]} responseNames - Response keys matching request promises.
338
+ * @param {string} responseName - Response key for this request.
339
+ * @param {() => Promise<object>} getRequest - Test optimization request factory.
340
+ */
341
+ function addTestOptimizationRequest (requestPromises, responseNames, responseName, getRequest) {
342
+ responseNames.push(responseName)
343
+
344
+ try {
345
+ requestPromises.push(Promise.resolve(getRequest()))
346
+ } catch (err) {
347
+ requestPromises.push(Promise.reject(err))
348
+ }
349
+ }
350
+
270
351
  module.exports = {
271
352
  TEST_CODE_OWNERS,
272
353
  TEST_SESSION_NAME,
@@ -383,6 +464,7 @@ module.exports = {
383
464
  DD_CI_LIBRARY_CONFIGURATION_ERROR_KNOWN_TESTS,
384
465
  DD_CI_LIBRARY_CONFIGURATION_ERROR_TEST_MANAGEMENT_TESTS,
385
466
  getSessionItrSkippingEnabledTags,
467
+ getTestOptimizationRequestResults,
386
468
  checkShaDiscrepancies,
387
469
  getPullRequestDiff,
388
470
  getPullRequestBaseBranch,
@@ -400,6 +400,17 @@ function addRequestTags (context, spanType) {
400
400
  }
401
401
  }
402
402
 
403
+ // Datadog scan/test markers, tagged unconditionally so the API endpoint
404
+ // reducer can keep scan/test traffic out of the API inventory.
405
+ const endpointScan = req.headers['x-datadog-endpoint-scan']
406
+ if (endpointScan !== undefined) {
407
+ span.setTag(`${HTTP_REQUEST_HEADERS}.x-datadog-endpoint-scan`, endpointScan)
408
+ }
409
+ const securityTest = req.headers['x-datadog-security-test']
410
+ if (securityTest !== undefined) {
411
+ span.setTag(`${HTTP_REQUEST_HEADERS}.x-datadog-security-test`, securityTest)
412
+ }
413
+
403
414
  addHeaders(context)
404
415
  }
405
416