use crate::{
filters::Filters,
options::{Options, StyleConfig, TimeConfig},
};
use std::{io::Write, path::Path, sync::Mutex};
pub struct FileLogger<W: Send + 'static> {
options: Options,
filters: Filters,
path: Option<std::path::PathBuf>,
write: Mutex<W>,
}
impl FileLogger<std::fs::File> {
pub fn truncate(
options: impl Into<Options>,
path: impl AsRef<Path>,
) -> Result<Self, crate::Error> {
let options = options.into();
let path = path.as_ref();
std::fs::OpenOptions::new()
.create(true)
.write(true)
.open(path)
.map(|file| {
let mut this = Self::new(options, file);
this.path.replace(path.into());
this
})
.map_err(crate::Error::FileLogger)
}
pub fn append(
options: impl Into<Options>,
path: impl AsRef<Path>,
) -> Result<Self, crate::Error> {
let options = options.into();
let path = path.as_ref();
std::fs::OpenOptions::new()
.create(true)
.append(true)
.write(true)
.open(&path)
.map(|file| {
let mut this = Self::new(options, file);
this.path.replace(path.into());
this
})
.map_err(crate::Error::FileLogger)
}
pub fn timestamp(
options: impl Into<Options>,
path: impl AsRef<Path>,
) -> Result<Self, crate::Error> {
let options = options.into();
fn io_err(reason: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> crate::Error {
crate::Error::FileLogger(std::io::Error::new(std::io::ErrorKind::Other, reason))
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(io_err)?
.as_secs();
let path = path.as_ref();
let file_stem = path
.file_stem()
.ok_or_else(|| io_err("no file stem provided"))?;
let file_ext = path
.extension()
.and_then(|s| s.to_str())
.unwrap_or_default();
let new_name = file_stem
.to_str()
.map(|s| {
let mut file = s.to_string() + "_" + &now.to_string();
if !file_ext.is_empty() {
file.push('.');
file.push_str(file_ext);
}
file
})
.map(std::path::PathBuf::from)
.ok_or_else(|| io_err("cannot create new name"))?;
let path = path.with_file_name(new_name);
std::fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&path)
.map(|file| {
let mut this = Self::new(options, file);
this.path.replace(path);
this
})
.map_err(crate::Error::FileLogger)
}
pub fn file_name(&self) -> Option<&Path> {
self.path.as_deref()
}
}
impl<W: Write + Send + 'static> FileLogger<W> {
pub fn init(self) -> Result<(), crate::Error> {
crate::init(self)
}
pub fn new(options: impl Into<Options>, writer: W) -> Self {
let options = options.into();
Self {
options,
filters: Filters::from_env(),
write: Mutex::new(writer),
path: None,
}
}
fn print(&self, record: &log::Record<'_>) {
let Options {
time: timestamp,
style,
..
} = &self.options;
let mut file = self.write.lock().unwrap();
let _ = write!(file, "{:<5}", record.level());
match timestamp {
TimeConfig::None => {}
TimeConfig::Unix => {
let elapsed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("time should not go backwards");
let _ = write!(file, " {:04}", elapsed.as_secs(),);
}
TimeConfig::Relative(start) => {
let elapsed = start.elapsed();
let _ = write!(
file,
" {:04}.{:09}s",
elapsed.as_secs(),
elapsed.subsec_nanos()
);
}
TimeConfig::Timing(inner) => {
let inner = &mut *inner.lock().unwrap();
if let Some(start) = &*inner {
let elapsed = start.elapsed();
let _ = write!(
file,
" {:04}.{:09}s",
elapsed.as_secs(),
elapsed.subsec_nanos()
);
} else {
let _ = write!(file, " {:04}.{:09}s", 0, 0);
}
inner.replace(std::time::Instant::now());
}
#[cfg(feature = "time")]
TimeConfig::DateTime(format) => {
if let Ok(now) = time::OffsetDateTime::now_utc().format(&format) {
let _ = write!(file, " {}", now);
}
}
}
let _ = write!(file, " [");
let _ = write!(file, "{}", record.target());
let _ = write!(file, "]");
if let StyleConfig::MultiLine = style {
let _ = writeln!(file);
let _ = write!(file, "⤷");
}
let _ = write!(file, " {}", record.args());
let _ = writeln!(file);
}
}
impl<W: Write + Send + 'static> log::Log for FileLogger<W> {
#[inline]
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
self.filters.is_enabled(metadata)
}
#[inline]
fn log(&self, record: &log::Record<'_>) {
if self.enabled(record.metadata()) {
self.print(record);
}
}
#[inline]
fn flush(&self) {
let _ = self.write.lock().unwrap().flush();
}
}