Skip to main content

heliosdb_proxy/analytics/
config.rs

1//! Analytics Configuration
2//!
3//! Configuration for query analytics, slow query log, and pattern detection.
4
5use std::time::Duration;
6use std::path::PathBuf;
7
8/// Analytics configuration
9#[derive(Debug, Clone)]
10pub struct AnalyticsConfig {
11    /// Enable analytics
12    pub enabled: bool,
13
14    /// Normalize queries (replace literals with placeholders)
15    pub normalize_queries: bool,
16
17    /// Track parameter values (privacy consideration)
18    pub track_parameters: bool,
19
20    /// Statistics retention duration
21    pub retention: Duration,
22
23    /// Maximum fingerprints to track
24    pub max_fingerprints: usize,
25
26    /// Slow query configuration
27    pub slow_query: SlowQueryConfig,
28
29    /// Pattern detection configuration
30    pub patterns: PatternConfig,
31
32    /// Sampling configuration
33    pub sampling: SamplingConfig,
34
35    /// Alert configuration
36    pub alerts: AlertConfig,
37}
38
39impl Default for AnalyticsConfig {
40    fn default() -> Self {
41        Self {
42            enabled: true,
43            normalize_queries: true,
44            track_parameters: false,
45            retention: Duration::from_secs(7 * 24 * 3600), // 7 days
46            max_fingerprints: 10000,
47            slow_query: SlowQueryConfig::default(),
48            patterns: PatternConfig::default(),
49            sampling: SamplingConfig::default(),
50            alerts: AlertConfig::default(),
51        }
52    }
53}
54
55/// Builder for AnalyticsConfig
56#[derive(Debug, Default)]
57pub struct AnalyticsConfigBuilder {
58    config: AnalyticsConfig,
59}
60
61impl AnalyticsConfigBuilder {
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    pub fn enabled(mut self, enabled: bool) -> Self {
67        self.config.enabled = enabled;
68        self
69    }
70
71    pub fn normalize_queries(mut self, normalize: bool) -> Self {
72        self.config.normalize_queries = normalize;
73        self
74    }
75
76    pub fn track_parameters(mut self, track: bool) -> Self {
77        self.config.track_parameters = track;
78        self
79    }
80
81    pub fn retention_days(mut self, days: u64) -> Self {
82        self.config.retention = Duration::from_secs(days * 24 * 3600);
83        self
84    }
85
86    pub fn max_fingerprints(mut self, max: usize) -> Self {
87        self.config.max_fingerprints = max;
88        self
89    }
90
91    pub fn slow_query(mut self, config: SlowQueryConfig) -> Self {
92        self.config.slow_query = config;
93        self
94    }
95
96    pub fn patterns(mut self, config: PatternConfig) -> Self {
97        self.config.patterns = config;
98        self
99    }
100
101    pub fn sampling(mut self, config: SamplingConfig) -> Self {
102        self.config.sampling = config;
103        self
104    }
105
106    pub fn alerts(mut self, config: AlertConfig) -> Self {
107        self.config.alerts = config;
108        self
109    }
110
111    pub fn build(self) -> AnalyticsConfig {
112        self.config
113    }
114}
115
116impl AnalyticsConfig {
117    pub fn builder() -> AnalyticsConfigBuilder {
118        AnalyticsConfigBuilder::new()
119    }
120}
121
122/// Slow query log configuration
123#[derive(Debug, Clone)]
124pub struct SlowQueryConfig {
125    /// Enable slow query logging
126    pub enabled: bool,
127
128    /// Threshold duration for slow queries
129    pub threshold: Duration,
130
131    /// Log file path (None for in-memory only)
132    pub log_file: Option<PathBuf>,
133
134    /// Log parameter values
135    pub log_parameters: bool,
136
137    /// Maximum query length to log
138    pub max_query_length: usize,
139
140    /// Maximum recent entries to keep in memory
141    pub max_recent_entries: usize,
142}
143
144impl Default for SlowQueryConfig {
145    fn default() -> Self {
146        Self {
147            enabled: true,
148            threshold: Duration::from_secs(1),
149            log_file: None,
150            log_parameters: false,
151            max_query_length: 4096,
152            max_recent_entries: 1000,
153        }
154    }
155}
156
157impl SlowQueryConfig {
158    pub fn with_threshold_ms(mut self, ms: u64) -> Self {
159        self.threshold = Duration::from_millis(ms);
160        self
161    }
162
163    pub fn with_threshold_secs(mut self, secs: u64) -> Self {
164        self.threshold = Duration::from_secs(secs);
165        self
166    }
167
168    pub fn with_log_file(mut self, path: impl Into<PathBuf>) -> Self {
169        self.log_file = Some(path.into());
170        self
171    }
172
173    pub fn with_max_recent(mut self, max: usize) -> Self {
174        self.max_recent_entries = max;
175        self
176    }
177}
178
179/// Pattern detection configuration
180#[derive(Debug, Clone)]
181pub struct PatternConfig {
182    /// Enable N+1 query detection
183    pub n_plus_one_detection: bool,
184
185    /// Threshold for N+1 detection (min repeated queries)
186    pub n_plus_one_threshold: usize,
187
188    /// Enable burst detection
189    pub burst_detection: bool,
190
191    /// Threshold for burst detection (queries per window)
192    pub burst_threshold: usize,
193
194    /// Burst detection window
195    pub burst_window: Duration,
196
197    /// Session query history size
198    pub session_history_size: usize,
199
200    /// Session timeout (cleanup inactive sessions)
201    pub session_timeout: Duration,
202
203    /// Maximum sessions to track
204    pub max_sessions: usize,
205}
206
207impl Default for PatternConfig {
208    fn default() -> Self {
209        Self {
210            n_plus_one_detection: true,
211            n_plus_one_threshold: 5,
212            burst_detection: true,
213            burst_threshold: 50,
214            burst_window: Duration::from_millis(100),
215            session_history_size: 100,
216            session_timeout: Duration::from_secs(300),
217            max_sessions: 10000,
218        }
219    }
220}
221
222impl PatternConfig {
223    pub fn with_n_plus_one_threshold(mut self, threshold: usize) -> Self {
224        self.n_plus_one_threshold = threshold;
225        self
226    }
227
228    pub fn with_burst_threshold(mut self, threshold: usize) -> Self {
229        self.burst_threshold = threshold;
230        self
231    }
232
233    pub fn disable_patterns(mut self) -> Self {
234        self.n_plus_one_detection = false;
235        self.burst_detection = false;
236        self
237    }
238}
239
240/// Sampling configuration
241#[derive(Debug, Clone)]
242pub struct SamplingConfig {
243    /// Enable sampling
244    pub enabled: bool,
245
246    /// Sample rate (0.0 - 1.0)
247    pub rate: f64,
248
249    /// Always sample slow queries
250    pub always_sample_slow: bool,
251
252    /// Always sample errors
253    pub always_sample_errors: bool,
254}
255
256impl Default for SamplingConfig {
257    fn default() -> Self {
258        Self {
259            enabled: false,
260            rate: 1.0,
261            always_sample_slow: true,
262            always_sample_errors: true,
263        }
264    }
265}
266
267impl SamplingConfig {
268    pub fn with_rate(mut self, rate: f64) -> Self {
269        self.rate = rate.clamp(0.0, 1.0);
270        self.enabled = rate < 1.0;
271        self
272    }
273}
274
275/// Alert configuration
276#[derive(Debug, Clone)]
277pub struct AlertConfig {
278    /// Slow query alert threshold
279    pub slow_query_threshold: Duration,
280
281    /// Error rate threshold (0.0 - 1.0)
282    pub error_rate_threshold: f64,
283
284    /// N+1 detection alert
285    pub alert_on_n_plus_one: bool,
286
287    /// Burst detection alert
288    pub alert_on_burst: bool,
289
290    /// Webhook URL for alerts
291    pub webhook_url: Option<String>,
292}
293
294impl Default for AlertConfig {
295    fn default() -> Self {
296        Self {
297            slow_query_threshold: Duration::from_secs(5),
298            error_rate_threshold: 0.05,
299            alert_on_n_plus_one: true,
300            alert_on_burst: true,
301            webhook_url: None,
302        }
303    }
304}
305
306impl AlertConfig {
307    pub fn with_webhook(mut self, url: impl Into<String>) -> Self {
308        self.webhook_url = Some(url.into());
309        self
310    }
311
312    pub fn with_slow_threshold_secs(mut self, secs: u64) -> Self {
313        self.slow_query_threshold = Duration::from_secs(secs);
314        self
315    }
316
317    pub fn with_error_rate(mut self, rate: f64) -> Self {
318        self.error_rate_threshold = rate.clamp(0.0, 1.0);
319        self
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn test_default_config() {
329        let config = AnalyticsConfig::default();
330        assert!(config.enabled);
331        assert!(config.normalize_queries);
332        assert!(!config.track_parameters);
333        assert_eq!(config.max_fingerprints, 10000);
334    }
335
336    #[test]
337    fn test_builder() {
338        let config = AnalyticsConfig::builder()
339            .enabled(true)
340            .max_fingerprints(5000)
341            .retention_days(14)
342            .build();
343
344        assert!(config.enabled);
345        assert_eq!(config.max_fingerprints, 5000);
346        assert_eq!(config.retention, Duration::from_secs(14 * 24 * 3600));
347    }
348
349    #[test]
350    fn test_slow_query_config() {
351        let config = SlowQueryConfig::default()
352            .with_threshold_ms(500)
353            .with_max_recent(2000);
354
355        assert_eq!(config.threshold, Duration::from_millis(500));
356        assert_eq!(config.max_recent_entries, 2000);
357    }
358
359    #[test]
360    fn test_pattern_config() {
361        let config = PatternConfig::default()
362            .with_n_plus_one_threshold(10)
363            .with_burst_threshold(100);
364
365        assert_eq!(config.n_plus_one_threshold, 10);
366        assert_eq!(config.burst_threshold, 100);
367    }
368
369    #[test]
370    fn test_sampling_config() {
371        let config = SamplingConfig::default().with_rate(0.1);
372        assert!(config.enabled);
373        assert!((config.rate - 0.1).abs() < 0.001);
374    }
375}