agent_data_plane_config/domains/traces.rs
1//! Traces domain: APM trace processing, including environment, sampling, and obfuscation.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize, Serializer};
6
7use crate::defaults::{
8 DEFAULT_ERROR_SAMPLING_ENABLED, DEFAULT_MAX_RESOURCE_LEN, DEFAULT_RARE_SAMPLER_CARDINALITY,
9 DEFAULT_RARE_SAMPLER_COOLDOWN_SECS, DEFAULT_RARE_SAMPLER_TPS,
10};
11
12/// A beta APM feature flag.
13///
14/// The feature inventory is not stable across agent versions: a flag may be promoted to a
15/// dedicated setting or dropped. Unrecognized values are carried as `ApmFeature::Other` rather
16/// than rejected, so configurations for newer or removed flags load unchanged.
17#[derive(Clone, Debug, Eq, Hash, PartialEq)]
18pub enum ApmFeature {
19 /// Switches the probabilistic sampler from hashing the low 64 bits of the trace ID to hashing
20 /// the full 128-bit ID.
21 ProbabilisticSamplerFullTraceId,
22
23 /// An unrecognized feature ID, carried verbatim.
24 Other(String),
25}
26
27impl ApmFeature {
28 /// Returns the feature's configuration ID.
29 pub fn as_str(&self) -> &str {
30 match self {
31 ApmFeature::ProbabilisticSamplerFullTraceId => "probabilistic_sampler_full_trace_id",
32 ApmFeature::Other(feature) => feature,
33 }
34 }
35}
36
37impl From<&str> for ApmFeature {
38 fn from(feature: &str) -> Self {
39 match feature {
40 "probabilistic_sampler_full_trace_id" => ApmFeature::ProbabilisticSamplerFullTraceId,
41 other => ApmFeature::Other(other.to_owned()),
42 }
43 }
44}
45
46impl Serialize for ApmFeature {
47 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
48 where
49 S: Serializer,
50 {
51 serializer.serialize_str(self.as_str())
52 }
53}
54
55/// Resolved traces configuration.
56#[derive(Clone, Debug, PartialEq, Serialize)]
57pub struct Domain {
58 /// Trace environment configured by `apm_config.env` (`DD_APM_ENV`).
59 ///
60 /// `None` when no input set the key. An explicit empty value is kept as `Some("")`, which is
61 /// distinct from `None`: it still takes precedence over `env` and the `env:` global tags.
62 ///
63 /// Defaults to `None`. The value is not normalized; the trace pipeline resolves its environment
64 /// from this, `env`, and the global tags.
65 pub apm_env: Option<String>,
66
67 /// Agent environment configured by the top-level `env` key (`DD_ENV`).
68 ///
69 /// `None` when no input set the key. An explicit empty value is kept as `Some("")`, which still
70 /// takes precedence over the `env:` global tags.
71 ///
72 /// Defaults to `None`. The value is not normalized; the trace pipeline uses it when `apm_env` is
73 /// `None`.
74 pub env: Option<String>,
75
76 /// Whether trace stats are computed separately per span kind.
77 pub compute_stats_by_span_kind: bool,
78
79 /// Span tags promoted to peer tags for peer-service aggregation.
80 pub peer_tags: Vec<String>,
81
82 /// Whether stats are aggregated by peer tags.
83 pub peer_tags_aggregation: bool,
84
85 /// Whether error spans are sampled independently of the base sampler. (not in Datadog Agent
86 /// config schema)
87 pub error_sampling_enabled: bool,
88
89 /// Whether error tracking runs standalone, without full trace ingestion.
90 pub error_tracking_standalone_enabled: bool,
91
92 /// Target number of error traces sampled per second.
93 pub errors_per_second: f64,
94
95 /// Target number of traces sampled per second.
96 pub target_traces_per_second: f64,
97
98 /// Multiplier applied to every learned keep-rate of the adaptive samplers.
99 ///
100 /// Defaults to 1.0, leaving the computed rates unchanged; values above 1.0 keep
101 /// proportionally more. A value of `0` drops every trace decided by a learned rate.
102 /// Signatures without a learned rate—new, or freshly cleaned up—fall to the sampler's
103 /// default rate, which this multiplier does not scale.
104 ///
105 /// Operators who want coarser or finer sampling than the adaptive target computes can set
106 /// this once instead of re-planning the target: raise it temporarily for incident
107 /// investigations, lower it to control ingestion cost. The trade-off is coverage against
108 /// volume—every adaptive sampler's output scales together, so the per-service distribution
109 /// is preserved while the total scales.
110 pub extra_sample_rate: f64,
111
112 /// Event extraction rates by service, for the legacy analytics configuration.
113 ///
114 /// Empty by default, so no spans are nominated through this map. Rates apply only to
115 /// top-level spans of the named service. Superseded by `analyzed_spans_by_service`; the
116 /// legacy map is ignored when both are set.
117 pub analyzed_rate_by_service: HashMap<String, f64>,
118
119 /// Event extraction rates by service and operation name.
120 ///
121 /// Empty by default. Configured keys combine service and operation as
122 /// `service|operation` with exactly one separator; malformed keys and non-numeric rates
123 /// are dropped with a warning at translation. Takes precedence over
124 /// `analyzed_rate_by_service` when non-empty.
125 pub analyzed_spans_by_service: HashMap<String, HashMap<String, f64>>,
126
127 /// Upper bound on the average number of extracted events sampled per second.
128 ///
129 /// Defaults to 200. A decaying rate estimate caps survivors at this budget; spans of
130 /// manually kept traces bypass the budget entirely. Raise it when customers need more
131 /// event volume than the default allows; in normal traffic it should sit high enough that
132 /// the cap never engages, since capped events are dropped probabilistically.
133 pub max_events_per_second: f64,
134
135 /// Maximum number of service signatures the priority sampler tracks rates for.
136 ///
137 /// Defaults to 5000. If set to `0`, the default of 5000 applies. Distinct services beyond
138 /// the cap evict the least recently used entries and lose their learned rates.
139 ///
140 /// Deployments with more distinct (service, environment) pairs than the default should
141 /// raise this. The trade-off is memory—the catalog holds a map entry and an LRU entry
142 /// per tracked signature—against rate fidelity, since evicted services re-learn their
143 /// rates from cold.
144 pub max_catalog_entries: usize,
145
146 /// Beta APM feature flags enabled for traces.
147 ///
148 /// Empty by default. Unrecognized values are carried as `ApmFeature::Other` and ignored
149 /// without a warning: the flag inventory is not stable across versions. Enable
150 /// `ApmFeature::ProbabilisticSamplerFullTraceId` on every probabilistic sampler in the
151 /// ingestion path so they keep the same traces.
152 pub features: Vec<ApmFeature>,
153
154 /// Whether the rare-span sampler is enabled.
155 pub enable_rare_sampler: bool,
156
157 /// Rare-span sampler settings.
158 pub rare_sampler: RareSampler,
159
160 /// Probabilistic sampler settings.
161 pub probabilistic_sampler: ProbabilisticSampler,
162
163 /// Per-subsystem trace obfuscation settings.
164 pub obfuscation: Obfuscation,
165
166 /// OTTL span-drop filter settings.
167 pub ottl_filter: OttlFilter,
168
169 /// OTTL span-transform settings.
170 pub ottl_transform: OttlTransform,
171
172 /// Maximum length of a span's resource name, in bytes; longer resources are truncated.
173 ///
174 /// Defaults to 5000 bytes. If set to `0`, all span resources are truncated to empty strings.
175 /// Change this only if legitimate resources exceed the default.
176 pub max_resource_len: usize,
177
178 /// Regex-based trace tag replacement rules, used to scrub sensitive values the built-in
179 /// obfuscation passes through. Defaults to no rules.
180 ///
181 /// Rules run in order after obfuscation and before stats and sampling. A `"*"` rule rewrites
182 /// every non-`_`-prefixed tag, the resource, and span events; `"resource.name"` only the
183 /// resource; any other name only that tag. Matched values are stored as strings, and a
184 /// pattern that fails to compile prevents startup.
185 pub replace_tags: Vec<ReplaceRule>,
186}
187
188impl Default for Domain {
189 fn default() -> Self {
190 Self {
191 // Witnessed fields start as placeholders and are overwritten by Datadog `drive`.
192 apm_env: None,
193 env: None,
194 compute_stats_by_span_kind: false,
195 peer_tags: Vec::new(),
196 peer_tags_aggregation: false,
197 error_tracking_standalone_enabled: false,
198 errors_per_second: 0.0,
199 target_traces_per_second: 0.0,
200 extra_sample_rate: 1.0,
201 analyzed_rate_by_service: HashMap::new(),
202 analyzed_spans_by_service: HashMap::new(),
203 max_events_per_second: 0.0,
204 max_catalog_entries: 0,
205 features: Vec::new(),
206 enable_rare_sampler: false,
207 probabilistic_sampler: ProbabilisticSampler::default(),
208 obfuscation: Obfuscation::default(),
209 // Saluki-only fields own their absent-key behavior here.
210 max_resource_len: DEFAULT_MAX_RESOURCE_LEN,
211 replace_tags: Vec::new(),
212 error_sampling_enabled: DEFAULT_ERROR_SAMPLING_ENABLED,
213 rare_sampler: RareSampler::default(),
214 ottl_filter: OttlFilter::default(),
215 ottl_transform: OttlTransform::default(),
216 }
217 }
218}
219
220/// A regex-based trace tag replacement rule.
221#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
222pub struct ReplaceRule {
223 /// Tag key the rule targets: `"*"`, `"resource.name"`, or a literal tag key.
224 pub name: String,
225
226 /// Regular expression matched against each targeted value.
227 pub pattern: String,
228
229 /// Text spliced in place of each match; `$1`-style group references are supported.
230 pub repl: String,
231}
232
233/// Rare-span sampler.
234#[derive(Clone, Debug, PartialEq, Serialize)]
235pub struct RareSampler {
236 /// Maximum number of distinct span signatures tracked. (not in Datadog Agent config schema)
237 pub cardinality: usize,
238
239 /// Cooldown, in seconds, before a signature may be sampled again. (not in Datadog Agent config
240 /// schema)
241 pub cooldown: f64,
242
243 /// Target rare-span traces sampled per second. (not in Datadog Agent config schema)
244 pub tps: f64,
245}
246
247impl Default for RareSampler {
248 fn default() -> Self {
249 Self {
250 cardinality: DEFAULT_RARE_SAMPLER_CARDINALITY,
251 cooldown: DEFAULT_RARE_SAMPLER_COOLDOWN_SECS,
252 tps: DEFAULT_RARE_SAMPLER_TPS,
253 }
254 }
255}
256
257/// APM probabilistic sampler.
258#[derive(Clone, Debug, Default, PartialEq, Serialize)]
259pub struct ProbabilisticSampler {
260 /// Whether the probabilistic sampler is enabled.
261 pub enabled: bool,
262
263 /// Seed mixed into the trace-ID hash before sampling, from 0 to 4,294,967,295.
264 ///
265 /// Defaults to `0`. Samplers in the same ingestion path keep the same traces only when their
266 /// seeds match, so align this with every other probabilistic sampler that sees the traffic.
267 /// Values outside the range fail configuration.
268 pub hash_seed: u32,
269
270 /// Percentage of traces the probabilistic sampler keeps.
271 pub sampling_percentage: f64,
272}
273
274/// Trace obfuscation, one group per supported subsystem.
275#[derive(Clone, Debug, Default, PartialEq, Serialize)]
276pub struct Obfuscation {
277 /// Credit-card obfuscation in span metadata.
278 pub credit_cards: CreditCardObfuscation,
279
280 /// Elasticsearch query obfuscation.
281 pub elasticsearch: JsonQueryObfuscation,
282
283 /// HTTP path and query obfuscation.
284 pub http: HttpObfuscation,
285
286 /// Memcached command obfuscation.
287 pub memcached: MemcachedObfuscation,
288
289 /// MongoDB query obfuscation.
290 pub mongodb: JsonQueryObfuscation,
291
292 /// OpenSearch query obfuscation.
293 pub opensearch: JsonQueryObfuscation,
294
295 /// Redis command obfuscation.
296 pub redis: CacheObfuscation,
297
298 /// Valkey command obfuscation.
299 pub valkey: CacheObfuscation,
300
301 /// SQL query obfuscation. (not in Datadog Agent config schema)
302 pub sql: SqlObfuscation,
303}
304
305/// Credit-card obfuscation.
306#[derive(Clone, Debug, Default, PartialEq, Serialize)]
307pub struct CreditCardObfuscation {
308 /// Whether credit-card numbers are obfuscated.
309 pub enabled: bool,
310
311 /// Tag or field names whose values are not obfuscated.
312 pub keep_values: Vec<String>,
313
314 /// Whether a Luhn check is applied before a value is treated as a card number.
315 pub luhn: bool,
316}
317
318/// Obfuscation shape shared by the JSON-query engines (Elasticsearch, MongoDB, OpenSearch).
319#[derive(Clone, Debug, Default, PartialEq, Serialize)]
320pub struct JsonQueryObfuscation {
321 /// Whether queries are obfuscated.
322 pub enabled: bool,
323
324 /// JSON keys whose values are not obfuscated.
325 pub keep_values: Vec<String>,
326
327 /// JSON keys whose values are obfuscated as embedded SQL.
328 pub obfuscate_sql_values: Vec<String>,
329}
330
331/// HTTP path/query obfuscation.
332#[derive(Clone, Debug, Default, PartialEq, Serialize)]
333pub struct HttpObfuscation {
334 /// Whether path segments containing digits are removed.
335 pub remove_paths_with_digits: bool,
336
337 /// Whether the query string is removed.
338 pub remove_query_string: bool,
339}
340
341/// Memcached command obfuscation.
342#[derive(Clone, Debug, Default, PartialEq, Serialize)]
343pub struct MemcachedObfuscation {
344 /// Whether Memcached commands are obfuscated.
345 pub enabled: bool,
346
347 /// Whether the command verb is preserved.
348 pub keep_command: bool,
349}
350
351/// Obfuscation shape shared by the key/value caches (redis, valkey).
352#[derive(Clone, Debug, Default, PartialEq, Serialize)]
353pub struct CacheObfuscation {
354 /// Whether cache commands are obfuscated.
355 pub enabled: bool,
356
357 /// Whether all command arguments are removed.
358 pub remove_all_args: bool,
359}
360
361/// SQL obfuscation.
362#[derive(Clone, Debug, Default, PartialEq, Serialize)]
363pub struct SqlObfuscation {
364 /// SQL dialect the obfuscator parses against.
365 pub dbms: String,
366
367 /// Whether dollar-quoted function bodies are preserved.
368 pub dollar_quoted_func: bool,
369
370 /// Whether column and table aliases are preserved.
371 pub keep_sql_alias: bool,
372
373 /// Whether digits in identifiers are replaced with a placeholder.
374 pub replace_digits: bool,
375
376 /// Whether table names are collected as metadata.
377 pub table_names: bool,
378}
379
380/// Error-handling mode for OTTL condition/statement evaluation, shared by the OTTL filter and
381/// transform processors.
382#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
383pub enum OttlErrorMode {
384 /// Log evaluation errors and continue.
385 Ignore,
386 /// Swallow evaluation errors silently and continue.
387 Silent,
388 /// Propagate the error up the pipeline; the payload is dropped.
389 #[default]
390 Propagate,
391}
392
393/// OTTL filter processor: span-drop conditions applied during trace enrichment.
394#[derive(Clone, Debug, Default, PartialEq, Serialize)]
395pub struct OttlFilter {
396 /// How evaluation errors in the filter conditions are handled. (not in Datadog Agent config
397 /// schema)
398 pub error_mode: OttlErrorMode,
399
400 /// OTTL conditions; a span matching any of them is dropped. (not in Datadog Agent config
401 /// schema)
402 pub span_conditions: Vec<String>,
403}
404
405/// OTTL transform processor: span-mutating statements applied during trace enrichment.
406#[derive(Clone, Debug, Default, PartialEq, Serialize)]
407pub struct OttlTransform {
408 /// How evaluation errors in the transform statements are handled. (not in Datadog Agent config
409 /// schema)
410 pub error_mode: OttlErrorMode,
411
412 /// OTTL statements applied to each span. (not in Datadog Agent config schema)
413 pub trace_statements: Vec<String>,
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 #[test]
421 fn known_feature_ids_map_to_their_variants() {
422 assert_eq!(
423 ApmFeature::from("probabilistic_sampler_full_trace_id"),
424 ApmFeature::ProbabilisticSamplerFullTraceId
425 );
426 assert_eq!(
427 ApmFeature::from("error_rare_sample_tracer_drop"),
428 ApmFeature::Other("error_rare_sample_tracer_drop".to_owned())
429 );
430 }
431
432 #[test]
433 fn feature_ids_round_trip_through_as_str() {
434 for feature in [
435 ApmFeature::ProbabilisticSamplerFullTraceId,
436 ApmFeature::Other("table_names".to_owned()),
437 ] {
438 assert_eq!(ApmFeature::from(feature.as_str()), feature);
439 }
440 }
441}