use log::LevelFilter;
use std::collections::HashSet;
use std::error::Error;
use std::fmt::Display;
use std::path::PathBuf;
use std::sync::Mutex;
use crate::logger::Logger;
use crate::target::OutputTargetImpl;
use crate::LOGGER_INSTANCE;
#[derive(Debug)]
pub struct LoggerBuilder {
max_log_level: LevelFilter,
always_show_module_path: bool,
output_target: Option<OutputTargetImpl>,
module_blacklist: HashSet<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OutputTarget {
Stderr,
#[cfg(windows)]
WinDbg,
File(PathBuf),
}
#[derive(Debug)]
pub enum SetTargetError {
FileOpenError {
builder: LoggerBuilder,
path: PathBuf,
error: std::io::Error,
},
}
impl From<SetTargetError> for LoggerBuilder {
fn from(value: SetTargetError) -> Self {
match value {
SetTargetError::FileOpenError { builder, .. } => builder,
}
}
}
impl Error for SetTargetError {}
impl Display for SetTargetError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SetTargetError::FileOpenError {
builder: _,
path,
error,
} => {
write!(f, "Could not open '{}' ({})", path.display(), error)
}
}
}
}
#[derive(Debug)]
pub struct SetLoggerError(());
impl Error for SetLoggerError {}
impl Display for SetLoggerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Tried to set a global logger after one has already been configured"
)
}
}
impl LoggerBuilder {
pub fn new(max_log_level: LevelFilter) -> Self {
Self {
max_log_level,
always_show_module_path: false,
output_target: None,
module_blacklist: HashSet::new(),
}
}
pub fn build_global(self) -> Result<(), SetLoggerError> {
unsafe {
time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound)
};
let local_time_offset = time::UtcOffset::current_local_offset().unwrap_or_else(|_| {
eprintln!("Could not get the local time offset, defaulting to UTC");
time::UtcOffset::UTC
});
unsafe {
time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Sound)
};
let max_log_level = self.max_log_level;
let always_show_module_path = self.always_show_module_path;
let logger = Logger {
max_log_level,
always_show_module_path,
output_target: Mutex::new(
self.output_target
.unwrap_or_else(OutputTargetImpl::default_from_environment),
),
local_time_offset,
module_blacklist: self.module_blacklist,
};
match LOGGER_INSTANCE.try_insert(logger) {
Ok(logger_instance) => {
log::set_logger(logger_instance).map_err(|_| SetLoggerError(()))?;
log::set_max_level(max_log_level);
Ok(())
}
Err(_) => Err(SetLoggerError(())),
}
}
pub fn always_show_module_path(mut self) -> Self {
self.always_show_module_path = true;
self
}
pub fn filter_crate(mut self, crate_name: impl Into<String>) -> Self {
self.module_blacklist.insert(crate_name.into());
self
}
pub fn filter_module(mut self, crate_name: impl Into<String>) -> Self {
self.module_blacklist.insert(crate_name.into());
self
}
#[allow(clippy::result_large_err)]
pub fn with_output_target(mut self, target: OutputTarget) -> Result<Self, SetTargetError> {
self.output_target = Some(match target {
OutputTarget::Stderr => OutputTargetImpl::new_stderr(),
#[cfg(windows)]
OutputTarget::WinDbg => OutputTargetImpl::new_windbg(),
OutputTarget::File(path) => match OutputTargetImpl::new_file_path(&path) {
Ok(target) => target,
Err(error) => {
return Err(SetTargetError::FileOpenError {
builder: self,
path,
error,
})
}
},
});
Ok(self)
}
}