Skip to main content

agent_data_plane_config/domains/
dogstatsd.rs

1//! DogStatsD domain: source listeners, parsing, origin detection, aggregation, mapping, filters
2//! (some dynamic-capable), and debug logging.
3
4use std::collections::HashMap;
5use std::fmt;
6use std::num::{NonZeroU64, NonZeroUsize};
7use std::path::PathBuf;
8use std::str::FromStr;
9use std::time::Duration;
10
11use serde::{Deserialize, Serialize};
12
13use crate::defaults::{
14    DEFAULT_AGGREGATE_CONTEXT_LIMIT, DEFAULT_AGGREGATE_FLUSH_INTERVAL,
15    DEFAULT_AGGREGATE_PASSTHROUGH_IDLE_FLUSH_TIMEOUT, DEFAULT_AGGREGATE_WINDOW_DURATION_SECONDS,
16    DEFAULT_DOGSTATSD_ALLOW_CONTEXT_HEAP_ALLOCS, DEFAULT_DOGSTATSD_AUTOSCALE_UDP_LISTENERS,
17    DEFAULT_DOGSTATSD_BUFFER_COUNT, DEFAULT_DOGSTATSD_BUFFER_COUNT_MAX, DEFAULT_DOGSTATSD_CACHED_CONTEXTS_LIMIT,
18    DEFAULT_DOGSTATSD_CACHED_TAGSETS_LIMIT, DEFAULT_DOGSTATSD_MAPPER_STRING_INTERNER_SIZE_BYTES,
19    DEFAULT_DOGSTATSD_MINIMUM_SAMPLE_RATE, DEFAULT_DOGSTATSD_PERMISSIVE_DECODING, DEFAULT_DOGSTATSD_TCP_PORT,
20};
21use crate::Error;
22
23// TODO: better name than Domain? Pipeline? Topology? BlueprintConfig?
24/// Resolved DogStatsD configuration.
25#[derive(Clone, Debug, Default, PartialEq, Serialize)]
26pub struct Domain {
27    /// Source listeners and packet-decoding options.
28    pub listeners: Listeners,
29
30    /// Origin detection and tag cardinality.
31    pub origin: OriginDetection,
32
33    /// Context cache sizing and the sample-rate floor.
34    pub contexts: Contexts,
35
36    /// Metric aggregation window and flush behavior.
37    pub aggregation: Aggregation,
38
39    /// Metric-name mapper.
40    pub mapper: Mapper,
41
42    /// Which payload types are emitted.
43    pub enable_payloads: EnablePayloads,
44
45    /// Metric-name filtering.
46    pub metric_filter: MetricFilter,
47
48    /// Metric namespace prefixing.
49    pub prefix_filter: PrefixFilter,
50
51    /// Per-metric tag include/exclude rules.
52    pub tag_filterlist: Vec<MetricTagFilterEntry>,
53
54    /// Per-metric tag value allow-list rules.
55    pub tag_value_allowlist: Vec<MetricTagValueAllowlistEntry>,
56
57    /// Extra tags added to every metric.
58    pub tags: Vec<String>,
59
60    /// Telemetry emitted by the DogStatsD source.
61    pub telemetry: Telemetry,
62
63    /// Debug-log and verbose-log settings for the DogStatsD source.
64    pub debug_log: DebugLog,
65}
66
67/// Source listeners and packet-decoding options.
68#[derive(Clone, Debug, PartialEq, Serialize)]
69pub struct Listeners {
70    /// UDP port DogStatsD listens on.
71    pub port: u16,
72
73    /// TCP port DogStatsD listens on. (not in Datadog Agent config schema)
74    ///
75    /// Defaults to `0`, which disables TCP.
76    pub tcp_port: u16,
77
78    /// Path of the Unix datagram socket DogStatsD listens on.
79    pub socket: Option<String>,
80
81    /// Path of the Unix stream socket DogStatsD listens on.
82    pub stream_socket: Option<String>,
83
84    /// Windows named pipe name DogStatsD listens on. Unset when no named pipe is configured.
85    pub pipe_name: Option<String>,
86
87    /// SDDL security descriptor applied to the Windows named pipe listener.
88    pub windows_pipe_security_descriptor: String,
89
90    /// Whether the UDP listener accepts traffic from non-local addresses.
91    pub non_local_traffic: bool,
92
93    /// Size, in bytes, requested for the socket receive buffer.
94    pub so_rcvbuf: usize,
95
96    /// Size, in bytes, of each packet receive buffer.
97    pub buffer_size: usize,
98
99    /// Number of receive buffers allocated. (not in Datadog Agent config schema)
100    pub buffer_count: usize,
101
102    /// Maximum number of receive buffers. (not in Datadog Agent config schema)
103    pub buffer_count_max: usize,
104
105    /// Number of connectionless packet decoder workers.
106    pub workers_count: usize,
107
108    /// Whether to bind multiple UDP sockets via `SO_REUSEPORT`. (not in Datadog Agent config
109    /// schema)
110    pub autoscale_udp_listeners: bool,
111
112    /// Path a traffic capture is written to or replayed from.
113    pub capture_path: PathBuf,
114
115    /// Maximum number of captured packets queued for persistence.
116    ///
117    /// The capture writer raises a depth below its own minimum, so the default, 0, selects that minimum.
118    pub capture_depth: usize,
119
120    /// End-of-line markers required to terminate a stream-socket message.
121    pub eol_required: Vec<String>,
122
123    /// Whether to log stream messages that exceed the buffer size.
124    pub stream_log_too_big: bool,
125
126    /// Whether startup requires at least one active DogStatsD listener.
127    ///
128    /// Defaults to `false`. A listener that can't be created is logged and skipped either way. When `true`, startup
129    /// fails if no configured listener can be created. When `false`, the source keeps running without listeners.
130    ///
131    /// Enable this when a process supervisor should detect and restart a process that can't receive DogStatsD
132    /// traffic.
133    pub require_listener: bool,
134
135    /// Whether to relax decoder strictness on malformed packets. (not in Datadog Agent config
136    /// schema)
137    pub permissive_decoding: bool,
138
139    /// Host that received metrics are additionally forwarded to.
140    pub forward_host: Option<String>,
141
142    /// Port that received metrics are additionally forwarded to.
143    pub forward_port: u16,
144}
145
146impl Default for Listeners {
147    fn default() -> Self {
148        Self {
149            // Saluki-only settings retain these defaults when unset.
150            buffer_count: DEFAULT_DOGSTATSD_BUFFER_COUNT,
151            buffer_count_max: DEFAULT_DOGSTATSD_BUFFER_COUNT_MAX,
152            permissive_decoding: DEFAULT_DOGSTATSD_PERMISSIVE_DECODING,
153            tcp_port: DEFAULT_DOGSTATSD_TCP_PORT,
154            autoscale_udp_listeners: DEFAULT_DOGSTATSD_AUTOSCALE_UDP_LISTENERS,
155            // Witnessed settings are overwritten during translation.
156            port: 0,
157            socket: None,
158            stream_socket: None,
159            pipe_name: None,
160            windows_pipe_security_descriptor: String::new(),
161            non_local_traffic: false,
162            so_rcvbuf: 0,
163            buffer_size: 0,
164            workers_count: 0,
165            capture_path: PathBuf::new(),
166            capture_depth: 0,
167            eol_required: Vec::new(),
168            stream_log_too_big: false,
169            require_listener: false,
170            forward_host: None,
171            forward_port: 0,
172        }
173    }
174}
175
176/// Origin detection and tag cardinality.
177#[derive(Clone, Debug, Default, PartialEq, Serialize)]
178pub struct OriginDetection {
179    /// Whether origin detection tags metrics with their source workload.
180    pub detection: bool,
181
182    /// Whether client-supplied origin information is honored.
183    pub detection_client: bool,
184
185    /// Whether the unified origin-detection scheme is used.
186    pub unified: bool,
187
188    /// Whether a client may opt out of origin detection per metric.
189    pub optout_enabled: bool,
190
191    /// Whether a client-supplied entity ID takes precedence over the detected origin.
192    pub entity_id_precedence: bool,
193
194    /// Tag cardinality applied to origin-detected tags, when a payload doesn't specify one itself.
195    pub tag_cardinality: OriginTagCardinality,
196}
197
198/// Tag cardinality applied during origin detection.
199#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
200pub enum OriginTagCardinality {
201    #[default]
202    Low,
203    Orchestrator,
204    High,
205    None,
206}
207
208impl FromStr for OriginTagCardinality {
209    type Err = Error;
210
211    fn from_str(value: &str) -> Result<Self, Self::Err> {
212        // The Datadog Agent lower-cases the value before matching, and accepts `orch` as a shorthand
213        // for `orchestrator`.
214        match value.to_ascii_lowercase().as_str() {
215            "low" => Ok(Self::Low),
216            "orch" | "orchestrator" => Ok(Self::Orchestrator),
217            "high" => Ok(Self::High),
218            "none" => Ok(Self::None),
219            other => Err(Error::new_without_source(format!(
220                "unknown tag cardinality `{other}`; expected low, orch, orchestrator, high, or none"
221            ))),
222        }
223    }
224}
225
226/// Telemetry emitted by the DogStatsD source.
227#[derive(Clone, Debug, Default, PartialEq, Serialize)]
228pub struct Telemetry {
229    /// Whether processed-metric telemetry is broken down by detected origin.
230    pub origin_breakdown: bool,
231}
232
233/// Context cache sizing and sample-rate floor.
234#[derive(Clone, Debug, PartialEq, Serialize)]
235pub struct Contexts {
236    /// Maximum number of metric contexts held in the cache. (not in Datadog Agent config schema)
237    pub cached_contexts_limit: usize,
238
239    /// Maximum number of tagsets held in the cache. (not in Datadog Agent config schema)
240    pub cached_tagsets_limit: usize,
241
242    /// Number of entries the context string interner holds.
243    pub string_interner_size: u64,
244
245    /// Byte budget for the context string interner, overriding the entry count when set. (not in
246    /// Datadog Agent config schema)
247    pub string_interner_size_bytes: Option<u64>,
248
249    /// Whether contexts may be heap-allocated when the interner is full. (not in Datadog Agent
250    /// config schema)
251    pub allow_context_heap_allocs: bool,
252
253    /// Lowest sample rate honored; the decoder warns and clamps anything below it. (not in Datadog
254    /// Agent config schema)
255    pub minimum_sample_rate: f64,
256}
257
258impl Default for Contexts {
259    fn default() -> Self {
260        Self {
261            // Saluki-only settings retain these defaults when unset.
262            cached_contexts_limit: DEFAULT_DOGSTATSD_CACHED_CONTEXTS_LIMIT,
263            cached_tagsets_limit: DEFAULT_DOGSTATSD_CACHED_TAGSETS_LIMIT,
264            string_interner_size_bytes: None,
265            allow_context_heap_allocs: DEFAULT_DOGSTATSD_ALLOW_CONTEXT_HEAP_ALLOCS,
266            minimum_sample_rate: DEFAULT_DOGSTATSD_MINIMUM_SAMPLE_RATE,
267            // Witnessed settings are overwritten during translation.
268            string_interner_size: 0,
269        }
270    }
271}
272
273/// Metric aggregation window and flush behavior.
274#[derive(Clone, Debug, PartialEq, Serialize)]
275pub struct Aggregation {
276    /// Length, in seconds, of each aggregation window. (not in Datadog Agent config schema)
277    pub window_duration_seconds: NonZeroU64,
278
279    /// Maximum number of contexts held per aggregation window. (not in Datadog Agent config schema)
280    pub context_limit: usize,
281
282    /// How often aggregated metrics are flushed. (not in Datadog Agent config schema)
283    pub flush_interval: Duration,
284
285    /// Whether windows that are still open are flushed on shutdown.
286    ///
287    /// Set by the Datadog `dogstatsd_flush_incomplete_buckets` key.
288    pub flush_open_windows: bool,
289
290    /// How long the no-aggregation passthrough waits before flushing while idle. (not in Datadog
291    /// Agent config schema)
292    pub passthrough_idle_flush_timeout: Duration,
293
294    /// How long, in seconds, a counter value is retained after its last update before expiring.
295    ///
296    /// Set by the Datadog `dogstatsd_expiry_seconds` key. A value of `0` disables zero-value counter
297    /// emission.
298    pub counter_expiry_seconds: Option<u64>,
299
300    /// How long, in seconds, a context is retained after its last update before expiring.
301    pub context_expiry_seconds: u64,
302
303    /// Whether metrics bypass aggregation and are forwarded directly.
304    pub no_aggregation_pipeline: bool,
305
306    /// Capacity of the aggregator's tag-filter result cache.
307    pub aggregator_tag_filter_cache_capacity: usize,
308}
309
310impl Default for Aggregation {
311    fn default() -> Self {
312        Self {
313            // Saluki-schema-only knobs: the Datadog Agent schema does not publish these, so they are
314            // seeded only when set; absent that, these defaults stand.
315            window_duration_seconds: DEFAULT_AGGREGATE_WINDOW_DURATION_SECONDS,
316            context_limit: DEFAULT_AGGREGATE_CONTEXT_LIMIT,
317            flush_interval: DEFAULT_AGGREGATE_FLUSH_INTERVAL,
318            passthrough_idle_flush_timeout: DEFAULT_AGGREGATE_PASSTHROUGH_IDLE_FLUSH_TIMEOUT,
319            // Datadog-schema knobs: always written by the witness driver, so these values are
320            // placeholders that never survive translation.
321            flush_open_windows: false,
322            counter_expiry_seconds: None,
323            context_expiry_seconds: 0,
324            no_aggregation_pipeline: false,
325            aggregator_tag_filter_cache_capacity: 0,
326        }
327    }
328}
329
330/// DogStatsD metric mapper.
331#[derive(Clone, Debug, PartialEq, Serialize)]
332pub struct Mapper {
333    /// Mapper profiles that rewrite matching metric names and tags.
334    pub profiles: Vec<MapperProfile>,
335
336    /// Number of mapper match results cached.
337    pub cache_size: usize,
338
339    /// Byte capacity of the mapper's string interner. (not in Datadog Agent config schema)
340    pub string_interner_size_bytes: NonZeroUsize,
341}
342
343impl Default for Mapper {
344    fn default() -> Self {
345        Self {
346            profiles: Vec::new(),
347            // Written by the Datadog witness driver.
348            cache_size: 0,
349            string_interner_size_bytes: DEFAULT_DOGSTATSD_MAPPER_STRING_INTERNER_SIZE_BYTES,
350        }
351    }
352}
353
354/// One mapper profile: a name, a metric prefix, and the mappings under it.
355#[derive(Clone, Debug, Default, PartialEq, Serialize)]
356pub struct MapperProfile {
357    /// Profile name, for diagnostics.
358    pub name: String,
359
360    /// Metric-name prefix the profile's mappings apply to.
361    pub prefix: String,
362
363    /// The name/tag mappings under this profile.
364    pub mappings: Vec<MetricMapping>,
365}
366
367/// A single metric-name mapping within a [`MapperProfile`].
368#[derive(Clone, Debug, Default, PartialEq, Serialize)]
369pub struct MetricMapping {
370    /// Pattern a metric name must match.
371    pub metric_match: String,
372
373    /// How `metric_match` is interpreted (for example, `wildcard` or `regex`).
374    pub match_type: String,
375
376    /// Replacement name emitted for a matching metric.
377    pub name: String,
378
379    /// Tags added to a matching metric, with values captured from the match.
380    pub tags: HashMap<String, String>,
381}
382
383/// Which payload types are emitted.
384#[derive(Clone, Debug, Default, PartialEq, Serialize)]
385pub struct EnablePayloads {
386    /// Whether event payloads are emitted.
387    pub events: bool,
388
389    /// Whether series (metric) payloads are emitted.
390    pub series: bool,
391
392    /// Whether service-check payloads are emitted.
393    pub service_checks: bool,
394
395    /// Whether sketch (distribution) payloads are emitted.
396    pub sketches: bool,
397}
398
399/// One `metric_filterlist_prefix` rule.
400#[derive(Clone, Debug, Default, PartialEq, Serialize)]
401pub struct MetricPrefixRule {
402    /// Prefix of metric names to drop.
403    ///
404    /// The empty string matches every stored metric name.
405    pub prefix: String,
406
407    /// Prefix exceptions that are kept.
408    ///
409    /// Exceptions apply across all prefix rules.
410    pub except_prefix: Vec<String>,
411
412    /// Exact-name exceptions that are kept.
413    ///
414    /// Exceptions apply across all prefix rules.
415    pub except_exact: Vec<String>,
416}
417
418/// Metric-name filtering (dynamic-capable).
419#[derive(Clone, Debug, Default, PartialEq, Serialize)]
420pub struct MetricFilter {
421    /// Metric names or prefixes that are dropped.
422    ///
423    /// Defaults to an empty list, which disables metric-name filtering.
424    pub values: Vec<String>,
425
426    /// Whether entries match by prefix rather than exact name.
427    ///
428    /// Defaults to `false`, which requires exact matches.
429    pub match_prefix: bool,
430
431    /// Additional per-entry prefix rules with optional exceptions.
432    ///
433    /// Defaults to an empty list.
434    pub prefix_rules: Vec<MetricPrefixRule>,
435}
436
437/// Metric namespace prefixing.
438#[derive(Clone, Debug, Default, PartialEq, Serialize)]
439pub struct PrefixFilter {
440    /// Namespace prepended to every metric name.
441    pub metric_namespace: String,
442
443    /// Namespaces excluded from the metric-namespace prefixing.
444    pub metric_namespace_blocklist: Vec<String>,
445}
446
447/// One tag-filterlist entry (dynamic-capable).
448#[derive(Clone, Debug, Default, PartialEq, Serialize)]
449pub struct MetricTagFilterEntry {
450    /// Metric name the entry applies to.
451    pub metric_name: String,
452
453    /// Whether the listed tags are included or excluded.
454    pub action: FilterAction,
455
456    /// Tags the action applies to.
457    pub tags: Vec<String>,
458}
459
460/// One tag value allow-list entry.
461///
462/// Rules apply to counters and sketch-backed metrics after mapper rewrites and metric namespace prefixing, and only
463/// to metrics that are aggregated: metrics carrying an explicit client timestamp bypass tag filtering entirely.
464/// Distinct prefixes must not overlap. Multiple rules may use the same prefix when they target different tags.
465#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
466pub struct MetricTagValueAllowlistEntry {
467    /// Non-empty metric-name prefix the entry applies to.
468    ///
469    /// Matching is exact and case-sensitive, including any whitespace. Empty prefixes and overlapping distinct
470    /// prefixes are invalid. Multiple rules may use the same prefix when they target different tags.
471    pub metric_prefix: String,
472
473    /// Non-empty tag key whose values are constrained.
474    ///
475    /// Bare tags have no value and are not changed. Key/value tags with an empty value are processed normally. Empty
476    /// names and names containing `:` are invalid. Matching is exact and preserves whitespace.
477    pub tag_name: String,
478
479    /// Tag values retained unchanged.
480    ///
481    /// The default is an empty list, which treats every key/value tag as a mismatch. The empty string is a valid list
482    /// member and retains tags with an empty value. Matching is exact and preserves whitespace.
483    #[serde(default)]
484    pub values: Vec<String>,
485
486    /// Action applied when a tag value is absent from [`values`][Self::values].
487    ///
488    /// The default is [`Remove`][TagValueMismatchAction::Remove].
489    #[serde(default)]
490    pub on_miss: TagValueMismatchAction,
491
492    /// Replacement value used when `on_miss` is [`Replace`][TagValueMismatchAction::Replace].
493    ///
494    /// The default is `other`. This field has no effect when `on_miss` is
495    /// [`Remove`][TagValueMismatchAction::Remove]. The replacement is emitted exactly as configured, including
496    /// whitespace.
497    #[serde(default = "default_tag_value_replacement")]
498    pub replacement: String,
499}
500
501fn default_tag_value_replacement() -> String {
502    "other".to_string()
503}
504
505impl Default for MetricTagValueAllowlistEntry {
506    fn default() -> Self {
507        Self {
508            metric_prefix: String::new(),
509            tag_name: String::new(),
510            values: Vec::new(),
511            on_miss: TagValueMismatchAction::Remove,
512            replacement: default_tag_value_replacement(),
513        }
514    }
515}
516
517/// Reports why a metric tag value allow-list cannot be represented.
518#[derive(Clone, Debug, PartialEq, Eq)]
519pub struct InvalidMetricTagValueAllowlist(String);
520
521impl fmt::Display for InvalidMetricTagValueAllowlist {
522    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523        f.write_str(&self.0)
524    }
525}
526
527impl std::error::Error for InvalidMetricTagValueAllowlist {}
528
529/// Validates metric tag value allow-list entries.
530///
531/// # Errors
532///
533/// Returns an error for empty prefixes or tag names, tag names containing `:`, overlapping distinct prefixes, or a
534/// duplicate prefix and tag pair.
535pub fn validate_metric_tag_value_allowlists(
536    entries: &[MetricTagValueAllowlistEntry],
537) -> Result<(), InvalidMetricTagValueAllowlist> {
538    for (index, entry) in entries.iter().enumerate() {
539        let rule = index + 1;
540        if entry.metric_prefix.is_empty() {
541            return Err(InvalidMetricTagValueAllowlist(format!(
542                "metric tag value allow-list rule {rule} has an empty `metric_prefix`; configure a non-empty metric-name prefix"
543            )));
544        }
545        if entry.tag_name.is_empty() {
546            return Err(InvalidMetricTagValueAllowlist(format!(
547                "metric tag value allow-list rule {rule} for prefix '{}' has an empty `tag_name`; configure a non-empty tag name",
548                entry.metric_prefix
549            )));
550        }
551        if entry.tag_name.contains(':') {
552            return Err(InvalidMetricTagValueAllowlist(format!(
553                "metric tag value allow-list tag name '{}' contains ':'; configure only the tag key, without a colon or value",
554                entry.tag_name
555            )));
556        }
557    }
558
559    let mut sorted_entries = entries.iter().collect::<Vec<_>>();
560    sorted_entries.sort_unstable_by(|left, right| {
561        left.metric_prefix
562            .cmp(&right.metric_prefix)
563            .then_with(|| left.tag_name.cmp(&right.tag_name))
564    });
565
566    // After sorting by prefix and then tag, duplicate prefix/tag pairs are adjacent. Any distinct prefix that extends
567    // another prefix follows the complete group for the shorter prefix, so one adjacent pair also exposes that overlap.
568    for pair in sorted_entries.windows(2) {
569        let [left, right] = pair else {
570            unreachable!("a two-entry window must contain two entries");
571        };
572        if left.metric_prefix == right.metric_prefix {
573            if left.tag_name == right.tag_name {
574                return Err(InvalidMetricTagValueAllowlist(format!(
575                    "metric prefix '{}' is configured more than once for tag '{}'; configure each prefix and tag pair only once",
576                    left.metric_prefix, left.tag_name
577                )));
578            }
579        } else if right.metric_prefix.starts_with(&left.metric_prefix) {
580            return Err(InvalidMetricTagValueAllowlist(format!(
581                "overlapping metric prefixes '{}' and '{}' are configured; configure distinct prefixes that do not overlap",
582                left.metric_prefix, right.metric_prefix
583            )));
584        }
585    }
586
587    Ok(())
588}
589
590/// Action applied when a tag value is absent from its allow-list.
591#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
592#[serde(rename_all = "snake_case")]
593pub enum TagValueMismatchAction {
594    /// Removes the tag.
595    #[default]
596    Remove,
597    /// Replaces the tag value with the configured sentinel.
598    Replace,
599}
600
601/// Whether a tag-filterlist entry includes or excludes the listed tags.
602#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
603pub enum FilterAction {
604    Include,
605    #[default]
606    Exclude,
607}
608
609/// DogStatsD debug-log and verbose-log settings.
610#[derive(Clone, Debug, Default, PartialEq, Serialize)]
611pub struct DebugLog {
612    /// Whether the metric debug-log destination is added at startup.
613    ///
614    /// Defaults to `true`. Set this to `false` to remove the destination from the topology.
615    pub logging_enabled: bool,
616
617    /// Path of the DogStatsD metric debug log.
618    ///
619    /// Defaults to `None`, which selects the platform-specific Agent log path at startup.
620    /// Set a path to write the diagnostic log elsewhere.
621    pub log_file: Option<PathBuf>,
622
623    /// Number of rotated debug log files retained.
624    ///
625    /// Defaults to `3`. The file writer retains one rotated file when this is `0`.
626    pub log_file_max_rolls: usize,
627
628    /// Maximum size, in bytes, of the active debug log file before rotation.
629    ///
630    /// Defaults to `10,485,760` bytes (`10Mb`). A value of `0` is accepted as the rotation threshold.
631    /// Set this based on the diagnostic data and disk space to retain.
632    pub log_file_max_size: u64,
633
634    /// Whether per-metric processing statistics are written to the debug log.
635    ///
636    /// Defaults to `false` and can change at runtime. Enable this when collecting metric-level diagnostics.
637    pub metrics_stats_enable: bool,
638
639    /// Whether per-packet parse errors are logged at debug rather than error level.
640    ///
641    /// Defaults to `false`. Enable this to reduce error-level log volume from malformed packets.
642    pub disable_verbose_logs: bool,
643}