dd-trace 6.2.0 → 6.3.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 (27) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -4
  3. package/packages/datadog-instrumentations/src/cookie.js +7 -1
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -1
  5. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +30 -54
  6. package/packages/datadog-instrumentations/src/http/server.js +27 -0
  7. package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +248 -1
  8. package/packages/datadog-instrumentations/src/playwright.js +40 -3
  9. package/packages/datadog-plugin-azure-functions/src/index.js +2 -1
  10. package/packages/datadog-plugin-graphql/src/request.js +1 -1
  11. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +144 -26
  12. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/utils.js +30 -0
  13. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  14. package/packages/datadog-shimmer/src/shimmer.js +9 -2
  15. package/packages/dd-trace/src/appsec/channels.js +1 -0
  16. package/packages/dd-trace/src/appsec/index.js +6 -3
  17. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  18. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +23 -16
  19. package/packages/dd-trace/src/config/config-types.d.ts +8 -0
  20. package/packages/dd-trace/src/config/defaults.js +20 -15
  21. package/packages/dd-trace/src/config/index.js +13 -3
  22. package/packages/dd-trace/src/config/parsers.js +79 -0
  23. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
  24. package/packages/dd-trace/src/llmobs/tagger.js +15 -1
  25. package/packages/dd-trace/src/opentelemetry/span_processor.js +7 -5
  26. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +32 -16
  27. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +20 -11
@@ -35,6 +35,7 @@ const {
35
35
  INTEGRATION,
36
36
  DECORATOR,
37
37
  PROPAGATED_ML_APP_KEY,
38
+ PROPAGATED_SESSION_ID_KEY,
38
39
  DEFAULT_PROMPT_NAME,
39
40
  INTERNAL_CONTEXT_VARIABLE_KEYS,
40
41
  INTERNAL_QUERY_VARIABLE_KEYS,
@@ -139,7 +140,8 @@ class LLMObsTagger {
139
140
  if (modelName) this.tagModelName(span, modelName)
140
141
  if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
141
142
 
142
- sessionId = sessionId || registry.get(parent)?.[SESSION_ID]
143
+ const traceTags = span.context()._trace.tags
144
+ sessionId = sessionId || registry.get(parent)?.[SESSION_ID] || traceTags[PROPAGATED_SESSION_ID_KEY]
143
145
  if (sessionId) this._setTag(span, SESSION_ID, sessionId)
144
146
  if (integration) this._setTag(span, INTEGRATION, integration)
145
147
  if (_decorator) this._setTag(span, DECORATOR, _decorator)
@@ -752,6 +754,18 @@ class LLMObsTagger {
752
754
 
753
755
  const tagsCarrier = registry.get(span)
754
756
  tagsCarrier[key] = value
757
+
758
+ // The first session set in a trace becomes the trace-level default, stored on the trace-shared
759
+ // propagating tags so later spans (incl. those under a session-less parent) inherit it and it
760
+ // rides `x-datadog-tags` across service boundaries. Established here, the single choke point for
761
+ // session writes, so sessions post-populated by integrations after span start also seed it.
762
+ // First-writer wins, so an explicit session still overrides locally.
763
+ if (key === SESSION_ID && value) {
764
+ const traceTags = span.context()._trace.tags
765
+ if (traceTags[PROPAGATED_SESSION_ID_KEY] === undefined) {
766
+ traceTags[PROPAGATED_SESSION_ID_KEY] = value
767
+ }
768
+ }
755
769
  }
756
770
  }
757
771
 
@@ -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
 
@@ -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