dd-trace 6.2.0 → 6.4.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 (76) hide show
  1. package/README.md +1 -1
  2. package/index.electron.js +3 -0
  3. package/package.json +8 -5
  4. package/packages/datadog-instrumentations/src/ai.js +29 -24
  5. package/packages/datadog-instrumentations/src/cookie.js +7 -1
  6. package/packages/datadog-instrumentations/src/cucumber.js +4 -3
  7. package/packages/datadog-instrumentations/src/express.js +20 -2
  8. package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -1
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +30 -54
  11. package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
  12. package/packages/datadog-instrumentations/src/http/server.js +27 -0
  13. package/packages/datadog-instrumentations/src/http2/server.js +143 -17
  14. package/packages/datadog-instrumentations/src/jest.js +10 -4
  15. package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +248 -1
  16. package/packages/datadog-instrumentations/src/playwright.js +40 -3
  17. package/packages/datadog-instrumentations/src/router.js +180 -105
  18. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
  19. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
  20. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
  21. package/packages/datadog-plugin-aws-sdk/src/util.js +0 -22
  22. package/packages/datadog-plugin-azure-functions/src/index.js +2 -1
  23. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -5
  24. package/packages/datadog-plugin-graphql/src/execute.js +90 -14
  25. package/packages/datadog-plugin-graphql/src/parse.js +22 -1
  26. package/packages/datadog-plugin-graphql/src/request.js +33 -2
  27. package/packages/datadog-plugin-graphql/src/utils.js +42 -0
  28. package/packages/datadog-plugin-graphql/src/validate.js +13 -1
  29. package/packages/datadog-plugin-http2/src/server.js +38 -6
  30. package/packages/datadog-plugin-jest/src/util.js +21 -6
  31. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +144 -26
  32. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/utils.js +30 -0
  33. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  34. package/packages/datadog-plugin-vitest/src/index.js +4 -2
  35. package/packages/datadog-shimmer/src/shimmer.js +9 -2
  36. package/packages/dd-trace/index.electron.js +3 -0
  37. package/packages/dd-trace/index.js +2 -37
  38. package/packages/dd-trace/src/appsec/channels.js +1 -0
  39. package/packages/dd-trace/src/appsec/index.js +6 -3
  40. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  41. package/packages/dd-trace/src/bootstrap.js +39 -0
  42. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +23 -16
  43. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +14 -2
  44. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
  45. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
  46. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +55 -5
  47. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +8 -1
  48. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  49. package/packages/dd-trace/src/config/config-types.d.ts +8 -0
  50. package/packages/dd-trace/src/config/defaults.js +20 -15
  51. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  52. package/packages/dd-trace/src/config/index.js +13 -3
  53. package/packages/dd-trace/src/config/parsers.js +79 -0
  54. package/packages/dd-trace/src/config/supported-configurations.json +17 -0
  55. package/packages/dd-trace/src/constants.js +7 -0
  56. package/packages/dd-trace/src/datastreams/pathway.js +6 -4
  57. package/packages/dd-trace/src/feature-registry.js +22 -0
  58. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  59. package/packages/dd-trace/src/llmobs/index.js +10 -2
  60. package/packages/dd-trace/src/llmobs/tagger.js +19 -1
  61. package/packages/dd-trace/src/noop/proxy.js +5 -4
  62. package/packages/dd-trace/src/openfeature/register.js +46 -0
  63. package/packages/dd-trace/src/opentelemetry/span_processor.js +7 -5
  64. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +32 -16
  65. package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
  66. package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
  67. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
  68. package/packages/dd-trace/src/opentracing/tracer.js +7 -1
  69. package/packages/dd-trace/src/plugins/util/test.js +9 -4
  70. package/packages/dd-trace/src/plugins/util/web.js +12 -0
  71. package/packages/dd-trace/src/proxy.js +21 -9
  72. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +20 -11
  73. package/packages/dd-trace/src/standalone/index.js +0 -22
  74. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  75. package/vendor/dist/protobufjs/index.js +1 -1
  76. package/vendor/dist/protobufjs/minimal/index.js +1 -1
@@ -0,0 +1,46 @@
1
+ 'use strict'
2
+
3
+ const { registerFeature } = require('../feature-registry')
4
+
5
+ const noop = new (require('./noop'))()
6
+
7
+ /**
8
+ * @param {import('../proxy')} proxy
9
+ * @returns {boolean}
10
+ */
11
+ function hasFlaggingProvider (proxy) {
12
+ const descriptor = Reflect.getOwnPropertyDescriptor(proxy, 'openfeature')
13
+
14
+ return descriptor?.value !== undefined && descriptor.value !== noop
15
+ }
16
+
17
+ registerFeature({
18
+ name: 'openfeature',
19
+ noop,
20
+ factory: () => require('./index'),
21
+
22
+ /**
23
+ * @param {object} rc - RemoteConfig instance
24
+ * @param {import('../config/config-base')} config
25
+ * @param {import('../proxy')} proxy
26
+ */
27
+ remoteConfig (rc, config, proxy) {
28
+ const openfeatureRemoteConfig = require('./remote_config')
29
+ openfeatureRemoteConfig.enable(rc, config, () => proxy.openfeature)
30
+ },
31
+
32
+ /**
33
+ * @param {import('../config/config-base')} config
34
+ * @param {import('../tracer')} tracer
35
+ * @param {import('../proxy')} proxy
36
+ * @param {Function} lazyProxy
37
+ */
38
+ enable (config, tracer, proxy, lazyProxy) {
39
+ if (config.experimental.flaggingProvider.enabled) {
40
+ proxy._modules.openfeature.enable(config)
41
+ if (!hasFlaggingProvider(proxy)) {
42
+ lazyProxy(proxy, 'openfeature', () => require('./flagging_provider'), tracer, config)
43
+ }
44
+ }
45
+ },
46
+ })
@@ -14,32 +14,34 @@ class NoopSpanProcessor {
14
14
  }
15
15
 
16
16
  class MultiSpanProcessor extends NoopSpanProcessor {
17
+ #processors
18
+
17
19
  constructor (spanProcessors) {
18
20
  super()
19
- this._processors = spanProcessors
21
+ this.#processors = spanProcessors
20
22
  }
21
23
 
22
24
  forceFlush () {
23
25
  return Promise.all(
24
- this._processors.map(p => p.forceFlush())
26
+ this.#processors.map(p => p.forceFlush())
25
27
  )
26
28
  }
27
29
 
28
30
  onStart (span, context) {
29
- for (const processor of this._processors) {
31
+ for (const processor of this.#processors) {
30
32
  processor.onStart(span, context)
31
33
  }
32
34
  }
33
35
 
34
36
  onEnd (span) {
35
- for (const processor of this._processors) {
37
+ for (const processor of this.#processors) {
36
38
  processor.onEnd(span)
37
39
  }
38
40
  }
39
41
 
40
42
  shutdown () {
41
43
  return Promise.all(
42
- this._processors.map(p => p.shutdown())
44
+ this.#processors.map(p => p.shutdown())
43
45
  )
44
46
  }
45
47
  }
@@ -10,46 +10,62 @@ const { MultiSpanProcessor, NoopSpanProcessor } = require('./span_processor')
10
10
  const Tracer = require('./tracer')
11
11
 
12
12
  class TracerProvider {
13
+ #activeProcessor = new NoopSpanProcessor()
14
+ #contextManager = new ContextManager()
15
+ #processors = []
16
+ #tracers = new Map()
17
+
13
18
  constructor (config = {}) {
14
19
  this.config = config
15
20
  this.resource = config.resource
16
21
 
17
- this._processors = []
18
- this._tracers = new Map()
19
- this._activeProcessor = new NoopSpanProcessor()
20
- this._contextManager = new ContextManager()
22
+ // @opentelemetry/sdk-trace 2.x (used by @opentelemetry/sdk-node 0.220+)
23
+ // dropped `addSpanProcessor` and hands the processors to the provider
24
+ // constructor instead. Wire them the same way the 1.x `addSpanProcessor`
25
+ // path does, so a NodeSDK configured with a trace exporter or custom
26
+ // processors still delivers onStart/onEnd to them.
27
+ if (Array.isArray(config.spanProcessors)) {
28
+ for (const spanProcessor of config.spanProcessors) {
29
+ this.addSpanProcessor(spanProcessor)
30
+ }
31
+ }
21
32
  }
22
33
 
23
34
  getTracer (name = 'opentelemetry', version = '0.0.0', options) {
24
35
  const key = `${name}@${version}`
25
- if (!this._tracers.has(key)) {
26
- this._tracers.set(key, new Tracer(
36
+ if (!this.#tracers.has(key)) {
37
+ this.#tracers.set(key, new Tracer(
27
38
  { ...options, name, version },
28
39
  this.config,
29
40
  this
30
41
  ))
31
42
  }
32
- return this._tracers.get(key)
43
+ return this.#tracers.get(key)
33
44
  }
34
45
 
46
+ /**
47
+ * @param {NoopSpanProcessor} spanProcessor
48
+ */
35
49
  addSpanProcessor (spanProcessor) {
36
- if (!this._processors.length) {
37
- this._activeProcessor.shutdown()
50
+ if (this.#processors.includes(spanProcessor)) return
51
+
52
+ if (!this.#processors.length) {
53
+ this.#activeProcessor.shutdown()
38
54
  }
39
- this._processors.push(spanProcessor)
40
- this._activeProcessor = new MultiSpanProcessor(
41
- this._processors
55
+ this.#processors.push(spanProcessor)
56
+ this.#activeProcessor = new MultiSpanProcessor(
57
+ this.#processors
42
58
  )
43
59
  }
44
60
 
45
61
  getActiveSpanProcessor () {
46
- return this._activeProcessor
62
+ return this.#activeProcessor
47
63
  }
48
64
 
49
65
  // Not actually required by the SDK spec, but the official Node.js SDK does
50
66
  // this and the docs reflect that so we should do this too for familiarity.
51
67
  register (config = {}) {
52
- context.setGlobalContextManager(this._contextManager)
68
+ context.setGlobalContextManager(this.#contextManager)
53
69
  if (!trace.setGlobalTracerProvider(this)) {
54
70
  trace.getTracerProvider().setDelegate(this)
55
71
  }
@@ -69,11 +85,11 @@ class TracerProvider {
69
85
  }
70
86
 
71
87
  exporter._writer?.flush()
72
- return this._activeProcessor.forceFlush()
88
+ return this.#activeProcessor.forceFlush()
73
89
  }
74
90
 
75
91
  shutdown () {
76
- return this._activeProcessor.shutdown()
92
+ return this.#activeProcessor.shutdown()
77
93
  }
78
94
  }
79
95
 
@@ -8,8 +8,13 @@ class LogPropagator {
8
8
  this._config = config
9
9
  }
10
10
 
11
+ /**
12
+ * @param {DatadogSpanContext | null | undefined} spanContext
13
+ * @param {Record<string, unknown>} [carrier]
14
+ * @returns {Record<string, unknown> | undefined}
15
+ */
11
16
  inject (spanContext, carrier) {
12
- if (!carrier) return
17
+ if (carrier === null) return
13
18
 
14
19
  const dd = {}
15
20
  let hasField = false
@@ -35,7 +40,11 @@ class LogPropagator {
35
40
  hasField = true
36
41
  }
37
42
 
38
- if (hasField) carrier.dd = dd
43
+ if (!hasField) return
44
+
45
+ carrier ??= {}
46
+ carrier.dd = dd
47
+ return carrier
39
48
  }
40
49
 
41
50
  extract (carrier) {
@@ -8,6 +8,7 @@ const log = require('../../log')
8
8
  const tags = require('../../../../../ext/tags')
9
9
  const { getConfiguredEnvName } = require('../../config/helper')
10
10
  const { setAllBaggageItems, getAllBaggageItems, removeAllBaggageItems } = require('../../baggage')
11
+ const { hasTraceSourcePropagationTag } = require('../../standalone/tracesource')
11
12
  const telemetryMetrics = require('../../telemetry/metrics')
12
13
  const { DD_MAJOR } = require('../../../../../version')
13
14
 
@@ -119,22 +120,35 @@ class TextMapPropagator {
119
120
  return this.#baggageTagKeysSet
120
121
  }
121
122
 
123
+ /**
124
+ * @param {DatadogSpanContext | null | undefined} spanContext
125
+ * @param {Record<string, string>} [carrier]
126
+ * @returns {Record<string, string> | undefined}
127
+ */
122
128
  inject (spanContext, carrier) {
123
- if (!carrier) return
124
- this._injectBaggageItems(spanContext, carrier)
125
- if (!spanContext) return
129
+ if (carrier === null) return
130
+ let injectedCarrier = this._injectBaggageItems(spanContext, carrier)
131
+ if (!spanContext) return injectedCarrier
126
132
 
127
- this._injectDatadog(spanContext, carrier)
128
- this._injectB3MultipleHeaders(spanContext, carrier)
129
- this._injectB3SingleHeader(spanContext, carrier)
130
- this._injectTraceparent(spanContext, carrier)
131
-
132
- if (injectCh.hasSubscribers) {
133
- injectCh.publish({ spanContext, carrier })
133
+ const injectTraceContext = this._config.apmTracingEnabled !== false ||
134
+ hasTraceSourcePropagationTag(spanContext._trace.tags)
135
+ if (injectTraceContext) {
136
+ injectedCarrier = this._injectDatadog(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
137
+ injectedCarrier = this._injectB3MultipleHeaders(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
138
+ injectedCarrier = this._injectB3SingleHeader(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
134
139
  }
140
+ injectedCarrier = this
141
+ ._injectTraceparent(spanContext, injectedCarrier ?? carrier, injectTraceContext) ?? injectedCarrier
142
+
143
+ if (injectedCarrier === undefined) return
144
+
145
+ carrier = injectedCarrier
146
+ if (injectCh.hasSubscribers) injectCh.publish({ spanContext, carrier })
135
147
 
136
148
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
137
149
  log.debug(() => `Inject into carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
150
+
151
+ return carrier
138
152
  }
139
153
 
140
154
  extract (carrier) {
@@ -156,15 +170,23 @@ class TextMapPropagator {
156
170
  return spanContext
157
171
  }
158
172
 
173
+ /**
174
+ * @param {DatadogSpanContext} spanContext
175
+ * @param {Record<string, string>} [carrier]
176
+ * @returns {Record<string, string> | undefined}
177
+ */
159
178
  _injectDatadog (spanContext, carrier) {
160
179
  if (!this._hasPropagationStyle('inject', 'datadog')) return
161
180
 
181
+ carrier ??= {}
162
182
  carrier[traceKey] = spanContext.toTraceId()
163
183
  carrier[spanKey] = spanContext.toSpanId()
164
184
 
165
185
  this._injectOrigin(spanContext, carrier)
166
186
  this._injectSamplingPriority(spanContext, carrier)
167
187
  this._injectTags(spanContext, carrier)
188
+
189
+ return carrier
168
190
  }
169
191
 
170
192
  _injectOrigin (spanContext, carrier) {
@@ -183,7 +205,13 @@ class TextMapPropagator {
183
205
  }
184
206
  }
185
207
 
208
+ /**
209
+ * @param {DatadogSpanContext | null | undefined} spanContext
210
+ * @param {Record<string, string>} [carrier]
211
+ * @returns {Record<string, string> | undefined}
212
+ */
186
213
  _injectBaggageItems (spanContext, carrier) {
214
+ let injectedCarrier
187
215
  if (this._config.legacyBaggageEnabled) {
188
216
  const baggageItems = spanContext?._baggageItems
189
217
  if (baggageItems) {
@@ -202,7 +230,8 @@ class TextMapPropagator {
202
230
  if (invalidHeaderValueCharExpr.test(headerValue)) {
203
231
  headerValue = encodeURIComponent(headerValue)
204
232
  }
205
- carrier[headerName] = headerValue
233
+ injectedCarrier ??= carrier ?? {}
234
+ injectedCarrier[headerName] = headerValue
206
235
  }
207
236
  }
208
237
  }
@@ -213,7 +242,6 @@ class TextMapPropagator {
213
242
  let byteCounter = 0
214
243
 
215
244
  const baggageItems = getAllBaggageItems()
216
- if (!baggageItems) return
217
245
  for (const key of Object.keys(baggageItems)) {
218
246
  const baggageKey = key.trim()
219
247
  if (!baggageTokenExpr.test(baggageKey)) continue
@@ -242,10 +270,13 @@ class TextMapPropagator {
242
270
 
243
271
  baggage = baggage.slice(0, -1)
244
272
  if (baggage) {
245
- carrier.baggage = baggage
273
+ injectedCarrier ??= carrier ?? {}
274
+ injectedCarrier.baggage = baggage
246
275
  tracerMetrics.count('context_header_style.injected', ['header_style:baggage']).inc()
247
276
  }
248
277
  }
278
+
279
+ return injectedCarrier
249
280
  }
250
281
 
251
282
  _injectTags (spanContext, carrier) {
@@ -278,12 +309,18 @@ class TextMapPropagator {
278
309
  }
279
310
  }
280
311
 
312
+ /**
313
+ * @param {DatadogSpanContext} spanContext
314
+ * @param {Record<string, string>} [carrier]
315
+ * @returns {Record<string, string> | undefined}
316
+ */
281
317
  _injectB3MultipleHeaders (spanContext, carrier) {
282
318
  // v5 also accepts the legacy `'b3'` spelling for multi; v6 routes `'b3'` to single-header.
283
319
  const hasB3multi = this._hasPropagationStyle('inject', 'b3multi') ||
284
320
  (DD_MAJOR < 6 && this._hasPropagationStyle('inject', 'b3'))
285
321
  if (!hasB3multi) return
286
322
 
323
+ carrier ??= {}
287
324
  carrier[b3TraceKey] = spanContext._traceId.toTraceIdHex(spanContext._trace.tags['_dd.p.tid'])
288
325
  carrier[b3SpanKey] = spanContext._spanId.toString(16)
289
326
  carrier[b3SampledKey] = spanContext._sampling.priority >= AUTO_KEEP ? '1' : '0'
@@ -295,14 +332,22 @@ class TextMapPropagator {
295
332
  if (spanContext._parentId) {
296
333
  carrier[b3ParentKey] = spanContext._parentId.toString(16)
297
334
  }
335
+
336
+ return carrier
298
337
  }
299
338
 
339
+ /**
340
+ * @param {DatadogSpanContext} spanContext
341
+ * @param {Record<string, string>} [carrier]
342
+ * @returns {Record<string, string> | undefined}
343
+ */
300
344
  _injectB3SingleHeader (spanContext, carrier) {
301
345
  // v6 keeps `'b3 single header'` as a back-compat alias for callers that bypass parser normalisation.
302
346
  const hasB3SingleHeader = this._hasPropagationStyle('inject', 'b3 single header') ||
303
347
  (DD_MAJOR >= 6 && this._hasPropagationStyle('inject', 'b3'))
304
348
  if (!hasB3SingleHeader) return
305
349
 
350
+ carrier ??= {}
306
351
  const traceId = spanContext._traceId.toTraceIdHex(spanContext._trace.tags['_dd.p.tid'])
307
352
  const spanId = spanContext._spanId.toString(16)
308
353
  const sampled = spanContext._sampling.priority >= AUTO_KEEP ? '1' : '0'
@@ -311,11 +356,30 @@ class TextMapPropagator {
311
356
  if (spanContext._parentId) {
312
357
  carrier[b3HeaderKey] += '-' + spanContext._parentId.toString(16)
313
358
  }
359
+
360
+ return carrier
314
361
  }
315
362
 
316
- _injectTraceparent (spanContext, carrier) {
363
+ /**
364
+ * @param {DatadogSpanContext} spanContext
365
+ * @param {Record<string, string> | undefined} carrier
366
+ * @param {boolean} injectTraceContext
367
+ * @returns {Record<string, string> | undefined}
368
+ */
369
+ _injectTraceparent (spanContext, carrier, injectTraceContext) {
317
370
  if (!this._hasPropagationStyle('inject', 'tracecontext')) return
318
371
 
372
+ if (!injectTraceContext) {
373
+ const tracestate = TraceState.fromString(spanContext._tracestate?.toString())
374
+ tracestate.delete('dd')
375
+ const header = tracestate.toString()
376
+ if (!header) return
377
+ carrier ??= {}
378
+ carrier.tracestate = header
379
+ return carrier
380
+ }
381
+
382
+ carrier ??= {}
319
383
  const {
320
384
  _sampling: { priority, mechanism },
321
385
  _tracestate: ts = new TraceState(),
@@ -363,6 +427,8 @@ class TextMapPropagator {
363
427
  })
364
428
 
365
429
  carrier.tracestate = ts.toString()
430
+
431
+ return carrier
366
432
  }
367
433
 
368
434
  _hasPropagationStyle (mode, name) {
@@ -13,13 +13,22 @@ class DSMTextMapPropagator {
13
13
  this.config = config
14
14
  }
15
15
 
16
+ /**
17
+ * @param {object} ctx DSM pathway context.
18
+ * @param {Record<string, string>} [carrier]
19
+ * @returns {Record<string, string> | undefined}
20
+ */
16
21
  inject (ctx, carrier) {
17
22
  if (!this.config.dsmEnabled) return
18
23
 
19
- DsmPathwayCodec.encode(ctx, carrier)
24
+ const injectedCarrier = DsmPathwayCodec.encode(ctx, carrier)
25
+ if (injectedCarrier === undefined) return
20
26
 
27
+ carrier = injectedCarrier
21
28
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
22
29
  log.debug(() => `Inject into carrier (DSM): ${JSON.stringify(pick(carrier, logKeys))}.`)
30
+
31
+ return carrier
23
32
  }
24
33
 
25
34
  extract (carrier) {
@@ -101,6 +101,12 @@ class DatadogTracer {
101
101
  return span
102
102
  }
103
103
 
104
+ /**
105
+ * @param {Span|SpanContext} context
106
+ * @param {string} format
107
+ * @param {object} [carrier]
108
+ * @returns {object | undefined}
109
+ */
104
110
  inject (context, format, carrier) {
105
111
  if (context instanceof Span) {
106
112
  context = context.context()
@@ -110,7 +116,7 @@ class DatadogTracer {
110
116
  if (format !== 'text_map_dsm' && format !== formats.LOG) {
111
117
  this._prioritySampler.sample(context)
112
118
  }
113
- this._propagators[format].inject(context, carrier)
119
+ return this._propagators[format].inject(context, carrier)
114
120
  } catch (e) {
115
121
  log.error('Error injecting trace', e)
116
122
  runtimeMetrics.increment('datadog.tracer.node.inject.errors', true)
@@ -1454,7 +1454,7 @@ function parseAnnotations (annotations) {
1454
1454
  * Returns 0 when the test is too slow to retry (≥ 5 min).
1455
1455
  *
1456
1456
  * @param {number} durationMs
1457
- * @param {Record<string, number>} slowTestRetries e.g. { '5s': 10, '10s': 5, '30s': 3, '5m': 2 }
1457
+ * @param {Record<string, number>} slowTestRetries e.g. { '5s': 10, '10s': 5, '30s': 3, '5m': 2 }
1458
1458
  * @returns {number}
1459
1459
  */
1460
1460
  function getEfdRetryCount (durationMs, slowTestRetries) {
@@ -1469,12 +1469,17 @@ function getEfdRetryCount (durationMs, slowTestRetries) {
1469
1469
  /**
1470
1470
  * Returns the maximum retry count configured by the backend for EFD.
1471
1471
  *
1472
- * @param {Record<string, number>} slowTestRetries e.g. { '5s': 10, '10s': 5, '30s': 3, '5m': 2 }
1473
- * @returns {number}
1472
+ * @param {Record<string, number> | undefined} slowTestRetries
1473
+ * @returns {number | undefined}
1474
1474
  */
1475
1475
  function getMaxEfdRetryCount (slowTestRetries) {
1476
+ if (slowTestRetries === undefined) return
1477
+
1478
+ const retryCounts = Object.values(slowTestRetries)
1479
+ if (retryCounts.length === 0) return
1480
+
1476
1481
  let maxRetries = 0
1477
- for (const retryCount of Object.values(slowTestRetries || {})) {
1482
+ for (const retryCount of retryCounts) {
1478
1483
  if (retryCount > maxRetries) {
1479
1484
  maxRetries = retryCount
1480
1485
  }
@@ -185,6 +185,18 @@ const web = {
185
185
  return context
186
186
  },
187
187
 
188
+ // Key an existing context on the underlying `Http2Stream` so a second request
189
+ // backed by the same stream resolves to it via the `req.stream` lookup in
190
+ // `patch`. Only a mixed HTTP/2 server (raw-stream + 'request' listeners) needs
191
+ // this: it creates the span from a throwaway stream adapter, then the
192
+ // compatibility layer synthesizes the real `Http2ServerRequest` off the same
193
+ // stream and must reuse the adapter's span rather than open an orphan context.
194
+ // Confined to that path so the common single-listener request pays no extra
195
+ // per-request map write.
196
+ linkContextToStream (stream, context) {
197
+ contexts.set(stream, context)
198
+ },
199
+
188
200
  // Return the request root span.
189
201
  root (req) {
190
202
  const context = contexts.get(req)
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const NoopProxy = require('./noop/proxy')
4
+ const { features } = require('./feature-registry')
4
5
  const DatadogTracer = require('./tracer')
5
6
  const getConfig = require('./config')
6
7
  const runtimeMetrics = require('./runtime_metrics')
@@ -91,7 +92,10 @@ class Tracer extends NoopProxy {
91
92
  iast: new LazyModule(() => require('./appsec/iast')),
92
93
  llmobs: new LazyModule(() => require('./llmobs')),
93
94
  rewriter: new LazyModule(() => require('./appsec/iast/taint-tracking/rewriter')),
94
- openfeature: new LazyModule(() => require('./openfeature')),
95
+ }
96
+
97
+ for (const feature of Object.values(features)) {
98
+ this._modules[feature.name] = new LazyModule(feature.factory)
95
99
  }
96
100
  }
97
101
 
@@ -114,7 +118,11 @@ class Tracer extends NoopProxy {
114
118
  propagationHash.configure(config)
115
119
 
116
120
  if (config.DD_CRASHTRACKING_ENABLED) {
117
- require('./crashtracking').start(config)
121
+ try {
122
+ require('./crashtracking').start(config)
123
+ } catch (e) {
124
+ log.warn('Crashtracking is not available in this environment', e)
125
+ }
118
126
  }
119
127
 
120
128
  if (config.DD_HEAP_SNAPSHOT_COUNT > 0) {
@@ -176,8 +184,9 @@ class Tracer extends NoopProxy {
176
184
  DynamicInstrumentation.start(config, rc)
177
185
  }
178
186
 
179
- const openfeatureRemoteConfig = require('./openfeature/remote_config')
180
- openfeatureRemoteConfig.enable(rc, config, () => this.openfeature)
187
+ for (const feature of Object.values(features)) {
188
+ feature.remoteConfig?.(rc, config, this)
189
+ }
181
190
  }
182
191
 
183
192
  if (config.profiling.DD_PROFILING_ENABLED === 'true') {
@@ -287,14 +296,15 @@ class Tracer extends NoopProxy {
287
296
  lazyProxy(this, 'llmobs', () => require('./llmobs/sdk'), this._tracer, this._modules.llmobs, config)
288
297
 
289
298
  if (config.experimental?.aiguard?.enabled) {
290
- this._modules.aiguard.enable(this._tracer, config)
291
299
  lazyProxy(this, 'aiguard', () => require('./aiguard/sdk'), this._tracer, config)
292
300
  }
293
301
  this._tracingInitialized = true
294
302
  }
295
- if (config.experimental.flaggingProvider.enabled) {
296
- this._modules.openfeature.enable(config)
297
- lazyProxy(this, 'openfeature', () => require('./openfeature/flagging_provider'), this._tracer, config)
303
+ for (const feature of Object.values(features)) {
304
+ feature.enable?.(config, this._tracer, this, lazyProxy)
305
+ }
306
+ if (config.experimental?.aiguard?.enabled) {
307
+ this._modules.aiguard.enable(this._tracer, config)
298
308
  }
299
309
  if (config.iast.enabled) {
300
310
  this._modules.iast.enable(config, this._tracer)
@@ -305,7 +315,9 @@ class Tracer extends NoopProxy {
305
315
  this._modules.aiguard.disable()
306
316
  this._modules.iast.disable()
307
317
  this._modules.llmobs.disable()
308
- this._modules.openfeature.disable()
318
+ for (const feature of Object.values(features)) {
319
+ this._modules[feature.name].disable()
320
+ }
309
321
  }
310
322
 
311
323
  if (this._tracingInitialized) {
@@ -7,10 +7,16 @@ const os = require('os')
7
7
  const process = require('process')
8
8
  const { performance, PerformanceObserver, monitorEventLoopDelay } = require('perf_hooks')
9
9
  const log = require('../log')
10
- const { NODE_MAJOR } = require('../../../../version')
10
+ const { NODE_MAJOR, NODE_MINOR } = require('../../../../version')
11
11
  const { createMetricsClient } = require('./client')
12
12
 
13
13
  const eventLoopDelayResolution = 4
14
+ const EVENT_LOOP_SAMPLE_PER_ITERATION_AVAILABLE = NODE_MAJOR > 26 || (NODE_MAJOR === 26 && NODE_MINOR >= 5)
15
+
16
+ // @datadog/native-metrics is only needed on Node versions where
17
+ // monitorEventLoopDelay's samplePerIteration option is unavailable.
18
+ // TODO: remove @datadog/native-metrics and this branch once Node < 26.5 is no longer supported.
19
+ const NATIVE_METRICS_REQUIRED = !EVENT_LOOP_SAMPLE_PER_ITERATION_AVAILABLE
14
20
 
15
21
  let nativeMetrics = null
16
22
  let gcObserver = null
@@ -45,7 +51,10 @@ module.exports = {
45
51
  startGCObserver()
46
52
  }
47
53
 
48
- const useNative = config.runtimeMetrics.native !== false
54
+ // When per-iteration sampling is available we prefer it over the native
55
+ // addon: it provides accurate event loop delay measurements and lets us
56
+ // collect CPU/GC/heap metrics entirely from JS APIs.
57
+ const useNative = NATIVE_METRICS_REQUIRED && config.runtimeMetrics.native !== false
49
58
 
50
59
  if (useNative) {
51
60
  // Using no-gc prevents the native gc metrics from being tracked. Not
@@ -74,7 +83,10 @@ module.exports = {
74
83
  lastCpuUsage = process.cpuUsage()
75
84
 
76
85
  if (trackEventLoop) {
77
- eventLoopDelayObserver = monitorEventLoopDelay({ resolution: eventLoopDelayResolution })
86
+ eventLoopDelayObserver = monitorEventLoopDelay({
87
+ resolution: eventLoopDelayResolution,
88
+ samplePerIteration: EVENT_LOOP_SAMPLE_PER_ITERATION_AVAILABLE,
89
+ })
78
90
  eventLoopDelayObserver.enable()
79
91
  }
80
92
 
@@ -83,11 +95,6 @@ module.exports = {
83
95
  captureCommonMetrics(trackEventLoop)
84
96
  captureHeapSpace()
85
97
  if (trackEventLoop) {
86
- // Experimental: The Node.js implementation deviates from the native metrics.
87
- // We normalize the metrics to the same format but the Node.js values
88
- // are that way lower than they should be, while they are still nearer
89
- // to the native ones that way.
90
- // We use these only as fallback values.
91
98
  captureEventLoopDelay()
92
99
  }
93
100
  client.flush()
@@ -195,12 +202,14 @@ function captureEventLoopDelay () {
195
202
  eventLoopDelayObserver.disable()
196
203
 
197
204
  if (eventLoopDelayObserver.count !== 0) {
198
- const minimum = eventLoopDelayResolution * 1e6
205
+ // Node.js versions without iteration-based metrics use the default sampling method,
206
+ // which is interval-based and requires normalization because its values are much smaller
207
+ // than those from the original native implementation and iteration-based metrics.
208
+ const minimum = EVENT_LOOP_SAMPLE_PER_ITERATION_AVAILABLE ? 0 : eventLoopDelayResolution * 1e6
199
209
  const avg = Math.max(eventLoopDelayObserver.mean - minimum, 0)
200
210
  const sum = Math.round(avg * eventLoopDelayObserver.count)
201
211
 
202
212
  if (sum !== 0) {
203
- // Normalize the metrics to the same format as the native metrics.
204
213
  const stats = {
205
214
  min: Math.max(eventLoopDelayObserver.min - minimum, 0),
206
215
  max: Math.max(eventLoopDelayObserver.max - minimum, 0),
@@ -214,7 +223,7 @@ function captureEventLoopDelay () {
214
223
  histogram('runtime.node.event_loop.delay', stats)
215
224
  }
216
225
  }
217
- eventLoopDelayObserver = monitorEventLoopDelay({ resolution: eventLoopDelayResolution })
226
+ eventLoopDelayObserver.reset()
218
227
  eventLoopDelayObserver.enable()
219
228
  }
220
229