rust_web_server/request_log/
mod.rs1#[cfg(test)]
20mod tests;
21
22use std::collections::VecDeque;
23use std::sync::{Mutex, OnceLock};
24use std::time::{SystemTime, UNIX_EPOCH};
25
26use crate::application::Application;
27use crate::middleware::Middleware;
28use crate::request::Request;
29use crate::response::Response;
30use crate::server::ConnectionInfo;
31
32#[derive(Clone)]
34pub struct LogEntry {
35 pub timestamp: u64,
37 pub method: String,
38 pub path: String,
39 pub status: i16,
40 pub client_ip: String,
41 pub latency_ms: u64,
42}
43
44pub struct RequestLog {
46 entries: Mutex<VecDeque<LogEntry>>,
47 capacity: usize,
48}
49
50impl RequestLog {
51 fn new(capacity: usize) -> Self {
52 RequestLog {
53 entries: Mutex::new(VecDeque::with_capacity(capacity)),
54 capacity,
55 }
56 }
57
58 fn push(&self, entry: LogEntry) {
59 let mut guard = self.entries.lock().unwrap();
60 if guard.len() >= self.capacity {
61 guard.pop_front();
62 }
63 guard.push_back(entry);
64 }
65
66 pub fn recent(&self, n: usize) -> Vec<LogEntry> {
68 let guard = self.entries.lock().unwrap();
69 let skip = guard.len().saturating_sub(n);
70 guard.iter().skip(skip).cloned().collect()
71 }
72
73 pub fn recent_errors(&self, n: usize) -> Vec<LogEntry> {
75 let guard = self.entries.lock().unwrap();
76 let errors: Vec<LogEntry> = guard.iter()
77 .filter(|e| e.status >= 400)
78 .cloned()
79 .collect();
80 let skip = errors.len().saturating_sub(n);
81 errors.into_iter().skip(skip).collect()
82 }
83
84 pub fn len(&self) -> usize {
86 self.entries.lock().unwrap().len()
87 }
88
89 pub fn is_empty(&self) -> bool {
90 self.len() == 0
91 }
92}
93
94static INSTANCE: OnceLock<RequestLog> = OnceLock::new();
95
96pub fn global() -> &'static RequestLog {
98 INSTANCE.get_or_init(|| RequestLog::new(1000))
99}
100
101fn now_secs() -> u64 {
102 SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0)
103}
104
105pub struct LogLayer;
107
108impl Middleware for LogLayer {
109 fn handle(
110 &self,
111 request: &Request,
112 connection: &ConnectionInfo,
113 next: &dyn Application,
114 ) -> Result<Response, String> {
115 let start = std::time::Instant::now();
116 let result = next.execute(request, connection);
117 let latency_ms = start.elapsed().as_millis() as u64;
118
119 let status = match &result {
120 Ok(r) => r.status_code,
121 Err(_) => 500,
122 };
123 let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri).to_string();
124
125 global().push(LogEntry {
126 timestamp: now_secs(),
127 method: request.method.clone(),
128 path,
129 status,
130 client_ip: connection.client.ip.clone(),
131 latency_ms,
132 });
133
134 result
135 }
136}