use {
chrono::Local,
fehler::throws,
log::{info, LevelFilter, Log, Metadata, Record, SetLoggerError},
parse_display::FromStr as ParseFromStr,
std::{
fs::File,
io::{self, Write},
sync::{Arc, RwLock},
},
structopt::StructOpt,
thiserror::Error,
};
const fn parse_log_level(occurrences: u64) -> LevelFilter {
match occurrences {
0 => LevelFilter::Warn,
1 => LevelFilter::Info,
2 => LevelFilter::Debug,
_ => LevelFilter::Trace,
}
}
#[throws(InitLoggerError)]
pub(crate) fn init(config: LogConfig) {
let logger = Logger::new(config.components.unwrap_or_default())?;
log::set_boxed_logger(Box::new(logger))?;
log::set_max_level(config.level);
info!("Logger initialized");
}
#[derive(Clone, Debug, ParseFromStr, PartialEq)]
pub(crate) enum LogComponent {
Starship,
}
struct Logger {
file: Arc<RwLock<File>>,
components: Vec<LogComponent>,
}
impl Logger {
#[throws(CreateLoggerError)]
fn new(components: Vec<LogComponent>) -> Self {
let log_filename = "paper.log".to_string();
Self {
file: Arc::new(RwLock::new(File::create(&log_filename).map_err(
|error| CreateLoggerError {
file: log_filename,
error,
},
)?)),
components,
}
}
}
impl Log for Logger {
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
if metadata.target().starts_with("starship") {
self.components.contains(&LogComponent::Starship)
} else {
true
}
}
fn log(&self, record: &Record<'_>) {
if self.enabled(record.metadata()) {
if let Ok(mut file) = self.file.write() {
#[allow(unused_must_use)] {
writeln!(
file,
"{} [{}]: {}",
Local::now().format("%F %T"),
record.level(),
record.args()
);
}
}
}
}
fn flush(&self) {
if let Ok(mut file) = self.file.write() {
#[allow(unused_must_use)] {
file.flush();
}
}
}
}
#[derive(Clone, Debug, StructOpt)]
pub struct LogConfig {
#[structopt(long("log"), value_name("COMPONENT"), require_equals(true), possible_values(&["starship"]))]
components: Option<Vec<LogComponent>>,
#[structopt(short("v"), parse(from_occurrences = parse_log_level))]
level: LevelFilter,
}
impl Default for LogConfig {
#[inline]
fn default() -> Self {
LogConfig {
components: None,
level: LevelFilter::Warn,
}
}
}
#[derive(Debug, Error)]
pub enum InitLoggerError {
#[error(transparent)]
Create(#[from] CreateLoggerError),
#[error("unable to set logger: {0}")]
Set(#[from] SetLoggerError),
}
#[derive(Debug, Error)]
#[error("unable to create log file `{file}`: {error}")]
pub struct CreateLoggerError {
file: String,
#[source]
error: io::Error,
}