[LOGGING] Fix #14: Support rotation logs Rust avec tracing-appender dans veza-common

This commit is contained in:
senke 2025-12-27 02:02:28 +01:00
parent 112208df24
commit 4c9d28fb3c
2 changed files with 76 additions and 7 deletions

View file

@ -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"

View file

@ -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(())
}