use crate::component::{ComponentGuard, ComponentTracker};
use crate::output::{
BufferedOutput, ConsoleOutput, FileOutput, MultiOutput, OutputDestination, RotatingFileOutput,
};
use crate::{config::Config, context::Context, level::LogLevel, profile::ProfileGuard};
#[cfg(feature = "async")]
use crate::async_output::AsyncOutput;
#[cfg(feature = "system-monitor")]
use crate::monitor::SystemMonitor;
use parking_lot::RwLock;
use serde_json::Value;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
thread_local! {
static LOG_BUFFER: RefCell<HashMap<String, Value>> = RefCell::new(HashMap::with_capacity(16));
static TIMESTAMP_BUFFER: RefCell<String> = RefCell::new(String::with_capacity(35));
}
pub struct Logger {
name: String,
config: Arc<RwLock<Config>>,
context: Arc<RwLock<Context>>,
output: Arc<dyn OutputDestination>,
component_tracker: Arc<ComponentTracker>,
#[cfg(feature = "system-monitor")]
system_monitor: Arc<RwLock<SystemMonitor>>,
}
impl Logger {
pub fn new(name: &str) -> Self {
Self::with_config(name, Config::default())
}
pub fn with_config(name: &str, config: Config) -> Self {
if let Err(e) = config.validate() {
panic!("Invalid configuration: {}", e);
}
let output = Self::build_output(&config);
Self {
name: name.to_string(),
config: Arc::new(RwLock::new(config)),
context: Arc::new(RwLock::new(Context::new())),
output,
component_tracker: Arc::new(ComponentTracker::new()),
#[cfg(feature = "system-monitor")]
system_monitor: Arc::new(RwLock::new(SystemMonitor::new())),
}
}
fn build_output(config: &Config) -> Arc<dyn OutputDestination> {
let mut multi_output = MultiOutput::new();
if config.output.console_enabled {
let console = Box::new(ConsoleOutput::new(config.output.colored_output));
multi_output = multi_output.add_output(console);
}
if config.output.file_enabled {
if let Some(file_path) = &config.output.file_path {
if config.output.max_file_size > 0 && config.output.max_files > 1 {
match RotatingFileOutput::new(
file_path,
config.output.max_file_size,
config.output.max_files,
config.output.json_format,
) {
Ok(rotating) => {
multi_output = multi_output.add_output(Box::new(rotating));
}
Err(e) => {
eprintln!("Failed to create rotating file output: {}", e);
if let Ok(file) = FileOutput::new(file_path, config.output.json_format)
{
multi_output = multi_output.add_output(Box::new(file));
}
}
}
} else {
if let Ok(file) = FileOutput::new(file_path, config.output.json_format) {
multi_output = multi_output.add_output(Box::new(file));
}
}
}
}
let output: Arc<dyn OutputDestination> = Arc::new(multi_output);
let output = if config.performance.buffering_enabled {
Arc::new(BufferedOutput::new(output, config.performance.buffer_size))
} else {
output
};
#[cfg(feature = "async")]
let output = if config.performance.async_enabled {
match AsyncOutput::new(output.clone()) {
Ok(async_output) => Arc::new(async_output) as Arc<dyn OutputDestination>,
Err(e) => {
eprintln!("Failed to create async output: {}", e);
output
}
}
} else {
output
};
#[cfg(not(feature = "async"))]
let output = output;
output
}
pub fn name(&self) -> &str {
&self.name
}
pub fn get_component_tracker(&self) -> &ComponentTracker {
&self.component_tracker
}
pub fn set_config(&self, config: Config) {
if let Err(e) = config.validate() {
eprintln!("Invalid configuration: {}", e);
return;
}
let _new_output = Self::build_output(&config);
*self.config.write() = config;
eprintln!("Warning: Configuration updated but output destinations remain unchanged");
eprintln!(
"Consider creating a new logger instance for the new configuration to take full effect"
);
}
pub fn get_config(&self) -> Config {
self.config.read().clone()
}
pub fn debug(&self, message: &str) {
self.log(LogLevel::Debug, message, None);
}
pub fn debug_with(&self, message: &str, data: &[(&str, &str)]) {
self.log(LogLevel::Debug, message, Some(data));
}
pub fn info(&self, message: &str) {
self.log(LogLevel::Info, message, None);
}
pub fn info_with(&self, message: &str, data: &[(&str, &str)]) {
self.log(LogLevel::Info, message, Some(data));
}
pub fn warning(&self, message: &str) {
self.log(LogLevel::Warning, message, None);
}
pub fn warning_with(&self, message: &str, data: &[(&str, &str)]) {
self.log(LogLevel::Warning, message, Some(data));
}
pub fn error(&self, message: &str) {
self.log(LogLevel::Error, message, None);
}
pub fn error_with(&self, message: &str, data: &[(&str, &str)]) {
self.log(LogLevel::Error, message, Some(data));
}
pub fn critical(&self, message: &str) {
self.log(LogLevel::Critical, message, None);
}
pub fn critical_with(&self, message: &str, data: &[(&str, &str)]) {
self.log(LogLevel::Critical, message, Some(data));
}
pub fn log_with(&self, level: LogLevel, message: &str, data: &[(&str, &str)]) {
self.log(level, message, Some(data));
}
pub fn add_context(&self, key: &str, value: &str) {
self.context.write().add(key, value);
}
pub fn remove_context(&self, key: &str) {
self.context.write().remove(key);
}
pub fn clear_context(&self) {
self.context.write().clear();
}
pub fn with_context(&self, key: &str, value: &str) -> crate::context::ContextGuard {
self.context.write().add(key, value);
crate::context::ContextGuard::new(key.to_string(), Arc::clone(&self.context))
}
pub fn profile(&self, operation: &str) -> ProfileGuard {
ProfileGuard::new(operation, self.clone())
}
pub fn track_component(&self, name: &str) -> ComponentGuard {
#[cfg(feature = "system-monitor")]
{
ComponentGuard::new_with_monitor(
name,
Arc::clone(&self.component_tracker),
Arc::clone(&self.system_monitor),
)
}
#[cfg(not(feature = "system-monitor"))]
{
ComponentGuard::new(name, Arc::clone(&self.component_tracker))
}
}
pub fn component_tracker(&self) -> &Arc<ComponentTracker> {
&self.component_tracker
}
pub fn generate_visualization(
&self,
chart_type: crate::visualization::ChartType,
output_path: Option<&str>,
) -> Result<String, String> {
use crate::visualization::{ChartConfig, MermaidGenerator};
let config = ChartConfig::new().with_chart_type(chart_type);
let generator = MermaidGenerator::new(config);
let diagram = generator.generate_diagram(&self.component_tracker)?;
if let Some(path) = output_path {
std::fs::write(path, &diagram)
.map_err(|e| format!("Failed to write diagram to file: {}", e))?;
}
Ok(diagram)
}
#[cfg(feature = "system-monitor")]
pub fn system_monitor(&self) -> &Arc<RwLock<SystemMonitor>> {
&self.system_monitor
}
fn log(&self, level: LogLevel, message: &str, data: Option<&[(&str, &str)]>) {
let config = self.config.read();
if !level.should_log(config.min_level) {
return;
}
LOG_BUFFER.with(|buffer| {
let mut log_data = buffer.borrow_mut();
log_data.clear();
let timestamp = TIMESTAMP_BUFFER.with(|ts_buf| {
let mut ts = ts_buf.borrow_mut();
ts.clear();
use std::fmt::Write;
write!(ts, "{}", chrono::Utc::now().to_rfc3339()).unwrap();
ts.clone()
});
log_data.insert("timestamp".to_string(), Value::String(timestamp));
log_data.insert("level".to_string(), Value::String(level.to_string()));
log_data.insert("logger".to_string(), Value::String(self.name.clone()));
log_data.insert("message".to_string(), Value::String(message.to_string()));
let context = self.context.read();
for (key, value) in context.iter() {
log_data.insert(key.clone(), Value::String(value.clone()));
}
drop(context);
if let Some(data) = data {
for (key, value) in data {
log_data.insert(key.to_string(), Value::String(value.to_string()));
}
}
if let Err(e) = self.output.write(level, &log_data) {
eprintln!("Failed to write log: {}", e);
}
});
}
}
impl Clone for Logger {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
config: Arc::clone(&self.config),
context: Arc::clone(&self.context),
output: Arc::clone(&self.output),
component_tracker: Arc::clone(&self.component_tracker),
#[cfg(feature = "system-monitor")]
system_monitor: Arc::clone(&self.system_monitor),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_logger_creation() {
let logger = Logger::new("test");
assert_eq!(logger.name(), "test");
}
#[test]
fn test_logging_methods() {
let logger = Logger::new("test");
logger.debug("Debug message");
logger.info("Info message");
logger.warning("Warning message");
logger.error("Error message");
logger.critical("Critical message");
}
#[test]
fn test_context_management() {
let logger = Logger::new("test");
logger.add_context("user_id", "12345");
logger.add_context("session_id", "abcdef");
logger.info("Test message with context");
logger.remove_context("user_id");
logger.clear_context();
}
#[test]
fn test_config_update() {
let logger = Logger::new("test");
let new_config = Config::new().with_min_level(LogLevel::Warning);
logger.set_config(new_config);
let current_config = logger.get_config();
assert_eq!(current_config.min_level, LogLevel::Warning);
}
}