dd-trace 6.19.0 → 6.20.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 (77) hide show
  1. package/index.d.ts +163 -22
  2. package/package.json +2 -2
  3. package/packages/datadog-esbuild/index.js +9 -6
  4. package/packages/datadog-instrumentations/src/ai.js +26 -0
  5. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
  6. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -7
  7. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +2 -2
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +25 -0
  9. package/packages/datadog-plugin-openai/src/stream-helpers.js +50 -1
  10. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  11. package/packages/dd-trace/src/config/remote_config.js +12 -0
  12. package/packages/dd-trace/src/config/supported-configurations.json +21 -0
  13. package/packages/dd-trace/src/debugger/constants.js +13 -4
  14. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -1
  15. package/packages/dd-trace/src/debugger/devtools_client/condition.js +109 -6
  16. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
  17. package/packages/dd-trace/src/debugger/devtools_client/index.js +65 -6
  18. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +22 -10
  19. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +12 -8
  20. package/packages/dd-trace/src/debugger/devtools_client/send.js +14 -0
  21. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +43 -5
  22. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +3 -8
  23. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +2 -122
  24. package/packages/dd-trace/src/debugger/guardrail-metrics.js +0 -2
  25. package/packages/dd-trace/src/debugger/index.js +60 -28
  26. package/packages/dd-trace/src/debugger/inspect-segment.js +92 -23
  27. package/packages/dd-trace/src/debugger/pause-duration-histogram.js +74 -0
  28. package/packages/dd-trace/src/debugger/probe_sampler.js +192 -52
  29. package/packages/dd-trace/src/debugger/redaction.js +139 -0
  30. package/packages/dd-trace/src/knuth-hash.js +17 -0
  31. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  32. package/packages/dd-trace/src/llmobs/experiments/client.js +66 -2
  33. package/packages/dd-trace/src/llmobs/experiments/evaluator.js +138 -0
  34. package/packages/dd-trace/src/llmobs/experiments/experiment.js +213 -90
  35. package/packages/dd-trace/src/llmobs/experiments/index.js +40 -1
  36. package/packages/dd-trace/src/llmobs/experiments/noop.js +53 -2
  37. package/packages/dd-trace/src/llmobs/experiments/remote-evaluator.js +110 -0
  38. package/packages/dd-trace/src/llmobs/experiments/util.js +34 -6
  39. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +2 -2
  40. package/packages/dd-trace/src/llmobs/sdk.js +19 -3
  41. package/packages/dd-trace/src/llmobs/span_processor.js +42 -26
  42. package/packages/dd-trace/src/llmobs/tagger.js +35 -0
  43. package/packages/dd-trace/src/llmobs/writers/base.js +1 -6
  44. package/packages/dd-trace/src/openfeature/constants/constants.js +10 -0
  45. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -1
  46. package/packages/dd-trace/src/openfeature/writers/base.js +39 -18
  47. package/packages/dd-trace/src/openfeature/writers/flag-eval-evp-hook.js +105 -0
  48. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-aggregation.js +190 -0
  49. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-consumer.js +233 -0
  50. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-context.js +262 -0
  51. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-payload.js +181 -0
  52. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-pii.js +108 -0
  53. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-telemetry.js +154 -0
  54. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-worker.js +77 -0
  55. package/packages/dd-trace/src/openfeature/writers/flag-evaluations.js +398 -0
  56. package/packages/dd-trace/src/openfeature/writers/util.js +5 -4
  57. package/packages/dd-trace/src/opentelemetry/context_manager.js +3 -0
  58. package/packages/dd-trace/src/opentelemetry/span_context.js +6 -2
  59. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -0
  60. package/packages/dd-trace/src/opentelemetry/tracer.js +16 -18
  61. package/packages/dd-trace/src/opentracing/propagation/text_map.js +116 -25
  62. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +80 -16
  63. package/packages/dd-trace/src/opentracing/span.js +14 -9
  64. package/packages/dd-trace/src/opentracing/tracer.js +10 -3
  65. package/packages/dd-trace/src/otel-sampling.js +171 -0
  66. package/packages/dd-trace/src/plugins/index.js +0 -1
  67. package/packages/dd-trace/src/priority_sampler.js +155 -33
  68. package/packages/dd-trace/src/remote_config/capabilities.js +9 -0
  69. package/packages/dd-trace/src/sampler.js +2 -5
  70. package/packages/dd-trace/src/sampling_rule.js +4 -8
  71. package/packages/dd-trace/src/span_format.js +6 -0
  72. package/packages/dd-trace/src/span_processor.js +26 -2
  73. package/packages/dd-trace/src/standalone/index.js +5 -4
  74. package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +25 -5
  75. package/packages/dd-trace/src/telemetry/metrics.js +4 -3
  76. package/packages/datadog-instrumentations/src/postgres.js +0 -7
  77. package/packages/datadog-instrumentations/src/supabase.js +0 -15
@@ -77,6 +77,8 @@ const zeroTraceId = '0000000000000000'
77
77
  const hex16 = /^[0-9A-Fa-f]{16}$/
78
78
  const percentByte = /%([0-9A-Fa-f]{2})/g
79
79
 
80
+ let otelSampling
81
+
80
82
  /**
81
83
  * @typedef {object} B3Context
82
84
  * @property {string} [flags]
@@ -85,6 +87,13 @@ const percentByte = /%([0-9A-Fa-f]{2})/g
85
87
  * @property {string} [traceId]
86
88
  */
87
89
 
90
+ /**
91
+ * @typedef {object} TraceTagInjection
92
+ * @property {DatadogSpanContext} spanContext
93
+ * @property {Array<string | undefined>} [traceTagReplacements]
94
+ * @property {number} [optionalTraceTagCount]
95
+ */
96
+
88
97
  /**
89
98
  * @param {Array<string | undefined>} traceTagReplacements
90
99
  * @param {string} key
@@ -96,6 +105,28 @@ function hasTraceTagReplacement (traceTagReplacements, key) {
96
105
  return false
97
106
  }
98
107
 
108
+ /** @param {string} key */
109
+ function toTraceStateTagKey (key) {
110
+ return 't.' + key.slice(6).replaceAll(tracestateTagKeyFilter, '_')
111
+ }
112
+
113
+ /**
114
+ * @param {string} key
115
+ * @param {TraceTagInjection | undefined} injection
116
+ */
117
+ function isOptionalDatadogTraceStateField (key, injection) {
118
+ if (!key.startsWith('t.') || key === 't.dm' || key === 't.ts') return false
119
+
120
+ const traceTagReplacements = injection?.traceTagReplacements
121
+ if (!traceTagReplacements) return true
122
+ const firstOptionalTraceTagIndex = traceTagReplacements.length - (injection.optionalTraceTagCount ?? 0) * 2
123
+ for (let index = 0; index < firstOptionalTraceTagIndex; index += 2) {
124
+ const traceTagKey = traceTagReplacements[index]
125
+ if (traceTagKey.startsWith('_dd.p.') && toTraceStateTagKey(traceTagKey) === key) return false
126
+ }
127
+ return true
128
+ }
129
+
99
130
  /**
100
131
  * @param {string | undefined} traceId
101
132
  * @param {string | undefined} spanId
@@ -149,12 +180,15 @@ function getB3Priority (sampled, debug) {
149
180
  * @returns {DatadogSpanContext | undefined}
150
181
  */
151
182
  function extractB3Context (b3) {
152
- const priority = getB3Priority(b3.sampled, b3.flags === '1')
153
- const spanContext = extractGenericContext(b3.traceId, b3.spanId, 16)
183
+ const debug = b3.flags === '1'
184
+ const priority = getB3Priority(b3.sampled, debug)
185
+ let spanContext = extractGenericContext(b3.traceId, b3.spanId, 16)
154
186
 
155
187
  if (priority !== undefined) {
156
- if (!spanContext) {
157
- return new DatadogSpanContext({
188
+ if (spanContext) {
189
+ spanContext._sampling.priority = priority
190
+ } else {
191
+ spanContext = new DatadogSpanContext({
158
192
  traceId: id(),
159
193
  spanId: null,
160
194
  sampling: { priority },
@@ -162,7 +196,7 @@ function extractB3Context (b3) {
162
196
  })
163
197
  }
164
198
 
165
- spanContext._sampling.priority = priority
199
+ if (debug) spanContext._sampling.isProbabilityDecision = false
166
200
  }
167
201
 
168
202
  if (spanContext && b3.traceId) extract128BitTraceId(b3.traceId, spanContext)
@@ -326,13 +360,15 @@ class TextMapPropagator {
326
360
  hasTraceSourcePropagationTag(spanContext._trace.tags)
327
361
  let traceTagReplacements
328
362
  let optionalTraceTagCount = 0
363
+ /** @type {TraceTagInjection | undefined} */
364
+ let traceTagInjection
329
365
  if (injectTraceContext && injectCh.hasSubscribers && (
330
366
  this.#hasPropagationStyle('inject', 'datadog') || this.#hasPropagationStyle('inject', 'tracecontext')
331
367
  )) {
332
- const injection = { spanContext }
333
- injectCh.publish(injection)
334
- traceTagReplacements = injection.traceTagReplacements
335
- optionalTraceTagCount = injection.optionalTraceTagCount ?? 0
368
+ traceTagInjection = { spanContext }
369
+ injectCh.publish(traceTagInjection)
370
+ traceTagReplacements = traceTagInjection.traceTagReplacements
371
+ optionalTraceTagCount = traceTagInjection.optionalTraceTagCount ?? 0
336
372
  }
337
373
  if (injectTraceContext) {
338
374
  injectedCarrier = this.#injectDatadog(
@@ -343,7 +379,7 @@ class TextMapPropagator {
343
379
  }
344
380
  injectedCarrier = this
345
381
  .#injectTraceparent(
346
- spanContext, injectedCarrier ?? carrier, injectTraceContext, traceTagReplacements
382
+ spanContext, injectedCarrier ?? carrier, injectTraceContext, traceTagReplacements, traceTagInjection
347
383
  ) ?? injectedCarrier
348
384
 
349
385
  if (injectedCarrier === undefined) return
@@ -509,7 +545,7 @@ class TextMapPropagator {
509
545
  }
510
546
 
511
547
  if (traceTagReplacements) {
512
- const requiredEntryCount = traceTagReplacements.length - optionalTraceTagCount * 2
548
+ const firstOptionalTraceTagIndex = traceTagReplacements.length - optionalTraceTagCount * 2
513
549
  for (let index = 0; index < traceTagReplacements.length; index += 2) {
514
550
  const key = traceTagReplacements[index]
515
551
  const value = traceTagReplacements[index + 1]
@@ -520,7 +556,7 @@ class TextMapPropagator {
520
556
  }
521
557
 
522
558
  const entry = `${header ? ',' : ''}${key}=${value}`
523
- if (index >= requiredEntryCount && header.length + entry.length > maxLength) break
559
+ if (index >= firstOptionalTraceTagIndex && header.length + entry.length > maxLength) break
524
560
  header += entry
525
561
  }
526
562
  }
@@ -589,13 +625,14 @@ class TextMapPropagator {
589
625
  * @param {Record<string, string> | undefined} carrier
590
626
  * @param {boolean} injectTraceContext
591
627
  * @param {Array<string | undefined>} [traceTagReplacements]
628
+ * @param {TraceTagInjection} [traceTagInjection]
592
629
  * @returns {Record<string, string> | undefined}
593
630
  */
594
- #injectTraceparent (spanContext, carrier, injectTraceContext, traceTagReplacements) {
631
+ #injectTraceparent (spanContext, carrier, injectTraceContext, traceTagReplacements, traceTagInjection) {
595
632
  if (!this.#hasPropagationStyle('inject', 'tracecontext')) return
596
633
 
597
634
  if (!injectTraceContext) {
598
- const tracestate = TraceState.fromString(spanContext._tracestate?.toString())
635
+ const tracestate = spanContext._tracestate?.clone() ?? new TraceState()
599
636
  tracestate.delete('dd')
600
637
  const header = tracestate.toString()
601
638
  if (!header) return
@@ -611,11 +648,14 @@ class TextMapPropagator {
611
648
  _trace: { origin },
612
649
  } = spanContext
613
650
  const ts = traceTagReplacements
614
- ? TraceState.fromString(_tracestate?.toString())
651
+ ? _tracestate?.clone() ?? new TraceState()
615
652
  : _tracestate ?? new TraceState()
616
653
 
617
654
  writeTraceparent(carrier, spanContext.toTraceparent())
618
655
 
656
+ otelSampling ??= require('../../otel-sampling')
657
+ otelSampling.updateOtelTraceState(spanContext, ts)
658
+
619
659
  ts.forVendor('dd', state => {
620
660
  if (!spanContext._isRemote) {
621
661
  // SpanContext was created by a ddtrace span.
@@ -643,8 +683,7 @@ class TextMapPropagator {
643
683
  const tagValueRaw = spanContext._trace.tags[key]
644
684
  if (!tagValueRaw || !key.startsWith('_dd.p.')) continue
645
685
 
646
- const tagKey = 't.' + key.slice(6)
647
- .replaceAll(tracestateTagKeyFilter, '_')
686
+ const tagKey = toTraceStateTagKey(key)
648
687
 
649
688
  const tagValue = tagValueRaw
650
689
  .toString()
@@ -659,8 +698,7 @@ class TextMapPropagator {
659
698
  const key = traceTagReplacements[index]
660
699
  if (!key.startsWith('_dd.p.')) continue
661
700
 
662
- const tagKey = 't.' + key.slice(6)
663
- .replaceAll(tracestateTagKeyFilter, '_')
701
+ const tagKey = toTraceStateTagKey(key)
664
702
  const tagValueRaw = traceTagReplacements[index + 1]
665
703
  if (!tagValueRaw) {
666
704
  state.delete(tagKey)
@@ -675,7 +713,7 @@ class TextMapPropagator {
675
713
  state.set(tagKey, tagValue)
676
714
  }
677
715
  }
678
- })
716
+ }, isOptionalDatadogTraceStateField, traceTagInjection)
679
717
 
680
718
  writeTracestate(carrier, ts.toString())
681
719
 
@@ -690,6 +728,32 @@ class TextMapPropagator {
690
728
  return this.#config.tracePropagationStyle[mode].includes(name)
691
729
  }
692
730
 
731
+ /**
732
+ * Merges W3C sampling state into the context selected by propagation-style precedence.
733
+ *
734
+ * @param {DatadogSpanContext} w3cSpanContext
735
+ * @param {DatadogSpanContext} selectedSpanContext
736
+ */
737
+ #mergeTraceContextState (w3cSpanContext, selectedSpanContext) {
738
+ const selectedPriority = selectedSpanContext._sampling.priority
739
+ if (selectedPriority === undefined) {
740
+ selectedSpanContext._sampling.priority = w3cSpanContext._sampling.priority
741
+ selectedSpanContext._sampling.mechanism = w3cSpanContext._sampling.mechanism
742
+ selectedSpanContext._trace.tags['_dd.p.dm'] = w3cSpanContext._trace.tags['_dd.p.dm']
743
+ } else if (
744
+ selectedSpanContext._sampling.isProbabilityDecision === false ||
745
+ (selectedPriority >= AUTO_KEEP) !== (w3cSpanContext._sampling.priority >= AUTO_KEEP)
746
+ ) {
747
+ // Copied sampling metadata must not describe a conflicting or non-probabilistic selected decision.
748
+ selectedSpanContext._sampling.isProbabilityDecision = false
749
+ w3cSpanContext._tracestate.forVendor('dd', state => {
750
+ if (state.get('t.dm') !== undefined) state.delete('t.dm')
751
+ })
752
+ }
753
+
754
+ selectedSpanContext._tracestate = w3cSpanContext._tracestate
755
+ }
756
+
693
757
  /**
694
758
  * @param {DatadogSpanContext | undefined} w3cSpanContext
695
759
  * @param {DatadogSpanContext} firstSpanContext
@@ -699,10 +763,13 @@ class TextMapPropagator {
699
763
  */
700
764
  #resolveTraceContextConflicts (w3cSpanContext, firstSpanContext, carrier, datadogContext) {
701
765
  if (w3cSpanContext === undefined ||
702
- firstSpanContext.toTraceId(true) !== w3cSpanContext.toTraceId(true) ||
703
- firstSpanContext.toSpanId() === w3cSpanContext.toSpanId()) {
766
+ firstSpanContext.toTraceId(true) !== w3cSpanContext.toTraceId(true)) {
704
767
  return firstSpanContext
705
768
  }
769
+
770
+ this.#mergeTraceContextState(w3cSpanContext, firstSpanContext)
771
+ if (firstSpanContext.toSpanId() === w3cSpanContext.toSpanId()) return firstSpanContext
772
+
706
773
  if (tags.DD_PARENT_ID in w3cSpanContext._trace.tags) {
707
774
  // tracecontext headers contain a p value, ensure this value is sent to backend
708
775
  firstSpanContext._trace.tags[tags.DD_PARENT_ID] = w3cSpanContext._trace.tags[tags.DD_PARENT_ID]
@@ -740,7 +807,8 @@ class TextMapPropagator {
740
807
  traceContext = this.#extractTraceparentContext(carrier)
741
808
  traceContextExtracted = true
742
809
  }
743
- this.#addTraceContextState(extractedContext, traceContext)
810
+ const selectedContext = context && context !== traceContext ? context : extractedContext
811
+ this.#addTraceContextState(selectedContext, traceContext)
744
812
  }
745
813
  break
746
814
  case 'tracecontext':
@@ -842,7 +910,25 @@ class TextMapPropagator {
842
910
  }
843
911
 
844
912
  const traceTags = this.#extractTags(carrier)
845
- if (traceTags) spanContext._trace.tags = traceTags
913
+ if (traceTags) {
914
+ spanContext._trace.tags = traceTags
915
+ const decisionMaker = traceTags['_dd.p.dm']
916
+ if (decisionMaker !== undefined) {
917
+ // Avoid general integer parsing for the common single-digit mechanisms.
918
+ const digit = decisionMaker.length === 2 && decisionMaker[0] === '-'
919
+ ? decisionMaker.charCodeAt(1) - 48
920
+ : -1
921
+ if (digit >= 0 && digit <= 9) {
922
+ spanContext._sampling.mechanism = digit
923
+ } else {
924
+ // Preserve integer-prefix parsing; Number() also turns empty values into zero.
925
+ const mechanism = Math.abs(Number.parseInt(decisionMaker, 10))
926
+ if (Number.isInteger(mechanism) && mechanism >= 0) {
927
+ spanContext._sampling.mechanism = mechanism
928
+ }
929
+ }
930
+ }
931
+ }
846
932
 
847
933
  return spanContext
848
934
  }
@@ -879,7 +965,7 @@ class TextMapPropagator {
879
965
  #addTraceContextState (datadogContext, traceContext) {
880
966
  if (traceContext && datadogContext._traceId.equals(traceContext._traceId)) {
881
967
  datadogContext._traceparent = traceContext._traceparent
882
- datadogContext._tracestate = traceContext._tracestate
968
+ this.#mergeTraceContextState(traceContext, datadogContext)
883
969
  }
884
970
  }
885
971
 
@@ -905,6 +991,11 @@ class TextMapPropagator {
905
991
  // Version 00 should have no tail, but future versions may
906
992
  if (tail && version === '00') return
907
993
 
994
+ if (tracestate.get('ot') !== undefined) {
995
+ otelSampling ??= require('../../otel-sampling')
996
+ otelSampling.normalizeOtelTraceState(tracestate)
997
+ }
998
+
908
999
  const spanContext = new DatadogSpanContext({
909
1000
  traceId: id(traceId, 16),
910
1001
  spanId: id(spanId, 16),
@@ -3,6 +3,7 @@
3
3
  // W3C Trace Context §3.3.1.2: max 32 list-members.
4
4
  // https://www.w3.org/TR/trace-context/#tracestate-header-field-values
5
5
  const MAX_LIST_MEMBERS = 32
6
+ const MAX_VALUE_LENGTH = 256
6
7
  const WHITESPACE = /[ \t]/
7
8
 
8
9
  /**
@@ -12,10 +13,12 @@ const WHITESPACE = /[ \t]/
12
13
  * @param {string} fieldSeparator Between entries.
13
14
  * @param {string} pairSeparator Between key and value within an entry.
14
15
  * @param {boolean} rejectValueTabs Drop entries whose value contains an internal tab.
16
+ * @param {number} [maxEntries] Maximum number of entries to parse.
17
+ * @param {number} [maxValueLength] Maximum length of an entry value.
15
18
  * @returns {[string, string][]} Entries in reverse of wire order.
16
19
  */
17
- function parseEntries (value, fieldSeparator, pairSeparator, rejectValueTabs) {
18
- const segments = value.split(fieldSeparator, MAX_LIST_MEMBERS)
20
+ function parseEntries (value, fieldSeparator, pairSeparator, rejectValueTabs, maxEntries, maxValueLength) {
21
+ const segments = value.split(fieldSeparator, maxEntries)
19
22
 
20
23
  // TODO: We should extract dd no matter at what position and move it to the front of the list.
21
24
  // Extract up 31 additional entries.
@@ -29,7 +32,9 @@ function parseEntries (value, fieldSeparator, pairSeparator, rejectValueTabs) {
29
32
  // W3C §3.3.1.3.2: value = 0*255(chr) nblk-chr; chr = %x20 / nblk-chr (no tab).
30
33
  // Leading 0x20 is part of value; trailing whitespace is OWS.
31
34
  const entryValue = segment.slice(splitIndex + 1).trimEnd()
32
- if (!entryValue || rejectValueTabs && entryValue.includes('\t')) continue
35
+ if (!entryValue ||
36
+ maxValueLength !== undefined && entryValue.length > maxValueLength ||
37
+ rejectValueTabs && entryValue.includes('\t')) continue
33
38
  entries.push([key, entryValue])
34
39
  }
35
40
  // Reverse so the Map's insertion order is reverse of wire order. `toString`
@@ -38,11 +43,21 @@ function parseEntries (value, fieldSeparator, pairSeparator, rejectValueTabs) {
38
43
  return entries
39
44
  }
40
45
 
41
- function fromString (Type, value, fieldSeparator, pairSeparator, rejectValueTabs) {
46
+ /**
47
+ * @param {typeof TraceState | typeof TraceStateData} Type
48
+ * @param {string | undefined} value
49
+ * @param {string} fieldSeparator
50
+ * @param {string} pairSeparator
51
+ * @param {boolean} rejectValueTabs
52
+ * @param {number} [maxEntries]
53
+ * @param {number} [maxValueLength]
54
+ * @returns {TraceState | TraceStateData}
55
+ */
56
+ function fromString (Type, value, fieldSeparator, pairSeparator, rejectValueTabs, maxEntries, maxValueLength) {
42
57
  if (typeof value !== 'string' || !value.length) {
43
58
  return new Type()
44
59
  }
45
- return new Type(parseEntries(value, fieldSeparator, pairSeparator, rejectValueTabs))
60
+ return new Type(parseEntries(value, fieldSeparator, pairSeparator, rejectValueTabs, maxEntries, maxValueLength))
46
61
  }
47
62
 
48
63
  function toString (map, pairSeparator, fieldSeparator) {
@@ -76,8 +91,9 @@ class TraceStateData {
76
91
  }
77
92
 
78
93
  delete (key) {
94
+ if (!this.#map.delete(key)) return false
79
95
  this.changed = true
80
- return this.#map.delete(key)
96
+ return true
81
97
  }
82
98
 
83
99
  clear () {
@@ -97,6 +113,24 @@ class TraceStateData {
97
113
  return this.#map.size
98
114
  }
99
115
 
116
+ /**
117
+ * @template Context
118
+ * @param {number} valueLength
119
+ * @param {(key: string, context: Context) => boolean} isOptional
120
+ * @param {Context} context
121
+ */
122
+ trimOptionalFields (valueLength, isOptional, context) {
123
+ const fields = [...this.#map]
124
+ for (let index = fields.length - 1; index >= 0 && valueLength > MAX_VALUE_LENGTH; index--) {
125
+ const [key, value] = fields[index]
126
+ if (!isOptional(key, context)) continue
127
+
128
+ valueLength -= key.length + String(value).length + (this.#map.size > 1 ? 2 : 1)
129
+ this.delete(key)
130
+ }
131
+ }
132
+
133
+ /** @param {string | undefined} value */
100
134
  static fromString (value) {
101
135
  return fromString(TraceStateData, value, ';', ':', false)
102
136
  }
@@ -115,11 +149,22 @@ class TraceState {
115
149
 
116
150
  constructor (entries) {
117
151
  this.#map = entries ? new Map(entries) : new Map()
152
+ while (this.#map.size > MAX_LIST_MEMBERS) {
153
+ this.#map.delete(this.#map.keys().next().value)
154
+ }
118
155
  }
119
156
 
120
157
  // Delete entries on update to ensure they're moved to the end of the list
158
+ /**
159
+ * @param {string} key
160
+ * @param {string} value
161
+ */
121
162
  set (key, value) {
122
- if (this.#map.has(key)) this.#map.delete(key)
163
+ if (value.length > MAX_VALUE_LENGTH) return this
164
+ const updated = this.#map.delete(key)
165
+ if (!updated && this.#map.size === MAX_LIST_MEMBERS) {
166
+ this.#map.delete(this.#map.keys().next().value)
167
+ }
123
168
  this.#map.set(key, value)
124
169
  return this
125
170
  }
@@ -140,25 +185,44 @@ class TraceState {
140
185
  return this.#map.size
141
186
  }
142
187
 
143
- forVendor (vendor, handle) {
188
+ /** @returns {TraceState} A shallow copy with independent member storage. */
189
+ clone () {
190
+ return new TraceState(this.#map)
191
+ }
192
+
193
+ /**
194
+ * @template Context
195
+ * @param {string} vendor
196
+ * @param {(state: TraceStateData) => unknown} handle
197
+ * @param {(key: string, context: Context) => boolean} [isOptional]
198
+ * @param {Context} [context]
199
+ * @returns {unknown}
200
+ */
201
+ forVendor (vendor, handle, isOptional, context) {
144
202
  const data = this.#map.get(vendor)
145
203
  const state = TraceStateData.fromString(data)
146
204
  const result = handle(state)
147
205
 
148
- if (state.changed) {
149
- const value = state.toString()
150
- if (value) {
151
- this.set(vendor, value)
152
- } else {
153
- this.delete(vendor)
154
- }
206
+ if (!state.changed) return result
207
+
208
+ let value = state.toString()
209
+ if (value.length > MAX_VALUE_LENGTH && isOptional) {
210
+ state.trimOptionalFields(value.length, isOptional, context)
211
+ value = state.toString()
212
+ }
213
+ if (value.length > MAX_VALUE_LENGTH) {
214
+ if (isOptional) this.delete(vendor)
215
+ return result
155
216
  }
217
+ if (value === data) return result
218
+ if (value) this.set(vendor, value)
219
+ else this.delete(vendor)
156
220
 
157
221
  return result
158
222
  }
159
223
 
160
224
  static fromString (value) {
161
- return fromString(TraceState, value, ',', '=', true)
225
+ return fromString(TraceState, value, ',', '=', true, MAX_LIST_MEMBERS, MAX_VALUE_LENGTH)
162
226
  }
163
227
 
164
228
  toString () {
@@ -213,8 +213,8 @@ class DatadogSpan {
213
213
  setTag (key, value) {
214
214
  this._spanContext.setTag(key, value)
215
215
 
216
- if (isSamplingPriorityTag(key) && this._spanContext._sampling.priority === undefined) {
217
- this._prioritySampler.sample(this, false)
216
+ if (isSamplingPriorityTag(key)) {
217
+ this._prioritySampler.setPriorityFromTags(this, this._spanContext.getTags())
218
218
  }
219
219
 
220
220
  if (tagsUpdateCh.hasSubscribers) {
@@ -236,21 +236,26 @@ class DatadogSpan {
236
236
  if (keyValueMap !== null && typeof keyValueMap === 'object' && !Array.isArray(keyValueMap)) {
237
237
  Object.assign(tags, keyValueMap)
238
238
  mayChangeSamplingPriority =
239
- MANUAL_KEEP in keyValueMap ||
240
- MANUAL_DROP in keyValueMap ||
241
- SAMPLING_PRIORITY in keyValueMap
239
+ keyValueMap[MANUAL_KEEP] !== undefined ||
240
+ keyValueMap[MANUAL_DROP] !== undefined ||
241
+ keyValueMap[SAMPLING_PRIORITY] !== undefined
242
242
  } else {
243
243
  /* istanbul ignore if: v5 fallback, master ships 6.0.0-pre */
244
244
  if (DD_MAJOR < 6 && (typeof keyValueMap === 'string' || Array.isArray(keyValueMap))) {
245
- tagger.add(tags, keyValueMap)
246
- mayChangeSamplingPriority = true
245
+ const samplingTags = {}
246
+ tagger.add(samplingTags, keyValueMap)
247
+ Object.assign(tags, samplingTags)
248
+ mayChangeSamplingPriority =
249
+ samplingTags[MANUAL_KEEP] !== undefined ||
250
+ samplingTags[MANUAL_DROP] !== undefined ||
251
+ samplingTags[SAMPLING_PRIORITY] !== undefined
247
252
  } else {
248
253
  return this
249
254
  }
250
255
  }
251
256
 
252
- if (mayChangeSamplingPriority && this._spanContext._sampling.priority === undefined) {
253
- this._prioritySampler.sample(this, false)
257
+ if (mayChangeSamplingPriority) {
258
+ this._prioritySampler.setPriorityFromTags(this, tags)
254
259
  }
255
260
 
256
261
  if (tagsUpdateCh.hasSubscribers) {
@@ -39,8 +39,9 @@ class DatadogTracer {
39
39
  // silently lose all test spans. The same applies to the Electron exporter:
40
40
  // spans must reach the Electron SDK's IPC bridge, not an OTLP endpoint,
41
41
  // even when OTEL_* vars are set for unrelated telemetry.
42
- if (config.OTEL_TRACES_EXPORTER === 'otlp' && !config.isCiVisibility &&
43
- config.tracing.DD_TRACE_EXPERIMENTAL_EXPORTER !== 'electron') {
42
+ const exportOtlpTraces = config.OTEL_TRACES_EXPORTER === 'otlp' && !config.isCiVisibility &&
43
+ config.tracing.DD_TRACE_EXPERIMENTAL_EXPORTER !== 'electron'
44
+ if (exportOtlpTraces) {
44
45
  const { createOtlpTraceExporter } = require('../opentelemetry/trace')
45
46
  this._exporter = createOtlpTraceExporter(config)
46
47
  } else {
@@ -53,7 +54,13 @@ class DatadogTracer {
53
54
  const { createOtlpSpanStatsExporter } = require('../opentelemetry/metrics')
54
55
  otlpStatsExporter = createOtlpSpanStatsExporter(config)
55
56
  }
56
- this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config, otlpStatsExporter)
57
+ this._processor = new SpanProcessor(
58
+ this._exporter,
59
+ this._prioritySampler,
60
+ config,
61
+ otlpStatsExporter,
62
+ exportOtlpTraces
63
+ )
57
64
  this._url = this._exporter._url
58
65
  this._enableGetRumData = config.rum.DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED
59
66
  this._traceId128BitGenerationEnabled = config.traceId128BitGenerationEnabled