170 lines
5.8 KiB
Rust
170 lines
5.8 KiB
Rust
//! System Logger Implementation
|
|
//!
|
|
//! This module implements the system-wide logging functionality that captures
|
|
//! all non-job-specific logs from every component with target-based filtering.
|
|
|
|
use crate::{LoggerError, Result, custom_formatter::HeroFormatter};
|
|
use std::path::{Path, PathBuf};
|
|
use tracing_subscriber::{
|
|
filter::{EnvFilter, LevelFilter},
|
|
fmt,
|
|
layer::SubscriberExt,
|
|
util::SubscriberInitExt,
|
|
Layer,
|
|
};
|
|
use tracing_appender::{non_blocking::WorkerGuard, rolling};
|
|
|
|
/// Initialize the system logger with component-based filtering
|
|
///
|
|
/// This creates multiple file appenders, each filtered by a specific tracing target:
|
|
/// - `tracing::info!(target: "supervisor", ...)` -> `logs/supervisor/`
|
|
/// - `tracing::info!(target: "osis_actor", ...)` -> `logs/actor/osis/`
|
|
/// - etc.
|
|
pub fn init_system_logger<P: AsRef<Path>>(
|
|
logs_root: P,
|
|
components: &[String],
|
|
) -> Result<Vec<WorkerGuard>> {
|
|
let logs_root = logs_root.as_ref();
|
|
|
|
// Ensure log directories exist
|
|
crate::utils::ensure_log_directories(logs_root, components)?;
|
|
|
|
let mut guards = Vec::new();
|
|
let mut layers = Vec::new();
|
|
|
|
// Create a layer for each component
|
|
for component in components {
|
|
let (layer, guard) = create_component_layer(logs_root, component)?;
|
|
layers.push(layer);
|
|
guards.push(guard);
|
|
}
|
|
|
|
// Create the registry with all layers
|
|
let registry = tracing_subscriber::registry();
|
|
|
|
// Add all component layers to the registry
|
|
let collected_layers = layers.into_iter().collect::<Vec<_>>();
|
|
let registry = registry.with(collected_layers);
|
|
|
|
// Add console output for development
|
|
let console_layer = fmt::layer()
|
|
.with_target(true)
|
|
.with_thread_ids(true)
|
|
.with_file(true)
|
|
.with_line_number(true)
|
|
.with_filter(EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into()));
|
|
|
|
// Set as global default
|
|
registry.with(console_layer).init();
|
|
|
|
tracing::info!(target: "hero_logger", "System logger initialized with {} components", components.len());
|
|
|
|
Ok(guards)
|
|
}
|
|
|
|
/// Create a filtered layer for a specific component
|
|
fn create_component_layer<P: AsRef<Path>>(
|
|
logs_root: P,
|
|
component: &str,
|
|
) -> Result<(Box<dyn Layer<tracing_subscriber::Registry> + Send + Sync>, WorkerGuard)> {
|
|
let logs_root = logs_root.as_ref();
|
|
|
|
// Determine the log directory based on component type
|
|
let log_dir = if component == "supervisor" {
|
|
logs_root.join("supervisor")
|
|
} else {
|
|
// Extract actor type from component name (e.g., "osis_actor" -> "osis")
|
|
let actor_type = component.strip_suffix("_actor").unwrap_or(component);
|
|
logs_root.join("actor").join(actor_type)
|
|
};
|
|
|
|
// Create hourly rolling file appender
|
|
let file_appender = rolling::hourly(&log_dir, "log");
|
|
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
|
|
|
// Create a formatted layer with custom Hero formatter and target filtering
|
|
let layer = fmt::layer()
|
|
.with_writer(non_blocking)
|
|
.event_format(HeroFormatter::new())
|
|
.with_ansi(false) // No ANSI colors in log files
|
|
.with_filter(
|
|
EnvFilter::new(format!("{}=trace", component))
|
|
.add_directive(LevelFilter::INFO.into())
|
|
);
|
|
|
|
Ok((layer.boxed(), guard))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tempfile::TempDir;
|
|
use tracing::{info, warn};
|
|
use std::time::Duration;
|
|
use tokio::time::sleep;
|
|
|
|
#[tokio::test]
|
|
async fn test_system_logger_initialization() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let logs_root = temp_dir.path();
|
|
|
|
let components = vec![
|
|
"supervisor".to_string(),
|
|
"osis_actor".to_string(),
|
|
"sal_actor".to_string(),
|
|
];
|
|
|
|
let _guards = init_system_logger(logs_root, &components).unwrap();
|
|
|
|
// Test logging to different targets
|
|
info!(target: "supervisor", "Supervisor started");
|
|
info!(target: "osis_actor", "OSIS actor ready");
|
|
info!(target: "sal_actor", "SAL actor ready");
|
|
|
|
// Give some time for async writing
|
|
sleep(Duration::from_millis(100)).await;
|
|
|
|
// Verify directories were created
|
|
assert!(logs_root.join("supervisor").exists());
|
|
assert!(logs_root.join("actor/osis").exists());
|
|
assert!(logs_root.join("actor/sal").exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_component_layer_creation() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let logs_root = temp_dir.path();
|
|
|
|
// Create supervisor layer
|
|
let (supervisor_layer, _guard1) = create_component_layer(logs_root, "supervisor").unwrap();
|
|
assert!(logs_root.join("supervisor").exists());
|
|
|
|
// Create actor layer
|
|
let (actor_layer, _guard2) = create_component_layer(logs_root, "osis_actor").unwrap();
|
|
assert!(logs_root.join("actor/osis").exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_multiple_components() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let logs_root = temp_dir.path();
|
|
|
|
let components = vec![
|
|
"supervisor".to_string(),
|
|
"osis_actor".to_string(),
|
|
"sal_actor".to_string(),
|
|
"v_actor".to_string(),
|
|
"python_actor".to_string(),
|
|
];
|
|
|
|
let guards = init_system_logger(logs_root, &components).unwrap();
|
|
assert_eq!(guards.len(), components.len());
|
|
|
|
// Test that all directories were created
|
|
assert!(logs_root.join("supervisor").exists());
|
|
assert!(logs_root.join("actor/osis").exists());
|
|
assert!(logs_root.join("actor/sal").exists());
|
|
assert!(logs_root.join("actor/v").exists());
|
|
assert!(logs_root.join("actor/python").exists());
|
|
}
|
|
} |