//! Logging structuré avec tracing pour le serveur de chat //! //! Ce module fournit un système de logging avancé avec: //! - Logs structurés avec champs contextuels //! - Rotation des logs //! - Filtrage par niveau et module //! - Export vers différents formats (JSON, Pretty, Compact) //! - Intégration avec les métriques use crate::config::{LogFormat, LogRotation, LoggingConfig, ServerConfig}; use crate::error::{ChatError, Result}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; use std::time::Duration; use tracing::{debug, error, info, trace, warn}; use tracing_appender::{ non_blocking::{NonBlocking, WorkerGuard}, rolling::{RollingFileAppender, Rotation}, }; use tracing_subscriber::{ fmt::{self, format::Writer, time::ChronoUtc}, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer, Registry, }; /// Configuration du logging structuré #[derive(Debug)] pub struct StructuredLogging { config: LoggingConfig, _guard: Option, } impl StructuredLogging { /// Initialise le système de logging structuré pub fn new(config: LoggingConfig) -> Result { Ok(Self { config, _guard: None, }) } /// Configure et initialise le subscriber tracing pub fn setup(&self) -> Result<()> { // Filtre d'environnement let env_filter = EnvFilter::try_from_default_env() .or_else(|_| EnvFilter::try_new(&self.config.level)) .map_err(|e| { ChatError::configuration_error(&format!("Erreur configuration filtre: {e}")) })?; // Configuration du format let format_layer = match self.config.format { LogFormat::Json => fmt::layer() .json() .with_timer(ChronoUtc::rfc_3339()) .with_target(true) .with_file(true) .with_line_number(true) .boxed(), LogFormat::Pretty => fmt::layer() .pretty() .with_timer(ChronoUtc::rfc_3339()) .with_target(true) .with_file(true) .with_line_number(true) .boxed(), LogFormat::Compact => fmt::layer() .compact() .with_timer(ChronoUtc::rfc_3339()) .with_target(true) .boxed(), }; // Configuration de la sortie let registry = Registry::default().with(env_filter).with(format_layer); registry.init(); info!( level = %self.config.level, format = ?self.config.format, file = ?self.config.file, "🔧 Système de logging structuré initialisé" ); Ok(()) } } /// Macros de logging contextuel pour le chat pub mod chat_logs { use super::*; use std::collections::HashMap; use tracing::{debug, error, info, trace, warn}; use uuid::Uuid; /// Log d'authentification pub fn auth_success(user_id: i32, username: &str, ip: &str) { info!( event = "auth_success", user_id = %user_id, username = %username, ip_address = %ip, "🔐 Utilisateur authentifié" ); } /// Log d'échec d'authentification pub fn auth_failure(username: &str, ip: &str, reason: &str) { warn!( event = "auth_failure", username = %username, ip_address = %ip, reason = %reason, "❌ Échec d'authentification" ); } /// Log de connexion WebSocket pub fn websocket_connected(connection_id: &str, user_id: i32, username: &str) { info!( event = "websocket_connected", connection_id = %connection_id, user_id = %user_id, username = %username, "🔌 Connexion WebSocket établie" ); } /// Log de déconnexion WebSocket pub fn websocket_disconnected(connection_id: &str, user_id: i32, username: &str, reason: &str) { info!( event = "websocket_disconnected", connection_id = %connection_id, user_id = %user_id, username = %username, reason = %reason, "🔌 Connexion WebSocket fermée" ); } /// Log d'envoi de message pub fn message_sent( message_id: String, user_id: i32, username: &str, room_id: &str, message_type: &str, content_length: usize, ) { info!( event = "message_sent", message_id = %message_id, user_id = %user_id, username = %username, room_id = %room_id, message_type = %message_type, content_length = %content_length, "💬 Message envoyé" ); } /// Log de réception de message pub fn message_received( message_id: i32, user_id: i32, username: &str, room_id: &str, message_type: &str, ) { debug!( event = "message_received", message_id = %message_id, user_id = %user_id, username = %username, room_id = %room_id, message_type = %message_type, "📨 Message reçu" ); } /// Log de création de salon pub fn room_created(room_id: &str, room_name: &str, creator_id: i32, creator_username: &str) { info!( event = "room_created", room_id = %room_id, room_name = %room_name, creator_id = %creator_id, creator_username = %creator_username, "🏠 Salon créé" ); } /// Log de suppression de salon pub fn room_deleted(room_id: &str, room_name: &str, deleter_id: i32, deleter_username: &str) { warn!( event = "room_deleted", room_id = %room_id, room_name = %room_name, deleter_id = %deleter_id, deleter_username = %deleter_username, "🗑️ Salon supprimé" ); } /// Log d'erreur système pub fn system_error(error_type: &str, context: &str, error: &str) { error!( event = "system_error", error_type = %error_type, context = %context, error = %error, "💥 Erreur système" ); } /// Log d'erreur de validation pub fn validation_error(field: &str, value: &str, reason: &str) { warn!( event = "validation_error", field = %field, value = %value, reason = %reason, "⚠️ Erreur de validation" ); } /// Log de rate limiting pub fn rate_limit_triggered( user_id: i32, username: &str, limit_type: &str, current_count: u32, ) { warn!( event = "rate_limit_triggered", user_id = %user_id, username = %username, limit_type = %limit_type, current_count = %current_count, "🚫 Rate limit déclenché" ); } /// Log de métriques pub fn metrics_updated(metric_name: &str, value: f64, labels: &HashMap) { trace!( event = "metrics_updated", metric_name = %metric_name, value = %value, labels = ?labels, "📊 Métrique mise à jour" ); } /// Log de performance pub fn performance_measurement( operation: &str, duration_ms: f64, success: bool, additional_data: Option<&HashMap>, ) { let level = if success { "info" } else { "warn" }; let message = if success { "⚡ Opération terminée" } else { "🐌 Opération lente" }; match level { "info" => { info!( event = "performance_measurement", operation = %operation, duration_ms = %duration_ms, success = %success, additional_data = ?additional_data, "{}", message ); } _ => { warn!( event = "performance_measurement", operation = %operation, duration_ms = %duration_ms, success = %success, additional_data = ?additional_data, "{}", message ); } } } /// Log de démarrage du serveur pub fn server_started(bind_addr: &str, environment: &str, version: &str) { info!( event = "server_started", bind_addr = %bind_addr, environment = %environment, version = %version, "🚀 Serveur de chat démarré" ); } /// Log d'arrêt du serveur pub fn server_stopped(reason: &str, uptime_seconds: u64) { info!( event = "server_stopped", reason = %reason, uptime_seconds = %uptime_seconds, "🛑 Serveur de chat arrêté" ); } /// Log de configuration pub fn config_loaded(config_source: &str, config_path: Option<&str>) { info!( event = "config_loaded", config_source = %config_source, config_path = ?config_path, "⚙️ Configuration chargée" ); } /// Log de base de données pub fn database_operation(operation: &str, table: &str, duration_ms: f64, success: bool) { let level = if success { "debug" } else { "error" }; let message = if success { "🗄️ Opération DB réussie" } else { "💥 Erreur DB" }; match level { "debug" => { debug!( event = "database_operation", operation = %operation, table = %table, duration_ms = %duration_ms, success = %success, "{}", message ); } _ => { error!( event = "database_operation", operation = %operation, table = %table, duration_ms = %duration_ms, success = %success, "{}", message ); } } } /// Log de cache pub fn cache_operation(operation: &str, key: &str, hit: bool, duration_ms: f64) { debug!( event = "cache_operation", operation = %operation, key = %key, hit = %hit, duration_ms = %duration_ms, "💾 Opération de cache" ); } /// Log de sécurité pub fn security_event(event_type: &str, user_id: Option, ip: &str, details: &str) { warn!( event = "security_event", event_type = %event_type, user_id = ?user_id, ip_address = %ip, details = %details, "🔒 Événement de sécurité" ); } } /// Wrapper pour les logs avec contexte utilisateur pub struct UserContextLogger { user_id: i32, username: String, ip_address: String, } impl UserContextLogger { pub fn new(user_id: i32, username: String, ip_address: String) -> Self { Self { user_id, username, ip_address, } } pub fn info(&self, message: &str, fields: Option<&HashMap>) { info!( user_id = %self.user_id, username = %self.username, ip_address = %self.ip_address, additional_fields = ?fields, "{}", message ); } pub fn warn(&self, message: &str, fields: Option<&HashMap>) { warn!( user_id = %self.user_id, username = %self.username, ip_address = %self.ip_address, additional_fields = ?fields, "{}", message ); } pub fn error(&self, message: &str, fields: Option<&HashMap>) { error!( user_id = %self.user_id, username = %self.username, ip_address = %self.ip_address, additional_fields = ?fields, "{}", message ); } } /// Initialise le système de logging depuis la configuration du serveur pub fn init_logging_from_config(config: &ServerConfig) -> Result { let logging_config = config.logging.clone(); let structured_logging = StructuredLogging::new(logging_config)?; structured_logging.setup()?; chat_logs::config_loaded("server_config", None); chat_logs::server_started( &config.server.bind_addr.to_string(), &config.server.environment.to_string(), env!("CARGO_PKG_VERSION"), ); Ok(structured_logging) } /// Configuration par défaut pour les tests pub fn init_test_logging() -> Result<()> { let config = LoggingConfig { level: "debug".to_string(), format: LogFormat::Compact, file: None, rotation: None, filters: vec!["chat_server=debug".to_string()], }; let structured_logging = StructuredLogging::new(config)?; structured_logging.setup()?; Ok(()) } #[cfg(test)] mod tests { use super::*; use std::collections::HashMap; #[test] fn test_user_context_logger() { let logger = UserContextLogger::new(1, "testuser".to_string(), "127.0.0.1".to_string()); // Test que le logger peut être créé sans erreur assert_eq!(logger.user_id, 1); assert_eq!(logger.username, "testuser"); assert_eq!(logger.ip_address, "127.0.0.1"); } #[test] fn test_chat_logs_macros() { // Test que les macros de logging peuvent être appelées // (elles ne feront rien en mode test sans subscriber) chat_logs::auth_success(1, "testuser", "127.0.0.1"); chat_logs::message_sent(1, 1, "testuser", "room1", "text", 10); chat_logs::system_error("test", "context", "error"); } #[test] fn test_structured_logging_creation() { let config = LoggingConfig { level: "info".to_string(), format: LogFormat::Pretty, file: None, rotation: None, filters: vec![], }; let result = StructuredLogging::new(config); assert!(result.is_ok()); } }