1mod collector;
62mod config;
63mod error;
64mod insights;
65mod metrics;
66mod middleware;
67
68pub use collector::*;
69pub use config::*;
70pub use error::*;
71pub use insights::*;
72pub use metrics::*;
73pub use middleware::*;
74
75use chrono::{DateTime, Utc};
76use serde::{Deserialize, Serialize};
77use std::collections::HashMap;
78use std::sync::Arc;
79use std::time::Duration;
80
81#[derive(Clone)]
85pub struct Analytics {
86 inner: Arc<AnalyticsInner>,
87}
88
89struct AnalyticsInner {
90 config: AnalyticsConfig,
91 collector: MetricsCollector,
92 started_at: DateTime<Utc>,
93}
94
95impl Analytics {
96 pub fn new(config: AnalyticsConfig) -> Self {
98 Self {
99 inner: Arc::new(AnalyticsInner {
100 config,
101 collector: MetricsCollector::new(),
102 started_at: Utc::now(),
103 }),
104 }
105 }
106
107 pub fn record_request(&self, record: RequestRecord) {
109 self.inner.collector.record_request(record);
110 }
111
112 pub fn record_rate_limit(&self, event: RateLimitEvent) {
114 self.inner.collector.record_rate_limit(event);
115 }
116
117 pub fn record_error(&self, error: ErrorRecord) {
119 self.inner.collector.record_error(error);
120 }
121
122 pub fn snapshot(&self) -> AnalyticsSnapshot {
124 let collector = &self.inner.collector;
125
126 AnalyticsSnapshot {
127 timestamp: Utc::now(),
128 uptime_seconds: (Utc::now() - self.inner.started_at).num_seconds() as u64,
129 requests: collector.request_metrics(),
130 latency: collector.latency_metrics(),
131 errors: collector.error_metrics(),
132 rate_limits: collector.rate_limit_metrics(),
133 endpoints: collector.endpoint_metrics(),
134 throughput: collector.throughput_metrics(),
135 }
136 }
137
138 pub fn dashboard_json(&self) -> String {
140 serde_json::to_string_pretty(&self.snapshot()).unwrap_or_else(|_| "{}".to_string())
141 }
142
143 pub fn reset(&self) {
145 self.inner.collector.reset();
146 }
147
148 pub fn config(&self) -> &AnalyticsConfig {
150 &self.inner.config
151 }
152}
153
154#[derive(Debug, Clone)]
160pub struct RequestRecord {
161 pub method: String,
163 pub path: String,
165 pub status: u16,
167 pub duration: Duration,
169 pub timestamp: DateTime<Utc>,
171 pub client_id: Option<String>,
173 pub response_size: Option<u64>,
175 pub authenticated: bool,
177 pub tags: HashMap<String, String>,
179}
180
181impl RequestRecord {
182 pub fn new(method: impl Into<String>, path: impl Into<String>, status: u16, duration: Duration) -> Self {
184 Self {
185 method: method.into(),
186 path: path.into(),
187 status,
188 duration,
189 timestamp: Utc::now(),
190 client_id: None,
191 response_size: None,
192 authenticated: false,
193 tags: HashMap::new(),
194 }
195 }
196
197 pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
198 self.client_id = Some(client_id.into());
199 self
200 }
201
202 pub fn with_response_size(mut self, size: u64) -> Self {
203 self.response_size = Some(size);
204 self
205 }
206
207 pub fn with_authenticated(mut self, authenticated: bool) -> Self {
208 self.authenticated = authenticated;
209 self
210 }
211
212 pub fn with_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
213 self.tags.insert(key.into(), value.into());
214 self
215 }
216
217 pub fn is_success(&self) -> bool {
219 self.status >= 200 && self.status < 300
220 }
221
222 pub fn is_client_error(&self) -> bool {
224 self.status >= 400 && self.status < 500
225 }
226
227 pub fn is_server_error(&self) -> bool {
229 self.status >= 500
230 }
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
239pub enum RateLimitEventType {
240 Allowed,
242 Limited,
244 Warning,
246}
247
248#[derive(Debug, Clone)]
250pub struct RateLimitEvent {
251 pub client_id: String,
253 pub event_type: RateLimitEventType,
255 pub current_count: u64,
257 pub limit: u64,
259 pub window_seconds: u64,
261 pub endpoint: Option<String>,
263 pub timestamp: DateTime<Utc>,
265}
266
267impl RateLimitEvent {
268 pub fn allowed(client_id: impl Into<String>, current: u64, limit: u64, window: u64) -> Self {
269 Self {
270 client_id: client_id.into(),
271 event_type: RateLimitEventType::Allowed,
272 current_count: current,
273 limit,
274 window_seconds: window,
275 endpoint: None,
276 timestamp: Utc::now(),
277 }
278 }
279
280 pub fn limited(client_id: impl Into<String>, current: u64, limit: u64, window: u64) -> Self {
281 Self {
282 client_id: client_id.into(),
283 event_type: RateLimitEventType::Limited,
284 current_count: current,
285 limit,
286 window_seconds: window,
287 endpoint: None,
288 timestamp: Utc::now(),
289 }
290 }
291
292 pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
293 self.endpoint = Some(endpoint.into());
294 self
295 }
296
297 pub fn utilization(&self) -> f64 {
299 if self.limit == 0 {
300 0.0
301 } else {
302 (self.current_count as f64 / self.limit as f64) * 100.0
303 }
304 }
305}
306
307#[derive(Debug, Clone)]
313pub struct ErrorRecord {
314 pub error_type: String,
316 pub message: String,
318 pub status: Option<u16>,
320 pub endpoint: Option<String>,
322 pub stack_trace: Option<String>,
324 pub timestamp: DateTime<Utc>,
326 pub context: HashMap<String, String>,
328}
329
330impl ErrorRecord {
331 pub fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
332 Self {
333 error_type: error_type.into(),
334 message: message.into(),
335 status: None,
336 endpoint: None,
337 stack_trace: None,
338 timestamp: Utc::now(),
339 context: HashMap::new(),
340 }
341 }
342
343 pub fn with_status(mut self, status: u16) -> Self {
344 self.status = Some(status);
345 self
346 }
347
348 pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
349 self.endpoint = Some(endpoint.into());
350 self
351 }
352
353 pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
354 self.context.insert(key.into(), value.into());
355 self
356 }
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct AnalyticsSnapshot {
366 pub timestamp: DateTime<Utc>,
368 pub uptime_seconds: u64,
370 pub requests: RequestMetrics,
372 pub latency: LatencyMetrics,
374 pub errors: ErrorMetrics,
376 pub rate_limits: RateLimitMetrics,
378 pub endpoints: Vec<EndpointMetrics>,
380 pub throughput: ThroughputMetrics,
382}
383
384#[derive(Debug, Clone, Default, Serialize, Deserialize)]
386pub struct RequestMetrics {
387 pub total: u64,
389 pub success: u64,
391 pub client_errors: u64,
393 pub server_errors: u64,
395 pub by_method: HashMap<String, u64>,
397 pub by_status: HashMap<u16, u64>,
399}
400
401impl RequestMetrics {
402 pub fn success_rate(&self) -> f64 {
404 if self.total == 0 {
405 100.0
406 } else {
407 (self.success as f64 / self.total as f64) * 100.0
408 }
409 }
410
411 pub fn error_rate(&self) -> f64 {
413 if self.total == 0 {
414 0.0
415 } else {
416 ((self.client_errors + self.server_errors) as f64 / self.total as f64) * 100.0
417 }
418 }
419}
420
421#[derive(Debug, Clone, Default, Serialize, Deserialize)]
423pub struct LatencyMetrics {
424 pub avg_ms: f64,
426 pub min_ms: f64,
428 pub max_ms: f64,
430 pub p50_ms: f64,
432 pub p90_ms: f64,
434 pub p95_ms: f64,
436 pub p99_ms: f64,
438 pub samples: u64,
440}
441
442#[derive(Debug, Clone, Default, Serialize, Deserialize)]
444pub struct ErrorMetrics {
445 pub total: u64,
447 pub by_type: HashMap<String, u64>,
449 pub by_status: HashMap<u16, u64>,
451 pub recent: Vec<ErrorSummary>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
457pub struct ErrorSummary {
458 pub error_type: String,
459 pub message: String,
460 pub count: u64,
461 pub last_seen: DateTime<Utc>,
462}
463
464#[derive(Debug, Clone, Default, Serialize, Deserialize)]
466pub struct RateLimitMetrics {
467 pub total_checks: u64,
469 pub allowed: u64,
471 pub limited: u64,
473 pub unique_clients_limited: u64,
475 pub avg_utilization: f64,
477 pub top_limited_clients: Vec<ClientRateLimitInfo>,
479}
480
481#[derive(Debug, Clone, Serialize, Deserialize)]
483pub struct ClientRateLimitInfo {
484 pub client_id: String,
485 pub times_limited: u64,
486 pub last_limited: DateTime<Utc>,
487}
488
489#[derive(Debug, Clone, Serialize, Deserialize)]
491pub struct EndpointMetrics {
492 pub path: String,
494 pub method: String,
496 pub requests: u64,
498 pub errors: u64,
500 pub avg_latency_ms: f64,
502 pub p99_latency_ms: f64,
504 pub error_rate: f64,
506}
507
508#[derive(Debug, Clone, Default, Serialize, Deserialize)]
510pub struct ThroughputMetrics {
511 pub requests_per_second: f64,
513 pub requests_last_minute: u64,
515 pub requests_last_hour: u64,
517 pub peak_rps: f64,
519 pub avg_response_size: u64,
521 pub total_bytes_transferred: u64,
523}
524
525#[cfg(test)]
530mod tests {
531 use super::*;
532
533 #[test]
534 fn test_request_record() {
535 let record = RequestRecord::new("GET", "/api/users", 200, Duration::from_millis(50))
536 .with_client_id("user-123")
537 .with_response_size(1024)
538 .with_authenticated(true)
539 .with_tag("version", "v1");
540
541 assert!(record.is_success());
542 assert!(!record.is_client_error());
543 assert!(!record.is_server_error());
544 assert_eq!(record.client_id, Some("user-123".to_string()));
545 }
546
547 #[test]
548 fn test_rate_limit_event() {
549 let event = RateLimitEvent::limited("client-1", 100, 100, 60);
550 assert_eq!(event.utilization(), 100.0);
551
552 let event = RateLimitEvent::allowed("client-2", 50, 100, 60);
553 assert_eq!(event.utilization(), 50.0);
554 }
555
556 #[test]
557 fn test_request_metrics() {
558 let metrics = RequestMetrics {
559 total: 100,
560 success: 90,
561 client_errors: 8,
562 server_errors: 2,
563 ..Default::default()
564 };
565
566 assert_eq!(metrics.success_rate(), 90.0);
567 assert_eq!(metrics.error_rate(), 10.0);
568 }
569
570 #[test]
571 fn test_analytics_creation() {
572 let analytics = Analytics::new(AnalyticsConfig::default());
573 let snapshot = analytics.snapshot();
574
575 assert_eq!(snapshot.requests.total, 0);
576 assert_eq!(snapshot.errors.total, 0);
577 }
578}
579