From 4c9d28fb3c026ca95ac3139ac83f7dfd6d1d1b25 Mon Sep 17 00:00:00 2001 From: senke Date: Sat, 27 Dec 2025 02:02:28 +0100 Subject: [PATCH] [LOGGING] Fix #14: Support rotation logs Rust avec tracing-appender dans veza-common --- veza-common/Cargo.toml | 1 + veza-common/src/logging.rs | 82 ++++++++++++++++++++++++++++++++++---- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/veza-common/Cargo.toml b/veza-common/Cargo.toml index 00ec50d85..ce75be1e8 100644 --- a/veza-common/Cargo.toml +++ b/veza-common/Cargo.toml @@ -37,6 +37,7 @@ sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid" # Logging tracing = "0.1" tracing-subscriber = "0.3" +tracing-appender = "0.2" # FIX #14: Support rotation des logs # Configuration config = "0.13" diff --git a/veza-common/src/logging.rs b/veza-common/src/logging.rs index 3f8e61b07..35d8e20af 100644 --- a/veza-common/src/logging.rs +++ b/veza-common/src/logging.rs @@ -1,16 +1,22 @@ //! Logging utilities for Veza Rust services //! //! This module provides centralized logging configuration and utilities. +//! FIX #14: Support rotation des logs avec tracing-appender use tracing::{Level, Subscriber}; use tracing_subscriber::{ fmt::{self, format::FmtSpan}, layer::SubscriberExt, util::SubscriberInitExt, - EnvFilter, Registry, + EnvFilter, Registry, Layer, +}; +use tracing_appender::{ + non_blocking::WorkerGuard, + rolling::{RollingFileAppender, Rotation}, }; -use crate::{VezaError, VezaResult}; +use crate::error::server::{VezaError, VezaResult}; +use std::path::PathBuf; /// FIX #24: Normaliser le niveau de log depuis LOG_LEVEL ou RUST_LOG /// Convertit les niveaux Go (INFO, DEBUG, WARN, ERROR) vers les niveaux Rust (info, debug, warn, error) @@ -65,6 +71,9 @@ pub fn init() -> VezaResult<()> { } /// Initialize logging with custom configuration +/// FIX #14: Support rotation des logs si config.file est défini +/// Note: Le WorkerGuard doit être gardé en vie par l'appelant pour maintenir la rotation active +/// Pour l'instant, on utilise une variable statique leakée (acceptable car le guard doit vivre toute la durée de vie de l'app) pub fn init_with_config(config: LoggingConfig) -> VezaResult<()> { // FIX #24: Normaliser le niveau de log depuis la config ou LOG_LEVEL let log_level = if !config.level.is_empty() && config.level != "info" { @@ -88,6 +97,37 @@ pub fn init_with_config(config: LoggingConfig) -> VezaResult<()> { let env_filter = EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new(&log_level)); + // FIX #14: Configurer la rotation des logs si un fichier est spécifié + let (file_writer, guard) = if let Some(file_path) = &config.file { + let path = PathBuf::from(file_path); + let log_dir = path.parent() + .ok_or_else(|| VezaError::Config("Invalid log file path".to_string()))?; + let file_prefix = path.file_stem() + .and_then(|s| s.to_str()) + .ok_or_else(|| VezaError::Config("Invalid log file name".to_string()))?; + + // Déterminer la rotation selon max_size + let rotation = if config.max_size > 100 * 1024 * 1024 { + // > 100MB -> rotation quotidienne + Rotation::DAILY + } else { + // <= 100MB -> rotation horaire + Rotation::HOURLY + }; + + let file_appender = RollingFileAppender::new(rotation, log_dir, file_prefix); + let (non_blocking, worker_guard) = tracing_appender::non_blocking(file_appender); + + // FIX #14: Le WorkerGuard doit être gardé en vie pour maintenir la rotation active + // On utilise Box::leak pour le garder en vie pendant toute la durée de vie de l'application + // C'est acceptable car le guard doit vivre tant que l'application tourne + Box::leak(Box::new(worker_guard)); + + (Some(non_blocking), true) + } else { + (None, false) + }; + // FIX #25: Standardiser sur JSON en production/staging, texte en développement // Create formatting layer based on format let fmt_layer = match config.format.as_str() { @@ -112,13 +152,41 @@ pub fn init_with_config(config: LoggingConfig) -> VezaResult<()> { _ => return Err(VezaError::Config(format!("Invalid log format: {}", config.format))), }; - // Initialize the subscriber - Registry::default() + // FIX #14: Ajouter un layer pour les fichiers si rotation configurée + let mut registry = Registry::default() .with(env_filter) - .with(fmt_layer) - .init(); + .with(fmt_layer); + + if let Some(writer) = file_writer { + // Layer pour les fichiers avec rotation (toujours en JSON pour faciliter l'agrégation) + let file_layer = fmt::layer() + .json() + .with_writer(writer) + .with_target(true) + .with_thread_ids(true) + .with_thread_names(true) + .with_span_events(FmtSpan::CLOSE) + .with_file(true) + .with_line_number(true) + .boxed(); + + registry = registry.with(file_layer); + } + + // Initialize the subscriber + registry.init(); + + if guard { + tracing::info!( + file = ?config.file, + max_size = config.max_size, + max_files = config.max_files, + "Logging initialized with file rotation" + ); + } else { + tracing::info!("Logging initialized with custom config"); + } - tracing::info!("Logging initialized with custom config"); Ok(()) }