Skip to main content

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}