webserver_colin_ugo/utils/
log.rs1use std::fs::{File, OpenOptions};
2use std::io::{self, Write};
3use std::net::SocketAddr;
4use std::path::Path;
5use std::sync::{Arc, Mutex};
6use chrono::Local;
7
8use crate::config::ServerConfig;
9
10pub struct Logger {
12 log_file: Arc<Mutex<File>>,
13}
14
15impl Logger {
16 pub fn new(config: &ServerConfig) -> Result<Self, io::Error> {
26 let log_path = Path::new(&config.log_file);
27
28 if let Some(parent) = log_path.parent() {
30 if !parent.exists() {
31 std::fs::create_dir_all(parent)?;
32 }
33 }
34
35 let file = OpenOptions::new()
37 .create(true)
38 .append(true)
39 .open(log_path)?;
40
41 Ok(Self {
42 log_file: Arc::new(Mutex::new(file)),
43 })
44 }
45
46 pub fn log_request(&self, client_addr: &SocketAddr, method: &str, path: &str, status_code: &str) -> Result<(), io::Error> {
59 let timestamp = Local::now().format("%d/%b/%Y:%H:%M:%S %z").to_string();
60 let status = status_code
61 .split_whitespace()
62 .nth(1)
63 .unwrap_or("---")
64 .to_string();
65
66 let log_entry = format!(
67 "{} - [{}] \"{} {}\" {} \n",
68 client_addr.ip(),
69 timestamp,
70 method,
71 path,
72 status
73 );
74
75 let mut file_guard = self.log_file.lock().unwrap();
76 file_guard.write_all(log_entry.as_bytes())?;
77 file_guard.flush()
78 }
79
80 pub fn log_message(&self, message: &str) -> Result<(), io::Error> {
90 let timestamp = Local::now().format("%d/%b/%Y:%H:%M:%S %z").to_string();
91 let log_entry = format!("[{}] {} \n", timestamp, message);
92
93 let mut file_guard = self.log_file.lock().unwrap();
94 file_guard.write_all(log_entry.as_bytes())?;
95 file_guard.flush()
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::config::ServerConfig;
103 use std::collections::HashMap;
104 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
105 use std::fs;
106 use tempfile::tempdir;
107
108 #[test]
109 fn test_logger_creation() {
110 let dir = tempdir().unwrap();
111 let log_path = dir.path().join("test.log");
112
113 let config = ServerConfig {
114 host: "localhost".to_string(),
115 port: 8080,
116 public_dir: "public".to_string(),
117 log_file: log_path.to_str().unwrap().to_string(),
118 index_file: "index.html".to_string(),
119 not_found_page: "404.html".to_string(),
120 mime_types: HashMap::new(),
121 };
122
123 let logger = Logger::new(&config);
124 assert!(logger.is_ok());
125 }
126
127 #[test]
128 fn test_log_request() {
129 let dir = tempdir().unwrap();
130 let log_path = dir.path().join("test_request.log");
131
132 let config = ServerConfig {
133 host: "localhost".to_string(),
134 port: 8080,
135 public_dir: "public".to_string(),
136 log_file: log_path.to_str().unwrap().to_string(),
137 index_file: "index.html".to_string(),
138 not_found_page: "404.html".to_string(),
139 mime_types: HashMap::new(),
140 };
141
142 let logger = Logger::new(&config).unwrap();
143 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12345);
144
145 logger.log_request(&addr, "GET", "/index.html", "HTTP/1.1 200 OK").unwrap();
146
147 let contents = fs::read_to_string(&log_path).unwrap();
149 assert!(!contents.is_empty());
150 assert!(contents.contains("127.0.0.1"));
151 assert!(contents.contains("GET /index.html"));
152 assert!(contents.contains("200"));
153 }
154
155 #[test]
156 fn test_log_message() {
157 let dir = tempdir().unwrap();
158 let log_path = dir.path().join("test_message.log");
159
160 let config = ServerConfig {
161 host: "localhost".to_string(),
162 port: 8080,
163 public_dir: "public".to_string(),
164 log_file: log_path.to_str().unwrap().to_string(),
165 index_file: "index.html".to_string(),
166 not_found_page: "404.html".to_string(),
167 mime_types: HashMap::new(),
168 };
169
170 let logger = Logger::new(&config).unwrap();
171 logger.log_message("Serveur démarré").unwrap();
172
173 let contents = fs::read_to_string(&log_path).unwrap();
175 assert!(!contents.is_empty());
176 assert!(contents.contains("Serveur démarré"));
177 }
178}