Skip to main content

fraiseql_server/auth/
monitoring.rs

1// Authentication monitoring and observability
2use std::time::Instant;
3
4use serde::Serialize;
5use tracing::{Level, info, span, warn};
6
7/// Structured log for authentication events
8#[derive(Debug, Serialize)]
9pub struct AuthEvent {
10    pub event:       String,
11    pub user_id:     Option<String>,
12    pub provider:    Option<String>,
13    pub status:      String,
14    pub duration_ms: f64,
15    pub error:       Option<String>,
16    pub timestamp:   String,
17    pub request_id:  Option<String>,
18}
19
20impl AuthEvent {
21    pub fn new(event: &str) -> Self {
22        Self {
23            event:       event.to_string(),
24            user_id:     None,
25            provider:    None,
26            status:      "started".to_string(),
27            duration_ms: 0.0,
28            error:       None,
29            timestamp:   chrono::Utc::now().to_rfc3339(),
30            request_id:  None,
31        }
32    }
33
34    pub fn with_user_id(mut self, user_id: String) -> Self {
35        self.user_id = Some(user_id);
36        self
37    }
38
39    pub fn with_provider(mut self, provider: String) -> Self {
40        self.provider = Some(provider);
41        self
42    }
43
44    pub fn with_request_id(mut self, request_id: String) -> Self {
45        self.request_id = Some(request_id);
46        self
47    }
48
49    pub fn success(mut self, duration_ms: f64) -> Self {
50        self.status = "success".to_string();
51        self.duration_ms = duration_ms;
52        self
53    }
54
55    pub fn error(mut self, error: String, duration_ms: f64) -> Self {
56        self.status = "error".to_string();
57        self.error = Some(error);
58        self.duration_ms = duration_ms;
59        self
60    }
61
62    pub fn log(&self) {
63        match self.status.as_str() {
64            "success" => {
65                info!(
66                    event = %self.event,
67                    user_id = ?self.user_id,
68                    provider = ?self.provider,
69                    duration_ms = self.duration_ms,
70                    "Authentication event",
71                );
72            },
73            "error" => {
74                warn!(
75                    event = %self.event,
76                    error = ?self.error,
77                    duration_ms = self.duration_ms,
78                    "Authentication error",
79                );
80            },
81            _ => {},
82        }
83    }
84}
85
86/// Metrics for authentication operations
87#[derive(Debug, Clone)]
88pub struct AuthMetrics {
89    pub total_auth_attempts:        u64,
90    pub successful_authentications: u64,
91    pub failed_authentications:     u64,
92    pub tokens_issued:              u64,
93    pub tokens_refreshed:           u64,
94    pub sessions_revoked:           u64,
95}
96
97impl AuthMetrics {
98    pub fn new() -> Self {
99        Self {
100            total_auth_attempts:        0,
101            successful_authentications: 0,
102            failed_authentications:     0,
103            tokens_issued:              0,
104            tokens_refreshed:           0,
105            sessions_revoked:           0,
106        }
107    }
108
109    pub fn record_attempt(&mut self) {
110        self.total_auth_attempts += 1;
111    }
112
113    pub fn record_success(&mut self) {
114        self.successful_authentications += 1;
115    }
116
117    pub fn record_failure(&mut self) {
118        self.failed_authentications += 1;
119    }
120
121    pub fn record_token_issued(&mut self) {
122        self.tokens_issued += 1;
123    }
124
125    pub fn record_token_refreshed(&mut self) {
126        self.tokens_refreshed += 1;
127    }
128
129    pub fn record_session_revoked(&mut self) {
130        self.sessions_revoked += 1;
131    }
132
133    pub fn success_rate(&self) -> f64 {
134        if self.total_auth_attempts == 0 {
135            0.0
136        } else {
137            (self.successful_authentications as f64) / (self.total_auth_attempts as f64) * 100.0
138        }
139    }
140}
141
142impl Default for AuthMetrics {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148/// Timer for measuring operation duration
149pub struct OperationTimer {
150    start:     Instant,
151    operation: String,
152}
153
154impl OperationTimer {
155    pub fn start(operation: &str) -> Self {
156        let span = span!(Level::DEBUG, "operation", %operation);
157        let _guard = span.enter();
158
159        Self {
160            start:     Instant::now(),
161            operation: operation.to_string(),
162        }
163    }
164
165    pub fn elapsed_ms(&self) -> f64 {
166        self.start.elapsed().as_secs_f64() * 1000.0
167    }
168
169    pub fn finish(self) {
170        let elapsed = self.elapsed_ms();
171        info!(
172            operation = %self.operation,
173            duration_ms = elapsed,
174            "Operation completed",
175        );
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn test_auth_event_builder() {
185        let event = AuthEvent::new("login")
186            .with_user_id("user123".to_string())
187            .with_provider("google".to_string())
188            .success(50.0);
189
190        assert_eq!(event.event, "login");
191        assert_eq!(event.user_id, Some("user123".to_string()));
192        assert_eq!(event.provider, Some("google".to_string()));
193        assert_eq!(event.status, "success");
194        assert_eq!(event.duration_ms, 50.0);
195    }
196
197    #[test]
198    fn test_auth_metrics() {
199        let mut metrics = AuthMetrics::new();
200
201        metrics.record_attempt();
202        metrics.record_attempt();
203        metrics.record_success();
204        metrics.record_failure();
205
206        assert_eq!(metrics.total_auth_attempts, 2);
207        assert_eq!(metrics.successful_authentications, 1);
208        assert_eq!(metrics.failed_authentications, 1);
209        assert_eq!(metrics.success_rate(), 50.0);
210    }
211
212    #[test]
213    fn test_auth_metrics_success_rate() {
214        let mut metrics = AuthMetrics::new();
215
216        // 100% success rate
217        for _ in 0..10 {
218            metrics.record_attempt();
219            metrics.record_success();
220        }
221
222        assert_eq!(metrics.success_rate(), 100.0);
223
224        // Drop to 50%
225        metrics.record_attempt();
226        metrics.record_failure();
227
228        assert!((metrics.success_rate() - 90.91).abs() < 0.1); // ~90.91%
229    }
230
231    #[test]
232    fn test_operation_timer() {
233        let timer = OperationTimer::start("test_op");
234        std::thread::sleep(std::time::Duration::from_millis(10));
235        let elapsed = timer.elapsed_ms();
236
237        assert!(elapsed >= 10.0);
238        assert!(elapsed < 100.0); // Should be quick
239    }
240}