use std::{thread, time::Duration};
use chrono::Local;
use tempfile::tempdir;
use crate::{
appender::{
file::FileAppender,
rolling::{RollingFileAppender, RollingStrategy},
LogAppender,
},
config::{
ConsoleConfig, JsonFormatConfig, LogFormat, LogLevel, LoggerConfig, SizeRollingConfig,
TextFormatConfig, TimePeriod, TimeRollingConfig,
},
Logger,
};
#[test]
fn test_file_appender() {
let temp_dir = tempdir().unwrap();
let log_path = temp_dir.path().join("basic.log");
let appender = FileAppender::new(log_path.clone()).unwrap();
appender.write(LogLevel::Info, "Test message 1").unwrap();
appender.write(LogLevel::Error, "Test message 2").unwrap();
appender.flush().unwrap();
let content = std::fs::read_to_string(log_path).unwrap();
assert!(content.contains("Test message 1"));
assert!(content.contains("Test message 2"));
}
#[test]
fn test_logger_config() {
let config = LoggerConfig {
level: LogLevel::Info,
dir: "logs".into(),
filename: "test.log".into(),
rolling_policy: crate::config::RollingPolicy::Time(TimeRollingConfig {
period: TimePeriod::Daily,
keep_days: 7,
}),
format: LogFormat::Text(TextFormatConfig {
time_format: "%Y-%m-%d %H:%M:%S".to_string(),
show_level: true,
show_target: true,
show_thread_id: true,
show_file: true,
show_line: true,
}),
async_write: true,
split_by_level: false,
console: Some(ConsoleConfig {
enabled: true,
level: LogLevel::Info,
colored: true,
}),
};
let logger = Logger::new(config).unwrap();
logger.write(LogLevel::Info, "Test config message").unwrap();
logger.flush().unwrap();
}
#[test]
fn test_json_format() {
let temp_dir = tempdir().unwrap();
std::fs::create_dir_all(temp_dir.path()).unwrap();
let config = LoggerConfig {
level: LogLevel::Info,
dir: temp_dir.path().to_path_buf(),
filename: "json.log".to_string(),
rolling_policy: crate::config::RollingPolicy::Time(TimeRollingConfig {
period: TimePeriod::Daily,
keep_days: 7,
}),
format: LogFormat::Json(JsonFormatConfig {
time_format: "%Y-%m-%d %H:%M:%S".to_string(),
pretty: true,
include_caller: true,
include_thread: true,
}),
async_write: false,
split_by_level: false,
console: None,
};
let logger = Logger::new(config).unwrap();
logger
.write(LogLevel::Info, r#"{"message": "test json"}"#)
.unwrap();
logger.flush().unwrap();
std::thread::sleep(std::time::Duration::from_millis(100));
let now = Local::now();
let log_path = temp_dir
.path()
.join(format!("json.log.{}", now.format("%Y%m%d")));
println!("Looking for JSON log file at: {}", log_path.display());
let content = std::fs::read_to_string(log_path).unwrap();
assert!(content.contains(r#""message": "test json""#));
assert!(content.contains(r#""level": "INFO""#));
assert!(content.contains(r#""timestamp""#));
assert!(content.contains(r#""thread_id""#));
}
#[test]
fn test_error_handling() {
let result = FileAppender::new("/invalid/path/test.log".into());
assert!(result.is_err());
let level = LogLevel::Info;
let tracing_level: tracing::Level = level.into();
assert_eq!(tracing_level, tracing::Level::INFO);
}
#[test]
fn test_compound_appender() {
let temp_dir = tempdir().unwrap();
let config = LoggerConfig {
level: LogLevel::Info,
dir: temp_dir.path().to_path_buf(),
filename: "compound.log".to_string(),
rolling_policy: crate::config::RollingPolicy::Compound {
time: TimeRollingConfig {
period: TimePeriod::Daily,
keep_days: 7,
},
size: SizeRollingConfig {
max_size: 1024,
max_files: 5,
},
},
format: LogFormat::Text(TextFormatConfig {
time_format: "%Y-%m-%d %H:%M:%S".to_string(),
show_level: true,
show_target: true,
show_thread_id: true,
show_file: true,
show_line: true,
}),
async_write: true,
split_by_level: false,
console: None,
};
let logger = Logger::new(config).unwrap();
for i in 0..1000 {
logger
.write(LogLevel::Info, &format!("Compound test message {}", i))
.unwrap();
}
logger.flush().unwrap();
let files: Vec<_> = std::fs::read_dir(temp_dir.path())
.unwrap()
.filter_map(|e| e.ok())
.collect();
assert!(files.len() > 1);
}
#[test]
fn test_time_rolling() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path().join("test.log");
let config = TimeRollingConfig {
period: TimePeriod::Hourly,
keep_days: 7,
};
let appender = RollingFileAppender::new(base_path, RollingStrategy::Time(config)).unwrap();
for i in 0..100 {
appender
.write(LogLevel::Info, &format!("Test log message {}", i))
.unwrap();
}
thread::sleep(Duration::from_secs(2));
for i in 100..200 {
appender
.write(LogLevel::Info, &format!("Test log message {}", i))
.unwrap();
}
appender.flush().unwrap();
}
#[test]
fn test_size_rolling() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path().join("test.log");
let config = SizeRollingConfig {
max_size: 1024, max_files: 5,
};
let appender = RollingFileAppender::new(base_path, RollingStrategy::Size(config)).unwrap();
for i in 0..1000 {
appender
.write(LogLevel::Info, &format!("Test log message {}", i))
.unwrap();
}
appender.flush().unwrap();
}
#[test]
fn test_performance() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path().join("perf.log");
let config = SizeRollingConfig {
max_size: 10 * 1024 * 1024, max_files: 5,
};
let appender = RollingFileAppender::new(base_path, RollingStrategy::Size(config)).unwrap();
let start = std::time::Instant::now();
for i in 0..1_000_000 {
appender
.write(
LogLevel::Info,
&format!("Performance test log message {}", i),
)
.unwrap();
}
appender.flush().unwrap();
let duration = start.elapsed();
println!(
"Performance test: wrote 1,000,000 log messages in {:?} ({:.2} messages/sec)",
duration,
1_000_000.0 / duration.as_secs_f64()
);
}
#[test]
fn test_concurrent_writing() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path().join("concurrent.log");
let config = SizeRollingConfig {
max_size: 10 * 1024 * 1024, max_files: 5,
};
let appender = RollingFileAppender::new(base_path, RollingStrategy::Size(config)).unwrap();
let appender = std::sync::Arc::new(appender);
let mut handles = vec![];
for t in 0..10 {
let appender = appender.clone();
let handle = thread::spawn(move || {
for i in 0..100_000 {
appender
.write(LogLevel::Info, &format!("Thread {} - log message {}", t, i))
.unwrap();
}
});
handles.push(handle);
}
let start = std::time::Instant::now();
for handle in handles {
handle.join().unwrap();
}
appender.flush().unwrap();
let duration = start.elapsed();
println!(
"Concurrent test: wrote 1,000,000 log messages in {:?} ({:.2} messages/sec)",
duration,
1_000_000.0 / duration.as_secs_f64()
);
}
#[test]
fn test_log_levels() {
let temp_dir = tempdir().unwrap();
std::fs::create_dir_all(temp_dir.path()).unwrap();
let config = LoggerConfig {
level: LogLevel::Trace, dir: temp_dir.path().to_path_buf(),
filename: "levels.log".to_string(),
rolling_policy: crate::config::RollingPolicy::Time(TimeRollingConfig {
period: TimePeriod::Daily,
keep_days: 7,
}),
format: LogFormat::Text(TextFormatConfig {
time_format: "%Y-%m-%d %H:%M:%S".to_string(),
show_level: true,
show_target: true,
show_thread_id: true,
show_file: true,
show_line: true,
}),
async_write: false,
split_by_level: true,
console: None,
};
let logger = Logger::new(config).unwrap();
logger.write(LogLevel::Trace, "Trace message").unwrap();
logger.write(LogLevel::Debug, "Debug message").unwrap();
logger.write(LogLevel::Info, "Info message").unwrap();
logger.write(LogLevel::Warn, "Warn message").unwrap();
logger.write(LogLevel::Error, "Error message").unwrap();
logger.flush().unwrap();
std::thread::sleep(std::time::Duration::from_millis(100));
let files: Vec<_> = std::fs::read_dir(temp_dir.path())
.unwrap()
.filter_map(|e| e.ok())
.collect();
println!("Found files:");
for file in &files {
println!(" - {}", file.file_name().to_string_lossy());
}
assert!(files.len() >= 5, "应该有至少5个日志文件(每个级别一个)");
let filenames: Vec<_> = files
.iter()
.map(|f| f.file_name().to_string_lossy().to_string())
.collect();
assert!(
filenames.iter().any(|f| f.contains("trace")),
"未找到trace级别的日志文件"
);
assert!(
filenames.iter().any(|f| f.contains("debug")),
"未找到debug级别的日志文件"
);
assert!(
filenames.iter().any(|f| f.contains("info")),
"未找到info级别的日志文件"
);
assert!(
filenames.iter().any(|f| f.contains("warn")),
"未找到warn级别的日志文件"
);
assert!(
filenames.iter().any(|f| f.contains("error")),
"未找到error级别的日志文件"
);
}
#[test]
fn test_text_format_details() {
let temp_dir = tempdir().unwrap();
std::fs::create_dir_all(temp_dir.path()).unwrap();
let config = LoggerConfig {
level: LogLevel::Info,
dir: temp_dir.path().to_path_buf(),
filename: "text.log".to_string(),
rolling_policy: crate::config::RollingPolicy::Time(TimeRollingConfig {
period: TimePeriod::Daily,
keep_days: 7,
}),
format: LogFormat::Text(TextFormatConfig {
time_format: "%Y-%m-%d %H:%M:%S".to_string(),
show_level: true,
show_target: true,
show_thread_id: true,
show_file: true,
show_line: true,
}),
async_write: false,
split_by_level: false,
console: None,
};
let logger = Logger::new(config).unwrap();
logger.write(LogLevel::Info, "Test message").unwrap();
logger.flush().unwrap();
std::thread::sleep(std::time::Duration::from_millis(100));
let now = Local::now();
let log_path = temp_dir
.path()
.join(format!("text.log.{}", now.format("%Y%m%d")));
println!("Looking for text log file at: {}", log_path.display());
let content = std::fs::read_to_string(log_path).unwrap();
assert!(content.contains(&Local::now().format("%Y-%m-%d").to_string()));
assert!(content.contains("[INFO]"));
assert!(content.contains("[Thread-"));
assert!(content.contains("Test message"));
}
#[test]
fn test_time_rolling_details() {
let temp_dir = tempdir().unwrap();
let config = LoggerConfig {
level: LogLevel::Info,
dir: temp_dir.path().to_path_buf(),
filename: "time_roll.log".to_string(),
rolling_policy: crate::config::RollingPolicy::Time(TimeRollingConfig {
period: TimePeriod::Hourly,
keep_days: 1,
}),
format: LogFormat::Text(TextFormatConfig {
time_format: "%Y-%m-%d %H:%M:%S".to_string(),
show_level: true,
show_target: false,
show_thread_id: false,
show_file: false,
show_line: false,
}),
async_write: false,
split_by_level: false,
console: None,
};
let logger = Logger::new(config).unwrap();
for i in 0..100 {
logger
.write(LogLevel::Info, &format!("Message {}", i))
.unwrap();
if i % 10 == 0 {
thread::sleep(Duration::from_millis(100));
}
}
logger.flush().unwrap();
let files: Vec<_> = std::fs::read_dir(temp_dir.path())
.unwrap()
.filter_map(|e| e.ok())
.collect();
assert!(!files.is_empty(), "应该至少有一个日志文件");
}
#[test]
fn test_size_rolling_details() {
let temp_dir = tempdir().unwrap();
let config = LoggerConfig {
level: LogLevel::Info,
dir: temp_dir.path().to_path_buf(),
filename: "size_roll.log".to_string(),
rolling_policy: crate::config::RollingPolicy::Size(SizeRollingConfig {
max_size: 1024, max_files: 3,
}),
format: LogFormat::Text(TextFormatConfig {
time_format: "%Y-%m-%d %H:%M:%S".to_string(),
show_level: true,
show_target: false,
show_thread_id: false,
show_file: false,
show_line: false,
}),
async_write: false,
split_by_level: false,
console: None,
};
let logger = Logger::new(config).unwrap();
let long_message = "A".repeat(100);
for i in 0..50 {
logger
.write(LogLevel::Info, &format!("{} - {}", i, long_message))
.unwrap();
}
logger.flush().unwrap();
let files: Vec<_> = std::fs::read_dir(temp_dir.path())
.unwrap()
.filter_map(|e| e.ok())
.collect();
assert!(files.len() <= 3, "文件数量不应超过max_files设置");
}
#[test]
fn test_performance_metrics() {
let temp_dir = tempdir().unwrap();
let config = LoggerConfig {
level: LogLevel::Info,
dir: temp_dir.path().to_path_buf(),
filename: "perf.log".to_string(),
rolling_policy: crate::config::RollingPolicy::Size(SizeRollingConfig {
max_size: 10 * 1024 * 1024, max_files: 5,
}),
format: LogFormat::Text(TextFormatConfig {
time_format: "%Y-%m-%d %H:%M:%S".to_string(),
show_level: true,
show_target: false,
show_thread_id: false,
show_file: false,
show_line: false,
}),
async_write: true,
split_by_level: false,
console: None,
};
let logger = Logger::new(config).unwrap();
let start = std::time::Instant::now();
for i in 0..100_000 {
logger
.write(LogLevel::Info, &format!("Performance test message {}", i))
.unwrap();
}
logger.flush().unwrap();
let single_thread_duration = start.elapsed();
let single_thread_throughput = 100_000.0 / single_thread_duration.as_secs_f64();
let start = std::time::Instant::now();
let mut handles = vec![];
for t in 0..4 {
let logger = logger.clone();
let handle = thread::spawn(move || {
for i in 0..25_000 {
logger
.write(LogLevel::Info, &format!("Thread {} - message {}", t, i))
.unwrap();
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
logger.flush().unwrap();
let multi_thread_duration = start.elapsed();
let multi_thread_throughput = 100_000.0 / multi_thread_duration.as_secs_f64();
println!("\n性能测试结果:");
println!("1. 单线程性能:");
println!(" - 总时间: {:?}", single_thread_duration);
println!(" - 吞吐量: {:.2} 消息/秒", single_thread_throughput);
println!("\n2. 多线程性能 (4线程):");
println!(" - 总时间: {:?}", multi_thread_duration);
println!(" - 吞吐量: {:.2} 消息/秒", multi_thread_throughput);
println!(
" - 加速比: {:.2}x",
multi_thread_throughput / single_thread_throughput
);
}