use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{sync_channel, SyncSender};
use std::sync::Mutex;
use std::thread;
use crate::format::{Formatter, Record};
pub type DispatchResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
pub trait Dispatcher: Send + Sync + std::fmt::Debug {
fn dispatch(&self, record: &Record) -> DispatchResult;
}
#[derive(Debug)]
pub struct StdoutDispatcher<F: Formatter> {
formatter: F,
}
impl<F: Formatter> StdoutDispatcher<F> {
#[must_use]
pub const fn new(formatter: F) -> Self {
Self { formatter }
}
}
impl<F: Formatter + Send + Sync + std::fmt::Debug> Dispatcher for StdoutDispatcher<F> {
fn dispatch(&self, record: &Record) -> DispatchResult {
println!("{}", self.formatter.format(record));
Ok(())
}
}
#[derive(Debug)]
pub struct StderrDispatcher<F: Formatter> {
formatter: F,
}
impl<F: Formatter> StderrDispatcher<F> {
#[must_use]
pub const fn new(formatter: F) -> Self {
Self { formatter }
}
}
impl<F: Formatter + Send + Sync + std::fmt::Debug> Dispatcher for StderrDispatcher<F> {
fn dispatch(&self, record: &Record) -> DispatchResult {
eprintln!("{}", self.formatter.format(record));
Ok(())
}
}
#[derive(Debug)]
pub struct AsyncDispatcher {
sender: SyncSender<Record>,
}
impl AsyncDispatcher {
#[must_use]
pub fn new(dispatcher: Box<dyn Dispatcher + Send + Sync>, buffer_size: usize) -> Self {
let (sender, receiver) = sync_channel::<Record>(buffer_size);
thread::spawn(move || {
while let Ok(record) = receiver.recv() {
dispatcher.dispatch(&record).ok();
}
});
Self { sender }
}
}
impl Dispatcher for AsyncDispatcher {
fn dispatch(&self, record: &Record) -> DispatchResult {
let _ = self.sender.send(record.clone());
Ok(())
}
}
#[derive(Debug)]
pub struct RollingFileDispatcher<F: Formatter> {
file_path: PathBuf,
max_size: u64,
inner: Mutex<File>,
formatter: F,
}
impl<F: Formatter> RollingFileDispatcher<F> {
pub fn new<P: AsRef<Path>>(path: P, max_size: u64, formatter: F) -> Result<Self, std::io::Error> {
let file = OpenOptions::new().append(true).create(true).open(&path)?;
Ok(Self {
file_path: path.as_ref().to_path_buf(),
max_size,
inner: Mutex::new(file),
formatter,
})
}
fn rotate(&self, file: &mut File) -> std::io::Result<()> {
let backup_path = self.file_path.with_extension("log.old");
std::fs::rename(&self.file_path, backup_path)?;
*file = File::create(&self.file_path)?;
Ok(())
}
}
impl<F: Formatter + Send + Sync + std::fmt::Debug> Dispatcher for RollingFileDispatcher<F> {
fn dispatch(&self, record: &Record) -> DispatchResult {
let msg = self.formatter.format(record);
let mut file = self.inner.lock().map_err(|_| "Failed to acquire lock")?;
if let Ok(metadata) = file.metadata() {
if metadata.len() + msg.len() as u64 > self.max_size {
if let Err(e) = self.rotate(&mut file) {
eprintln!("Rotation failed: {e}");
}
}
}
writeln!(file, "{msg}")?;
file.flush()?;
drop(file);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::JsonFormatter;
use crate::Level;
use std::time::SystemTime;
fn create_test_record(level: Level, message: &str) -> Record {
Record {
level,
args: message.to_string(),
file: "test.rs",
line: 42,
module_path: "test_module",
timestamp: SystemTime::now(),
}
}
#[test]
fn test_stdout_dispatcher_dispatch() {
let record = create_test_record(Level::Info, "Testing stdout dispatcher");
let dispatcher = StdoutDispatcher::new(JsonFormatter);
dispatcher.dispatch(&record).ok();
}
#[test]
fn test_stderr_dispatcher_dispatch() {
let record = create_test_record(Level::Error, "Testing stderr dispatcher");
let dispatcher = StderrDispatcher::new(JsonFormatter);
dispatcher.dispatch(&record).ok();
}
#[test]
fn test_async_dispatcher_dispatch() {
let record = create_test_record(Level::Warn, "Testing async dispatcher");
let stdout = Box::new(StdoutDispatcher::new(JsonFormatter));
let async_dispatcher = AsyncDispatcher::new(stdout, 10);
async_dispatcher.dispatch(&record).ok();
}
}