heliosdb_proxy/analytics/
config.rs1use std::time::Duration;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone)]
10pub struct AnalyticsConfig {
11 pub enabled: bool,
13
14 pub normalize_queries: bool,
16
17 pub track_parameters: bool,
19
20 pub retention: Duration,
22
23 pub max_fingerprints: usize,
25
26 pub slow_query: SlowQueryConfig,
28
29 pub patterns: PatternConfig,
31
32 pub sampling: SamplingConfig,
34
35 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), 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#[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#[derive(Debug, Clone)]
124pub struct SlowQueryConfig {
125 pub enabled: bool,
127
128 pub threshold: Duration,
130
131 pub log_file: Option<PathBuf>,
133
134 pub log_parameters: bool,
136
137 pub max_query_length: usize,
139
140 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#[derive(Debug, Clone)]
181pub struct PatternConfig {
182 pub n_plus_one_detection: bool,
184
185 pub n_plus_one_threshold: usize,
187
188 pub burst_detection: bool,
190
191 pub burst_threshold: usize,
193
194 pub burst_window: Duration,
196
197 pub session_history_size: usize,
199
200 pub session_timeout: Duration,
202
203 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#[derive(Debug, Clone)]
242pub struct SamplingConfig {
243 pub enabled: bool,
245
246 pub rate: f64,
248
249 pub always_sample_slow: bool,
251
252 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#[derive(Debug, Clone)]
277pub struct AlertConfig {
278 pub slow_query_threshold: Duration,
280
281 pub error_rate_threshold: f64,
283
284 pub alert_on_n_plus_one: bool,
286
287 pub alert_on_burst: bool,
289
290 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}