webserver_colin_ugo/utils/
log.rs

1use 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
10/// Structure de gestion de journalisation pour le serveur
11pub struct Logger {
12    log_file: Arc<Mutex<File>>,
13}
14
15impl Logger {
16    /// Crée une nouvelle instance de Logger avec le chemin du fichier de log spécifié
17    ///
18    /// # Arguments
19    ///
20    /// * `config` - La configuration du serveur contenant le chemin du fichier de log
21    ///
22    /// # Retourne
23    ///
24    /// * `Result<Self, io::Error>` - Une instance de Logger ou une erreur
25    pub fn new(config: &ServerConfig) -> Result<Self, io::Error> {
26        let log_path = Path::new(&config.log_file);
27        
28        // Créer le répertoire parent si nécessaire
29        if let Some(parent) = log_path.parent() {
30            if !parent.exists() {
31                std::fs::create_dir_all(parent)?;
32            }
33        }
34        
35        // Ouvrir le fichier de log en mode append
36        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    /// Enregistre une requête HTTP dans le fichier de log
47    ///
48    /// # Arguments
49    ///
50    /// * `client_addr` - L'adresse IP du client
51    /// * `method` - La méthode HTTP utilisée
52    /// * `path` - Le chemin de la ressource demandée
53    /// * `status_code` - Le code de statut HTTP de la réponse
54    ///
55    /// # Retourne
56    ///
57    /// * `Result<(), io::Error>` - Résultat de l'opération d'écriture
58    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    /// Enregistre un message dans le fichier de log
81    ///
82    /// # Arguments
83    ///
84    /// * `message` - Le message à enregistrer
85    ///
86    /// # Retourne
87    ///
88    /// * `Result<(), io::Error>` - Résultat de l'opération d'écriture
89    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        // Vérifier que quelque chose a été écrit dans le fichier
148        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        // Vérifier que quelque chose a été écrit dans le fichier
174        let contents = fs::read_to_string(&log_path).unwrap();
175        assert!(!contents.is_empty());
176        assert!(contents.contains("Serveur démarré"));
177    }
178}