oxur_cli/config/
metrics.rs

1//! Metrics configuration for REPL observability
2//!
3//! Controls whether metrics are collected and where they are exported.
4
5use serde::{Deserialize, Serialize};
6
7/// Default TCP exporter address
8pub const DEFAULT_METRICS_ADDR: &str = "127.0.0.1:9100";
9
10/// Metrics configuration
11///
12/// Controls metrics collection and export settings for the REPL.
13///
14/// # Example TOML
15///
16/// ```toml
17/// [metrics]
18/// enabled = true
19/// exporter_address = "127.0.0.1:9100"
20/// ```
21///
22/// # Environment Variables
23///
24/// - `OXUR_METRICS_ENABLED` - Enable metrics (default: false)
25/// - `OXUR_METRICS_ADDRESS` - TCP exporter address
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(default)]
28pub struct MetricsConfig {
29    /// Whether metrics collection is enabled
30    ///
31    /// When false, metrics calls are no-ops (zero overhead).
32    /// Default: false
33    pub enabled: bool,
34
35    /// TCP exporter address for metrics streaming
36    ///
37    /// Format: "host:port"
38    /// Default: "127.0.0.1:9100"
39    pub exporter_address: String,
40
41    /// Enable client-side metrics (request counts, latency)
42    ///
43    /// Only applies when `enabled` is true.
44    /// Default: true
45    pub client_metrics: bool,
46
47    /// Enable server-side metrics (connections, sessions)
48    ///
49    /// Only applies when `enabled` is true.
50    /// Default: true
51    pub server_metrics: bool,
52}
53
54impl Default for MetricsConfig {
55    fn default() -> Self {
56        Self {
57            enabled: false,
58            exporter_address: DEFAULT_METRICS_ADDR.to_string(),
59            client_metrics: true,
60            server_metrics: true,
61        }
62    }
63}
64
65impl MetricsConfig {
66    /// Create a new builder
67    pub fn builder() -> MetricsConfigBuilder {
68        MetricsConfigBuilder::new()
69    }
70
71    /// Merge another config into this one
72    ///
73    /// Only merges explicitly set values from `other`.
74    pub fn merge(&mut self, other: MetricsConfig) {
75        // For metrics, we take the other's values if they differ from defaults
76        if other.enabled {
77            self.enabled = other.enabled;
78        }
79        if other.exporter_address != DEFAULT_METRICS_ADDR {
80            self.exporter_address = other.exporter_address;
81        }
82        // Always take explicit settings for granular control
83        self.client_metrics = other.client_metrics;
84        self.server_metrics = other.server_metrics;
85    }
86}
87
88/// Builder for MetricsConfig
89#[derive(Debug, Clone, Default)]
90pub struct MetricsConfigBuilder {
91    config: MetricsConfig,
92}
93
94impl MetricsConfigBuilder {
95    /// Create a new builder with defaults
96    pub fn new() -> Self {
97        Self { config: MetricsConfig::default() }
98    }
99
100    /// Enable or disable metrics
101    pub fn enabled(mut self, enabled: bool) -> Self {
102        self.config.enabled = enabled;
103        self
104    }
105
106    /// Set the TCP exporter address
107    pub fn exporter_address(mut self, addr: impl Into<String>) -> Self {
108        self.config.exporter_address = addr.into();
109        self
110    }
111
112    /// Enable or disable client-side metrics
113    pub fn client_metrics(mut self, enabled: bool) -> Self {
114        self.config.client_metrics = enabled;
115        self
116    }
117
118    /// Enable or disable server-side metrics
119    pub fn server_metrics(mut self, enabled: bool) -> Self {
120        self.config.server_metrics = enabled;
121        self
122    }
123
124    /// Build the MetricsConfig
125    pub fn build(self) -> MetricsConfig {
126        self.config
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_metrics_config_default() {
136        let config = MetricsConfig::default();
137        assert!(!config.enabled);
138        assert_eq!(config.exporter_address, "127.0.0.1:9100");
139        assert!(config.client_metrics);
140        assert!(config.server_metrics);
141    }
142
143    #[test]
144    fn test_metrics_config_builder() {
145        let config = MetricsConfig::builder()
146            .enabled(true)
147            .exporter_address("0.0.0.0:9200")
148            .client_metrics(true)
149            .server_metrics(false)
150            .build();
151
152        assert!(config.enabled);
153        assert_eq!(config.exporter_address, "0.0.0.0:9200");
154        assert!(config.client_metrics);
155        assert!(!config.server_metrics);
156    }
157
158    #[test]
159    fn test_serde_roundtrip() {
160        let config =
161            MetricsConfig::builder().enabled(true).exporter_address("localhost:9100").build();
162
163        let toml = toml::to_string(&config).unwrap();
164        let parsed: MetricsConfig = toml::from_str(&toml).unwrap();
165
166        assert_eq!(config.enabled, parsed.enabled);
167        assert_eq!(config.exporter_address, parsed.exporter_address);
168    }
169}