fraiseql_server/auth/
monitoring.rs1use std::time::Instant;
3
4use serde::Serialize;
5use tracing::{Level, info, span, warn};
6
7#[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#[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
148pub 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 for _ in 0..10 {
218 metrics.record_attempt();
219 metrics.record_success();
220 }
221
222 assert_eq!(metrics.success_rate(), 100.0);
223
224 metrics.record_attempt();
226 metrics.record_failure();
227
228 assert!((metrics.success_rate() - 90.91).abs() < 0.1); }
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); }
240}