use std::env;
use std::io::Write;
use anyhow::anyhow;
use fern::Output;
use indicatif::{ProgressBar, WeakProgressBar};
use log::{error, info};
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use crate::cli::VerbosityLevel;
#[derive(Debug)]
pub struct ProgressHandler<T: Write + Send>(T);
static CURRENT_PROGRESS_BAR: Lazy<RwLock<Option<WeakProgressBar>>> =
Lazy::new(|| RwLock::new(None));
impl<T: Write + Send> ProgressHandler<T> {
fn handle<F: FnOnce(&mut Self) -> R, R>(&mut self, inner_function: F) -> R {
let handle = get_progress_bar();
match handle {
Some(pb) => pb.suspend(|| inner_function(self)),
None => inner_function(self),
}
}
pub fn new(pipe: T) -> Self {
Self(pipe)
}
}
impl<T: Write + Send + 'static> ProgressHandler<T> {
pub fn into_output(self) -> Output {
let boxed: Box<dyn Write + Send + 'static> = Box::new(self);
boxed.into()
}
}
impl<T: Write + Send> Write for ProgressHandler<T> {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.handle(|this| this.0.write(buf))
}
#[inline]
fn flush(&mut self) -> std::io::Result<()> {
self.handle(|this| this.0.flush())
}
}
pub(crate) fn set_progress_bar(pb: Option<WeakProgressBar>) {
*CURRENT_PROGRESS_BAR.write() = pb;
}
pub(crate) fn get_progress_bar() -> Option<ProgressBar> {
return CURRENT_PROGRESS_BAR.read().as_ref()?.upgrade();
}
pub(crate) fn log_error(err: &anyhow::Error) {
error!("Error occurred: {}", err);
err.chain()
.skip(1)
.for_each(|cause| error!(" caused by: {}", cause));
}
pub(crate) fn initialize_logging(
verbosity: VerbosityLevel,
quiet_mode: bool,
) -> Result<(), anyhow::Error> {
let mut unknown_log_filter_level = None;
let log_filter_level = if quiet_mode {
log::LevelFilter::Off
} else {
verbosity.into_filter().unwrap_or_else(|| {
if let Some(log_level) = env::var_os("RUST_LOG") {
let log_level = log_level.to_string_lossy().to_ascii_lowercase();
match log_level.as_str() {
"off" => log::LevelFilter::Off,
"error" => log::LevelFilter::Error,
"warn" => log::LevelFilter::Warn,
"info" => log::LevelFilter::Info,
"debug" => log::LevelFilter::Debug,
"trace" => log::LevelFilter::Trace,
_ => {
unknown_log_filter_level = Some(log_level);
log::LevelFilter::Info
}
}
} else {
log::LevelFilter::Info
}
})
};
if matches!(verbosity, VerbosityLevel::None) {
build_logger(log_filter_level, |out, message, record| {
out.finish(format_args!(
"[{}][{}] {}",
chrono::Local::now().format("%T%.3f"),
record.level(),
message
))
})?;
} else {
build_logger(log_filter_level, |out, message, record| {
out.finish(format_args!(
"[{}][{}][{}] {}",
chrono::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Micros, false),
record.target(),
record.level(),
message
))
})?;
}
if let Some(filter_level) = unknown_log_filter_level {
error!(
"Unknown log filter level '{}' defined in 'RUST_LOG' env variable, using INFO instead.",
filter_level
);
}
Ok(())
}
fn build_logger<F>(log_filter_level: log::LevelFilter, formatter: F) -> Result<(), anyhow::Error>
where
F: Fn(fern::FormatCallback, &std::fmt::Arguments, &log::Record) + Sync + Send + 'static,
{
fern::Dispatch::new()
.format(formatter)
.level(log_filter_level)
.chain(ProgressHandler::new(std::io::stdout()).into_output())
.apply()
.map_err(|e| anyhow!("Unable to apply logger configuration ({:?})", e))
}
pub(crate) fn log_program_info() {
info!(
"{} v{} ({})",
std::env::args()
.next()
.unwrap_or_else(|| "splashsurf".to_string()),
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_NAME")
);
let cmd_line: String = {
let mut cmd_line = String::new();
for arg in env::args() {
cmd_line.push_str(&arg);
cmd_line.push(' ');
}
cmd_line.pop();
cmd_line
};
info!("Called with command line: {}", cmd_line);
}