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.
- package/index.d.ts +163 -22
- package/package.json +2 -2
- package/packages/datadog-esbuild/index.js +9 -6
- package/packages/datadog-instrumentations/src/ai.js +26 -0
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +25 -0
- package/packages/datadog-plugin-openai/src/stream-helpers.js +50 -1
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/remote_config.js +12 -0
- package/packages/dd-trace/src/config/supported-configurations.json +21 -0
- package/packages/dd-trace/src/debugger/constants.js +13 -4
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -1
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +109 -6
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +65 -6
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +22 -10
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +12 -8
- package/packages/dd-trace/src/debugger/devtools_client/send.js +14 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +43 -5
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +3 -8
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +2 -122
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +0 -2
- package/packages/dd-trace/src/debugger/index.js +60 -28
- package/packages/dd-trace/src/debugger/inspect-segment.js +92 -23
- package/packages/dd-trace/src/debugger/pause-duration-histogram.js +74 -0
- package/packages/dd-trace/src/debugger/probe_sampler.js +192 -52
- package/packages/dd-trace/src/debugger/redaction.js +139 -0
- package/packages/dd-trace/src/knuth-hash.js +17 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +66 -2
- package/packages/dd-trace/src/llmobs/experiments/evaluator.js +138 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +213 -90
- package/packages/dd-trace/src/llmobs/experiments/index.js +40 -1
- package/packages/dd-trace/src/llmobs/experiments/noop.js +53 -2
- package/packages/dd-trace/src/llmobs/experiments/remote-evaluator.js +110 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +34 -6
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +2 -2
- package/packages/dd-trace/src/llmobs/sdk.js +19 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +42 -26
- package/packages/dd-trace/src/llmobs/tagger.js +35 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -6
- package/packages/dd-trace/src/openfeature/constants/constants.js +10 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -1
- package/packages/dd-trace/src/openfeature/writers/base.js +39 -18
- package/packages/dd-trace/src/openfeature/writers/flag-eval-evp-hook.js +105 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-aggregation.js +190 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-consumer.js +233 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-context.js +262 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-payload.js +181 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-pii.js +108 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-telemetry.js +154 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-worker.js +77 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluations.js +398 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +5 -4
- package/packages/dd-trace/src/opentelemetry/context_manager.js +3 -0
- package/packages/dd-trace/src/opentelemetry/span_context.js +6 -2
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -0
- package/packages/dd-trace/src/opentelemetry/tracer.js +16 -18
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +116 -25
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +80 -16
- package/packages/dd-trace/src/opentracing/span.js +14 -9
- package/packages/dd-trace/src/opentracing/tracer.js +10 -3
- package/packages/dd-trace/src/otel-sampling.js +171 -0
- package/packages/dd-trace/src/plugins/index.js +0 -1
- package/packages/dd-trace/src/priority_sampler.js +155 -33
- package/packages/dd-trace/src/remote_config/capabilities.js +9 -0
- package/packages/dd-trace/src/sampler.js +2 -5
- package/packages/dd-trace/src/sampling_rule.js +4 -8
- package/packages/dd-trace/src/span_format.js +6 -0
- package/packages/dd-trace/src/span_processor.js +26 -2
- package/packages/dd-trace/src/standalone/index.js +5 -4
- package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +25 -5
- package/packages/dd-trace/src/telemetry/metrics.js +4 -3
- package/packages/datadog-instrumentations/src/postgres.js +0 -7
- package/packages/datadog-instrumentations/src/supabase.js +0 -15
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { AUTO_KEEP } = require('../../../ext/priority')
|
|
4
|
+
const knuthHash = require('./knuth-hash')
|
|
5
|
+
const { SAMPLING_AGENT_DECISION, SAMPLING_RULE_DECISION } = require('./constants')
|
|
6
|
+
|
|
7
|
+
const MAX_THRESHOLD = 2n ** 56n
|
|
8
|
+
const MAX_ENCODABLE_THRESHOLD = MAX_THRESHOLD - 1n
|
|
9
|
+
const UINT64_MASK = 2n ** 64n - 1n
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {unknown} value
|
|
13
|
+
* @param {number} minLength
|
|
14
|
+
* @param {number} maxLength
|
|
15
|
+
* @returns {value is string}
|
|
16
|
+
*/
|
|
17
|
+
function isLowerHex (value, minLength, maxLength) {
|
|
18
|
+
if (typeof value !== 'string' || value.length < minLength || value.length > maxLength) return false
|
|
19
|
+
|
|
20
|
+
for (let index = 0; index < value.length; index++) {
|
|
21
|
+
const code = value.charCodeAt(index)
|
|
22
|
+
if ((code < 48 || code > 57) && (code < 97 || code > 102)) return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Derives the OTel 56-bit random value from Datadog's sampling hash.
|
|
30
|
+
*
|
|
31
|
+
* @param {bigint} traceId
|
|
32
|
+
*/
|
|
33
|
+
function randomValueFor (traceId) {
|
|
34
|
+
return ((~knuthHash(traceId)) & UINT64_MASK) >> 8n
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Converts a sample rate to an OTel 56-bit rejection threshold.
|
|
39
|
+
*
|
|
40
|
+
* @param {number} sampleRate
|
|
41
|
+
*/
|
|
42
|
+
function thresholdFor (sampleRate) {
|
|
43
|
+
if (sampleRate === 1) return 0n
|
|
44
|
+
if (sampleRate === 0) return MAX_ENCODABLE_THRESHOLD
|
|
45
|
+
|
|
46
|
+
const threshold = BigInt(Math.round((1 - sampleRate) * Number(MAX_THRESHOLD)))
|
|
47
|
+
if (threshold < 0n) return 0n
|
|
48
|
+
if (threshold > MAX_ENCODABLE_THRESHOLD) return MAX_ENCODABLE_THRESHOLD
|
|
49
|
+
return threshold
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Formats an OTel threshold with trailing zero nibbles removed.
|
|
54
|
+
*
|
|
55
|
+
* @param {bigint} threshold
|
|
56
|
+
*/
|
|
57
|
+
function formatThreshold (threshold) {
|
|
58
|
+
const value = threshold.toString(16).padStart(14, '0')
|
|
59
|
+
let end = value.length
|
|
60
|
+
while (end > 0 && value.charCodeAt(end - 1) === 48) end--
|
|
61
|
+
return end === 0 ? '0' : value.slice(0, end)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Generates OTel sampling fields for a local probability decision.
|
|
66
|
+
*
|
|
67
|
+
* @param {import('./opentracing/span_context')} context
|
|
68
|
+
* @param {number} probabilityRate
|
|
69
|
+
* @returns {{ randomValue: string, threshold: string } | undefined}
|
|
70
|
+
*/
|
|
71
|
+
function generateFields (context, probabilityRate) {
|
|
72
|
+
const { priority } = context._sampling
|
|
73
|
+
if (priority === undefined || probabilityRate === undefined) return
|
|
74
|
+
|
|
75
|
+
const thresholdValue = thresholdFor(probabilityRate)
|
|
76
|
+
let randomValue = randomValueFor(context._traceId.toBigInt())
|
|
77
|
+
const kept = priority >= AUTO_KEEP
|
|
78
|
+
|
|
79
|
+
if (kept) {
|
|
80
|
+
if (randomValue < thresholdValue) {
|
|
81
|
+
randomValue = thresholdValue
|
|
82
|
+
}
|
|
83
|
+
} else if (randomValue >= thresholdValue) {
|
|
84
|
+
randomValue = thresholdValue > 0n ? thresholdValue - 1n : 0n
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
randomValue: randomValue.toString(16).padStart(14, '0'),
|
|
89
|
+
threshold: formatThreshold(thresholdValue),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Returns the probability rate recorded by a committed Datadog sampling decision.
|
|
95
|
+
* Sampling probes deliberately leave these rate fields unset.
|
|
96
|
+
*
|
|
97
|
+
* @param {import('./opentracing/span_context')} context
|
|
98
|
+
* @returns {number | undefined}
|
|
99
|
+
*/
|
|
100
|
+
function getProbabilityRate (context) {
|
|
101
|
+
if (context._sampling.isProbabilityDecision === false) return
|
|
102
|
+
return (context._trace[SAMPLING_RULE_DECISION] ?? context._trace[SAMPLING_AGENT_DECISION]) || undefined
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Removes malformed OTel sampling fields and returns the valid values.
|
|
107
|
+
*
|
|
108
|
+
* @param {import('./opentracing/propagation/tracestate')} state
|
|
109
|
+
* @returns {{ randomValue: string | undefined, threshold: string | undefined }}
|
|
110
|
+
*/
|
|
111
|
+
function sanitizeFields (state) {
|
|
112
|
+
let randomValue = state.get('rv')
|
|
113
|
+
let threshold = state.get('th')
|
|
114
|
+
|
|
115
|
+
if (!isLowerHex(randomValue, 14, 14)) {
|
|
116
|
+
state.delete('rv')
|
|
117
|
+
randomValue = undefined
|
|
118
|
+
}
|
|
119
|
+
if (!isLowerHex(threshold, 1, 14)) {
|
|
120
|
+
state.delete('th')
|
|
121
|
+
threshold = undefined
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { randomValue, threshold }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Sanitizes inherited OTel sampling fields without changing valid state.
|
|
129
|
+
*
|
|
130
|
+
* @param {import('./opentracing/propagation/tracestate')} traceState
|
|
131
|
+
*/
|
|
132
|
+
function normalizeOtelTraceState (traceState) {
|
|
133
|
+
if (traceState.get('ot') === undefined) return
|
|
134
|
+
traceState.forVendor('ot', sanitizeFields)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Updates the OTel tracestate member to represent the context's sampling decision.
|
|
139
|
+
*
|
|
140
|
+
* @param {import('./opentracing/span_context')} context
|
|
141
|
+
* @param {import('./opentracing/propagation/tracestate')} traceState
|
|
142
|
+
*/
|
|
143
|
+
function updateOtelTraceState (context, traceState) {
|
|
144
|
+
const otelMember = traceState.get('ot')
|
|
145
|
+
const probabilityRate = getProbabilityRate(context)
|
|
146
|
+
if (context._sampling.isProbabilityDecision === false) {
|
|
147
|
+
if (otelMember === undefined) return
|
|
148
|
+
} else if (probabilityRate === undefined) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
traceState.forVendor('ot', state => {
|
|
153
|
+
const { randomValue, threshold } = sanitizeFields(state)
|
|
154
|
+
|
|
155
|
+
if (context._sampling.isProbabilityDecision === false) {
|
|
156
|
+
state.delete('th')
|
|
157
|
+
} else if (randomValue === undefined && threshold === undefined) {
|
|
158
|
+
const generated = generateFields(context, probabilityRate)
|
|
159
|
+
if (generated) {
|
|
160
|
+
// Entries serialize in reverse insertion order, so add th before rv.
|
|
161
|
+
state.set('th', generated.threshold)
|
|
162
|
+
state.set('rv', generated.randomValue)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = {
|
|
169
|
+
normalizeOtelTraceState,
|
|
170
|
+
updateOtelTraceState,
|
|
171
|
+
}
|
|
@@ -15,7 +15,6 @@ const plugins = {
|
|
|
15
15
|
get '@supabase/postgrest-js' () { return require('../../../datadog-plugin-supabase/src') },
|
|
16
16
|
get '@supabase/realtime-js' () { return require('../../../datadog-plugin-supabase/src') },
|
|
17
17
|
get '@supabase/storage-js' () { return require('../../../datadog-plugin-supabase/src') },
|
|
18
|
-
get '@supabase/supabase-js' () { return require('../../../datadog-plugin-supabase/src') },
|
|
19
18
|
get 'durable-functions' () { return require('../../../datadog-plugin-azure-durable-functions/src') },
|
|
20
19
|
get '@azure/service-bus' () { return require('../../../datadog-plugin-azure-service-bus/src') },
|
|
21
20
|
get '@cucumber/cucumber' () { return require('../../../datadog-plugin-cucumber/src') },
|
|
@@ -26,8 +26,10 @@ const {
|
|
|
26
26
|
SAMPLING_MECHANISM_AGENT,
|
|
27
27
|
SAMPLING_MECHANISM_RULE,
|
|
28
28
|
SAMPLING_MECHANISM_MANUAL,
|
|
29
|
+
SAMPLING_MECHANISM_APPSEC,
|
|
29
30
|
SAMPLING_MECHANISM_REMOTE_USER,
|
|
30
31
|
SAMPLING_MECHANISM_REMOTE_DYNAMIC,
|
|
32
|
+
SAMPLING_MECHANISM_AI_GUARD,
|
|
31
33
|
SAMPLING_RULE_DECISION,
|
|
32
34
|
SAMPLING_LIMIT_DECISION,
|
|
33
35
|
SAMPLING_AGENT_DECISION,
|
|
@@ -39,6 +41,17 @@ const DEFAULT_KEY = 'service:,env:'
|
|
|
39
41
|
|
|
40
42
|
const defaultSampler = new Sampler(AUTO_KEEP)
|
|
41
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Returns whether a product has already force-kept the trace.
|
|
46
|
+
*
|
|
47
|
+
* @param {import('./opentracing/span_context')} context
|
|
48
|
+
*/
|
|
49
|
+
function isProductForceKeep (context) {
|
|
50
|
+
const { priority, mechanism } = context._sampling
|
|
51
|
+
return priority === USER_KEEP &&
|
|
52
|
+
(mechanism === SAMPLING_MECHANISM_APPSEC || mechanism === SAMPLING_MECHANISM_AI_GUARD)
|
|
53
|
+
}
|
|
54
|
+
|
|
42
55
|
/**
|
|
43
56
|
* PrioritySampler is responsible for determining whether a span should be sampled
|
|
44
57
|
* based on various rules, rate limits, and priorities. It supports manual and
|
|
@@ -114,10 +127,10 @@ class PrioritySampler {
|
|
|
114
127
|
const tag = this._getPriorityFromTags(context.getTags(), context)
|
|
115
128
|
|
|
116
129
|
if (this.validate(tag)) {
|
|
117
|
-
context._sampling.priority = tag
|
|
118
130
|
context._sampling.mechanism = SAMPLING_MECHANISM_MANUAL
|
|
131
|
+
this._recordDecision(context, tag)
|
|
119
132
|
} else if (auto) {
|
|
120
|
-
|
|
133
|
+
this.decideFromAuto(root)
|
|
121
134
|
} else {
|
|
122
135
|
return
|
|
123
136
|
}
|
|
@@ -125,6 +138,22 @@ class PrioritySampler {
|
|
|
125
138
|
this.#addDecisionMaker(root)
|
|
126
139
|
}
|
|
127
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Applies manual sampling tags from a supplied tag collection using their defined precedence.
|
|
143
|
+
*
|
|
144
|
+
* @param {DatadogSpan} span
|
|
145
|
+
* @param {Record<string, unknown>} tags
|
|
146
|
+
*/
|
|
147
|
+
setPriorityFromTags (span, tags) {
|
|
148
|
+
if (!span) return
|
|
149
|
+
|
|
150
|
+
const context = this._getContext(span)
|
|
151
|
+
if (isProductForceKeep(context)) return
|
|
152
|
+
|
|
153
|
+
const priority = this._getPriorityFromTags(tags, context)
|
|
154
|
+
if (this.validate(priority)) this.setPriority(span, priority)
|
|
155
|
+
}
|
|
156
|
+
|
|
128
157
|
/**
|
|
129
158
|
* Updates agent-provided sampling rates keyed by `service:,env:`.
|
|
130
159
|
*
|
|
@@ -179,14 +208,13 @@ class PrioritySampler {
|
|
|
179
208
|
return // noop span
|
|
180
209
|
}
|
|
181
210
|
|
|
182
|
-
context._sampling.priority = samplingPriority
|
|
183
|
-
|
|
184
211
|
const mechanism = product?.mechanism ?? SAMPLING_MECHANISM_MANUAL
|
|
185
212
|
context._sampling.mechanism = mechanism
|
|
213
|
+
this._recordDecision(context, samplingPriority)
|
|
186
214
|
|
|
187
215
|
log.trace(span, samplingPriority, mechanism)
|
|
188
216
|
|
|
189
|
-
this.#addDecisionMaker(root)
|
|
217
|
+
this.#addDecisionMaker(root, product === undefined)
|
|
190
218
|
}
|
|
191
219
|
|
|
192
220
|
/**
|
|
@@ -201,6 +229,23 @@ class PrioritySampler {
|
|
|
201
229
|
: /** @type {DatadogSpanContext} */ (span)
|
|
202
230
|
}
|
|
203
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Evaluates and records an automatic sampling decision.
|
|
234
|
+
*
|
|
235
|
+
* @param {DatadogSpan} span
|
|
236
|
+
* @returns {SamplingPriority}
|
|
237
|
+
*/
|
|
238
|
+
decideFromAuto (span) {
|
|
239
|
+
const context = this._getContext(span)
|
|
240
|
+
const rule = this.#findRule(span)
|
|
241
|
+
const priority = rule
|
|
242
|
+
? this.#getPriorityByRule(context, rule, true)
|
|
243
|
+
: this.#getPriorityByAgent(context, true)
|
|
244
|
+
|
|
245
|
+
context._sampling.priority = priority
|
|
246
|
+
return priority
|
|
247
|
+
}
|
|
248
|
+
|
|
204
249
|
/**
|
|
205
250
|
* Computes priority using rules and agent rates when no manual tag is present.
|
|
206
251
|
*
|
|
@@ -212,27 +257,25 @@ class PrioritySampler {
|
|
|
212
257
|
const rule = this.#findRule(span)
|
|
213
258
|
|
|
214
259
|
return rule
|
|
215
|
-
? this.#getPriorityByRule(context, rule)
|
|
216
|
-
: this.#getPriorityByAgent(context)
|
|
260
|
+
? this.#getPriorityByRule(context, rule, false)
|
|
261
|
+
: this.#getPriorityByAgent(context, false)
|
|
217
262
|
}
|
|
218
263
|
|
|
219
264
|
/**
|
|
220
|
-
*
|
|
221
|
-
* Included for compatibility with {@link import('./standalone/tracesource_priority_sampler')._getPriorityFromTags}
|
|
265
|
+
* Converts one sampling tag into a user priority.
|
|
222
266
|
*
|
|
223
|
-
* @param {
|
|
267
|
+
* @param {string} key
|
|
268
|
+
* @param {unknown} value
|
|
224
269
|
* @param {DatadogSpanContext} _context
|
|
225
270
|
* @returns {SamplingPriority|undefined}
|
|
226
271
|
*/
|
|
227
|
-
|
|
228
|
-
if (
|
|
272
|
+
_getPriorityFromTag (key, value, _context) {
|
|
273
|
+
if (key === MANUAL_KEEP && value !== false) {
|
|
229
274
|
return USER_KEEP
|
|
230
|
-
} else if (
|
|
275
|
+
} else if (key === MANUAL_DROP && value !== false) {
|
|
231
276
|
return USER_REJECT
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (rawPriority !== undefined) {
|
|
235
|
-
const priority = Math.trunc(rawPriority)
|
|
277
|
+
} else if (key === SAMPLING_PRIORITY && (typeof value === 'number' || typeof value === 'string')) {
|
|
278
|
+
const priority = Math.trunc(/** @type {number} */ (value))
|
|
236
279
|
|
|
237
280
|
if (priority === 1 || priority === 2) {
|
|
238
281
|
return USER_KEEP
|
|
@@ -242,30 +285,85 @@ class PrioritySampler {
|
|
|
242
285
|
}
|
|
243
286
|
}
|
|
244
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Computes priority from manual sampling tags if present.
|
|
290
|
+
* Calls the overridable single-tag conversion so specialized samplers retain their filtering rules.
|
|
291
|
+
*
|
|
292
|
+
* @param {Record<string, unknown>} tags
|
|
293
|
+
* @param {DatadogSpanContext} _context
|
|
294
|
+
* @returns {SamplingPriority|undefined}
|
|
295
|
+
*/
|
|
296
|
+
_getPriorityFromTags (tags, _context) {
|
|
297
|
+
if (!Object.hasOwn(tags, MANUAL_KEEP) && !Object.hasOwn(tags, MANUAL_DROP)) {
|
|
298
|
+
const priority = tags[SAMPLING_PRIORITY]
|
|
299
|
+
if (priority !== undefined) return this._getPriorityFromTag(SAMPLING_PRIORITY, priority, _context)
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return this.#getPriorityFromManualTags(tags, _context)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Computes priority when at least one manual keep/drop tag is present.
|
|
308
|
+
* Value checks cover the common case; own-property checks preserve undefined-valued manual tags.
|
|
309
|
+
*
|
|
310
|
+
* @param {Record<string, unknown>} tags
|
|
311
|
+
* @param {DatadogSpanContext} context
|
|
312
|
+
* @returns {SamplingPriority|undefined}
|
|
313
|
+
*/
|
|
314
|
+
#getPriorityFromManualTags (tags, context) {
|
|
315
|
+
const manualKeep = tags[MANUAL_KEEP]
|
|
316
|
+
if (manualKeep !== undefined || Object.hasOwn(tags, MANUAL_KEEP)) {
|
|
317
|
+
const priority = this._getPriorityFromTag(MANUAL_KEEP, manualKeep, context)
|
|
318
|
+
if (priority !== undefined) return priority
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const manualDrop = tags[MANUAL_DROP]
|
|
322
|
+
if (manualDrop !== undefined || Object.hasOwn(tags, MANUAL_DROP)) {
|
|
323
|
+
const priority = this._getPriorityFromTag(MANUAL_DROP, manualDrop, context)
|
|
324
|
+
if (priority !== undefined) return priority
|
|
325
|
+
}
|
|
326
|
+
const priority = tags[SAMPLING_PRIORITY]
|
|
327
|
+
if (priority !== undefined) return this._getPriorityFromTag(SAMPLING_PRIORITY, priority, context)
|
|
328
|
+
}
|
|
329
|
+
|
|
245
330
|
/**
|
|
246
331
|
* Applies a matching rule and rate limit to compute the sampling priority.
|
|
247
332
|
*
|
|
248
333
|
* @param {DatadogSpanContext} context
|
|
249
334
|
* @param {import('./sampling_rule')} rule
|
|
335
|
+
* @param {boolean} recordDecision
|
|
250
336
|
* @returns {SamplingPriority}
|
|
251
337
|
*/
|
|
252
|
-
#getPriorityByRule (context, rule) {
|
|
253
|
-
context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
|
|
338
|
+
#getPriorityByRule (context, rule, recordDecision) {
|
|
339
|
+
if (recordDecision) context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
|
|
254
340
|
context._trace.tags[SAMPLING_KNUTH_RATE] = formatKnuthRate(rule.sampleRate)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
341
|
+
if (recordDecision) {
|
|
342
|
+
context._sampling.mechanism = SAMPLING_MECHANISM_RULE
|
|
343
|
+
if (rule.provenance === 'customer') {
|
|
344
|
+
context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_USER
|
|
345
|
+
} else if (rule.provenance === 'dynamic') {
|
|
346
|
+
context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_DYNAMIC
|
|
347
|
+
}
|
|
260
348
|
}
|
|
261
349
|
|
|
262
350
|
if (rule.discard) {
|
|
263
351
|
context._sampling.discard = true
|
|
264
352
|
}
|
|
265
353
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
354
|
+
const sampled = rule.sample(context, true)
|
|
355
|
+
if (sampled !== true) {
|
|
356
|
+
if (recordDecision && sampled === undefined) this._recordDecisionMetadata(context)
|
|
357
|
+
return USER_REJECT
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!this._isSampledByRateLimit(context)) {
|
|
361
|
+
// The limiter makes only rejected traces non-probabilistic; allowed traces retain the rule probability.
|
|
362
|
+
if (recordDecision) this._recordDecisionMetadata(context)
|
|
363
|
+
return USER_REJECT
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return USER_KEEP
|
|
269
367
|
}
|
|
270
368
|
|
|
271
369
|
/**
|
|
@@ -287,39 +385,63 @@ class PrioritySampler {
|
|
|
287
385
|
* Computes priority using agent-provided sampling rates.
|
|
288
386
|
*
|
|
289
387
|
* @param {DatadogSpanContext} context
|
|
388
|
+
* @param {boolean} recordDecision
|
|
290
389
|
* @returns {SamplingPriority}
|
|
291
390
|
*/
|
|
292
|
-
#getPriorityByAgent (context) {
|
|
391
|
+
#getPriorityByAgent (context, recordDecision) {
|
|
293
392
|
const key = `service:${context.getTag(SERVICE_NAME)},env:${this._env}`
|
|
294
393
|
// TODO: Change underscored properties to private ones.
|
|
295
394
|
const sampler = this._samplers[key] || this._samplers[DEFAULT_KEY]
|
|
296
395
|
|
|
297
396
|
const rate = sampler.rate()
|
|
298
|
-
context._trace[SAMPLING_AGENT_DECISION] = rate
|
|
397
|
+
if (recordDecision) context._trace[SAMPLING_AGENT_DECISION] = rate
|
|
299
398
|
|
|
300
399
|
if (sampler === defaultSampler) {
|
|
301
|
-
context._sampling.mechanism = SAMPLING_MECHANISM_DEFAULT
|
|
400
|
+
if (recordDecision) context._sampling.mechanism = SAMPLING_MECHANISM_DEFAULT
|
|
302
401
|
} else {
|
|
303
402
|
context._trace.tags[SAMPLING_KNUTH_RATE] = formatKnuthRate(rate)
|
|
304
|
-
context._sampling.mechanism = SAMPLING_MECHANISM_AGENT
|
|
403
|
+
if (recordDecision) context._sampling.mechanism = SAMPLING_MECHANISM_AGENT
|
|
305
404
|
}
|
|
306
405
|
|
|
307
406
|
return sampler.isSampled(context) ? AUTO_KEEP : AUTO_REJECT
|
|
308
407
|
}
|
|
309
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Records that a sampling decision does not represent a probability.
|
|
411
|
+
*
|
|
412
|
+
* @param {DatadogSpanContext} context
|
|
413
|
+
*/
|
|
414
|
+
_recordDecisionMetadata (context) {
|
|
415
|
+
context._sampling.isProbabilityDecision = false
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Records a local non-probability sampling decision.
|
|
420
|
+
*
|
|
421
|
+
* @param {DatadogSpanContext} context
|
|
422
|
+
* @param {SamplingPriority} priority
|
|
423
|
+
* @returns {SamplingPriority}
|
|
424
|
+
*/
|
|
425
|
+
_recordDecision (context, priority) {
|
|
426
|
+
context._sampling.priority = priority
|
|
427
|
+
this._recordDecisionMetadata(context)
|
|
428
|
+
return priority
|
|
429
|
+
}
|
|
430
|
+
|
|
310
431
|
/**
|
|
311
432
|
* Tags the trace with a decision maker when priority is keep, or removes it otherwise.
|
|
312
433
|
*
|
|
313
434
|
* @param {DatadogSpan} span
|
|
435
|
+
* @param {boolean} [overwrite]
|
|
314
436
|
*/
|
|
315
|
-
#addDecisionMaker (span) {
|
|
437
|
+
#addDecisionMaker (span, overwrite = false) {
|
|
316
438
|
const context = span.context()
|
|
317
439
|
const trace = context._trace
|
|
318
440
|
const priority = context._sampling.priority
|
|
319
441
|
const mechanism = context._sampling.mechanism
|
|
320
442
|
|
|
321
443
|
if (priority >= AUTO_KEEP) {
|
|
322
|
-
if (!trace.tags[DECISION_MAKER_KEY]) {
|
|
444
|
+
if (overwrite || !trace.tags[DECISION_MAKER_KEY]) {
|
|
323
445
|
trace.tags[DECISION_MAKER_KEY] = `-${mechanism}`
|
|
324
446
|
}
|
|
325
447
|
} else if (trace.tags[DECISION_MAKER_KEY] !== undefined) {
|
|
@@ -12,19 +12,28 @@ module.exports = {
|
|
|
12
12
|
ASM_CUSTOM_BLOCKING_RESPONSE: 1n << 9n,
|
|
13
13
|
ASM_TRUSTED_IPS: 1n << 10n,
|
|
14
14
|
ASM_API_SECURITY_SAMPLE_RATE: 1n << 11n, // deprecated
|
|
15
|
+
APM_TRACING_SAMPLE_RATE: 1n << 12n,
|
|
16
|
+
APM_TRACING_LOGS_INJECTION: 1n << 13n,
|
|
17
|
+
APM_TRACING_HTTP_HEADER_TAGS: 1n << 14n,
|
|
18
|
+
APM_TRACING_CUSTOM_TAGS: 1n << 15n,
|
|
15
19
|
ASM_PROCESSOR_OVERRIDES: 1n << 16n,
|
|
16
20
|
ASM_CUSTOM_DATA_SCANNERS: 1n << 17n,
|
|
17
21
|
ASM_EXCLUSION_DATA: 1n << 18n,
|
|
22
|
+
APM_TRACING_ENABLED: 1n << 19n,
|
|
18
23
|
ASM_RASP_SQLI: 1n << 21n,
|
|
19
24
|
ASM_RASP_LFI: 1n << 22n,
|
|
20
25
|
ASM_RASP_SSRF: 1n << 23n,
|
|
21
26
|
ASM_RASP_SHI: 1n << 24n,
|
|
27
|
+
APM_TRACING_SAMPLE_RULES: 1n << 29n,
|
|
22
28
|
ASM_AUTO_USER_INSTRUM_MODE: 1n << 31n,
|
|
23
29
|
ASM_ENDPOINT_FINGERPRINT: 1n << 32n,
|
|
24
30
|
ASM_SESSION_FINGERPRINT: 1n << 33n,
|
|
25
31
|
ASM_NETWORK_FINGERPRINT: 1n << 34n,
|
|
26
32
|
ASM_HEADER_FINGERPRINT: 1n << 35n,
|
|
27
33
|
ASM_RASP_CMDI: 1n << 37n,
|
|
34
|
+
APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION: 1n << 38n,
|
|
35
|
+
APM_TRACING_ENABLE_CODE_ORIGIN: 1n << 40n,
|
|
36
|
+
APM_TRACING_ENABLE_LIVE_DEBUGGING: 1n << 41n,
|
|
28
37
|
ASM_DD_MULTICONFIG: 1n << 42n,
|
|
29
38
|
ASM_TRACE_TAGGING_RULES: 1n << 43n,
|
|
30
39
|
ASM_EXTENDED_DATA_COLLECTION: 1n << 44n,
|
|
@@ -5,10 +5,7 @@
|
|
|
5
5
|
// as it is cast into a float64 when computing the threshold
|
|
6
6
|
const MAX_TRACE_ID = 2 ** 64 - 1
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
// Knuth's factor for the sampling algorithm
|
|
11
|
-
const SAMPLING_KNUTH_FACTOR = 1_111_111_111_111_111_111n
|
|
8
|
+
const knuthHash = require('./knuth-hash')
|
|
12
9
|
|
|
13
10
|
/**
|
|
14
11
|
* `Sampler` determines whether or not to sample a trace/span based on the trace ID.
|
|
@@ -52,7 +49,7 @@ class Sampler {
|
|
|
52
49
|
|
|
53
50
|
span = typeof span.context === 'function' ? span.context() : span
|
|
54
51
|
|
|
55
|
-
return (span._traceId.toBigInt()
|
|
52
|
+
return knuthHash(span._traceId.toBigInt()) <= this.#threshold
|
|
56
53
|
}
|
|
57
54
|
}
|
|
58
55
|
|
|
@@ -274,16 +274,12 @@ class SamplingRule {
|
|
|
274
274
|
* Determines whether a span should be sampled based on the configured sampling rule.
|
|
275
275
|
*
|
|
276
276
|
* @param {DatadogSpan|DatadogSpanContext} span - The span or span context to evaluate.
|
|
277
|
+
* @param {boolean} [distinguishRateLimit] Return `undefined` instead of `false` for a rate-limit rejection.
|
|
277
278
|
*/
|
|
278
|
-
sample (span) {
|
|
279
|
-
if (!this._sampler.isSampled(span))
|
|
280
|
-
return false
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (this._limiter) {
|
|
284
|
-
return this._limiter.isAllowed()
|
|
285
|
-
}
|
|
279
|
+
sample (span, distinguishRateLimit = false) {
|
|
280
|
+
if (!this._sampler.isSampled(span)) return false
|
|
286
281
|
|
|
282
|
+
if (this._limiter && !this._limiter.isAllowed()) return distinguishRateLimit ? undefined : false
|
|
287
283
|
return true
|
|
288
284
|
}
|
|
289
285
|
}
|
|
@@ -47,6 +47,7 @@ const { IGNORE_OTEL_ERROR } = constants
|
|
|
47
47
|
* @property {number} duration
|
|
48
48
|
* @property {Array} links
|
|
49
49
|
* @property {Array<SpanEvent> | undefined} span_events
|
|
50
|
+
* @property {string} [trace_state] W3C tracestate, populated only for OTLP export.
|
|
50
51
|
*
|
|
51
52
|
* @typedef {object} SpanEvent Raw span event as stored on the span; the encoder
|
|
52
53
|
* layer derives `time_unix_nano` from `startTime` via `eventTimeNano`.
|
|
@@ -55,6 +56,11 @@ const { IGNORE_OTEL_ERROR } = constants
|
|
|
55
56
|
* @property {Record<string, string>} [attributes]
|
|
56
57
|
*/
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* @param {import('./opentracing/span')} span
|
|
61
|
+
* @param {boolean} [isFirstSpanInChunk]
|
|
62
|
+
* @param {string | false} [tagForFirstSpanInChunk]
|
|
63
|
+
*/
|
|
58
64
|
function format (span, isFirstSpanInChunk = false, tagForFirstSpanInChunk = false) {
|
|
59
65
|
const formatted = formatSpan(span)
|
|
60
66
|
|
|
@@ -7,17 +7,40 @@ const SpanSampler = require('./span_sampler')
|
|
|
7
7
|
const GitMetadataTagger = require('./git_metadata_tagger')
|
|
8
8
|
const processTags = require('./process-tags')
|
|
9
9
|
const { applyHttpOtelSemantics } = require('./plugins/util/http-otel-semantics')
|
|
10
|
+
const TraceState = require('./opentracing/propagation/tracestate')
|
|
10
11
|
const { APM_TRACING_ENABLED_KEY, SDK_OTLP_EXPORT_KEY } = require('./constants')
|
|
11
12
|
|
|
12
13
|
const startedSpans = new WeakSet()
|
|
13
14
|
const finishedSpans = new WeakSet()
|
|
14
15
|
|
|
16
|
+
let otelSampling
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Adds first-class OTLP trace context to a DD-formatted span.
|
|
20
|
+
*
|
|
21
|
+
* @param {import('./opentracing/span')} span
|
|
22
|
+
* @param {boolean} isFirstSpanInChunk
|
|
23
|
+
* @param {string | false} processTagsValue
|
|
24
|
+
*/
|
|
25
|
+
function formatOtlpSpan (span, isFirstSpanInChunk, processTagsValue) {
|
|
26
|
+
const formattedSpan = spanFormat(span, isFirstSpanInChunk, processTagsValue)
|
|
27
|
+
const context = span.context()
|
|
28
|
+
const traceState = context._tracestate?.clone() ?? new TraceState()
|
|
29
|
+
otelSampling ??= require('./otel-sampling')
|
|
30
|
+
otelSampling.updateOtelTraceState(context, traceState)
|
|
31
|
+
formattedSpan.trace_state = traceState.toString()
|
|
32
|
+
return formattedSpan
|
|
33
|
+
}
|
|
34
|
+
|
|
15
35
|
class SpanProcessor {
|
|
16
|
-
|
|
36
|
+
#formatSpan
|
|
37
|
+
|
|
38
|
+
constructor (exporter, prioritySampler, config, otlpStatsExporter, exportOtlpTraces) {
|
|
17
39
|
this._exporter = exporter
|
|
18
40
|
this._prioritySampler = prioritySampler
|
|
19
41
|
this._config = config
|
|
20
42
|
this._killAll = false
|
|
43
|
+
this.#formatSpan = exportOtlpTraces ? formatOtlpSpan : spanFormat
|
|
21
44
|
|
|
22
45
|
if (config.stats?.DD_TRACE_STATS_COMPUTATION_ENABLED &&
|
|
23
46
|
!config.appsec?.DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED) {
|
|
@@ -72,12 +95,13 @@ class SpanProcessor {
|
|
|
72
95
|
let isFirstSpanInChunk = true
|
|
73
96
|
const stampApmDisabled = this._config.apmTracingEnabled === false
|
|
74
97
|
const discard = this.#isDiscarded(spanContext)
|
|
98
|
+
const formatSpan = this.#formatSpan
|
|
75
99
|
|
|
76
100
|
for (const span of started) {
|
|
77
101
|
if (span._duration === undefined) {
|
|
78
102
|
active.push(span)
|
|
79
103
|
} else if (!discard) {
|
|
80
|
-
const formattedSpan =
|
|
104
|
+
const formattedSpan = formatSpan(span, isFirstSpanInChunk, this._processTags)
|
|
81
105
|
if (stampApmDisabled) {
|
|
82
106
|
formattedSpan.metrics[APM_TRACING_ENABLED_KEY] = 0
|
|
83
107
|
}
|
|
@@ -23,11 +23,12 @@ function configure (config) {
|
|
|
23
23
|
function onSpanExtract ({ spanContext = {} }) {
|
|
24
24
|
if (!spanContext._trace?.tags || !spanContext._sampling) return
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
if (!hasTraceSourcePropagationTag(spanContext._trace.tags)) {
|
|
28
|
-
spanContext._sampling.priority = undefined
|
|
29
|
-
} else if (spanContext._sampling.priority !== USER_KEEP) {
|
|
26
|
+
if (hasTraceSourcePropagationTag(spanContext._trace.tags)) {
|
|
30
27
|
spanContext._sampling.priority = USER_KEEP
|
|
28
|
+
spanContext._sampling.isProbabilityDecision = false
|
|
29
|
+
} else {
|
|
30
|
+
// Reset upstream priority if _dd.p.ts is not found.
|
|
31
|
+
spanContext._sampling.priority = undefined
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|