#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::implicit_return, clippy::missing_docs_in_private_items)]
use std::{
env, fs,
fs::{FileType, OpenOptions},
io,
os::unix,
path::PathBuf,
time::{Duration, Instant},
};
use chrono::Utc;
use color_eyre::eyre::{bail, Result};
use displaydoc::Display;
use log::{debug, info, trace};
use slog::{o, Drain, Duplicate, FnValue, LevelFilter, Logger};
use thiserror::Error;
use up_rs::opts::Color;
const IGNORED_ENV_VARS: [&str; 1] = [
"LS_COLORS",
];
fn main() -> Result<()> {
let now = Instant::now();
color_eyre::install()?;
let opts = up_rs::opts::parse();
let stderr_decorator_builder = slog_term::TermDecorator::new().stderr();
let stderr_decorator = match &opts.color {
Color::Auto => stderr_decorator_builder,
Color::Always => stderr_decorator_builder.force_color(),
Color::Never => stderr_decorator_builder.force_plain(),
}
.build();
let stderr_drain = slog_term::CompactFormat::new(stderr_decorator)
.build()
.fuse();
let stderr_async_drain = slog_async::Async::new(stderr_drain).build().fuse();
let stderr_level_filter = LevelFilter::new(stderr_async_drain, opts.log_level);
let LogPaths {
log_path,
log_path_link,
log_file,
} = get_log_path_file(opts.up_dir.as_ref())
.map_err(|e| MainError::LogFileSetupFailed { source: e })?;
let file_decorator = slog_term::PlainSyncDecorator::new(log_file);
let file_drain = slog_term::FullFormat::new(file_decorator).build().fuse();
let log_kv_pairs = o!("place" =>
FnValue(move |info| {
format!("{}:{} {}",
info.file(),
info.line(),
info.module(),
)
})
);
let file_level_filter = LevelFilter::new(file_drain, opts.file_log_level);
let root_logger = Logger::root(
Duplicate::new(stderr_level_filter, file_level_filter).fuse(),
log_kv_pairs,
);
let _guard = slog_scope::set_global_logger(root_logger);
slog_stdlog::init()?;
trace!("Starting up.");
debug!("Writing full logs to {log_path_link:?} (symlink to '{log_path:?}')",);
trace!("Received args: {opts:#?}");
trace!(
"Current env: {:?}",
env::vars()
.filter(|(k, _v)| !IGNORED_ENV_VARS.contains(&k.as_str()))
.collect::<Vec<_>>()
);
up_rs::run(opts)?;
let now_elapsed = now.elapsed();
if now_elapsed > Duration::from_secs(10) {
info!("Up-rs ran successfully in {now_elapsed:?}");
} else {
debug!("Up-rs ran successfully in {now_elapsed:?}");
}
trace!("Finished up.");
Ok(())
}
#[derive(Debug)]
struct LogPaths {
log_path: PathBuf,
log_file: fs::File,
log_path_link: PathBuf,
}
fn get_log_path_file(up_dir_opt: Option<&PathBuf>) -> Result<LogPaths> {
let mut log_dir = up_rs::get_up_dir(up_dir_opt);
log_dir.push("logs");
fs::create_dir_all(&log_dir).map_err(|e| MainError::CreateDirError {
path: log_dir.clone(),
source: e,
})?;
let log_path_link = log_dir.as_path().join("up-rs_latest.log");
let mut log_path = log_dir;
log_path.push(format!("up-rs_{}.log", Utc::now().to_rfc3339()));
if log_path_link.exists() || log_path_link.symlink_metadata().is_ok() {
let log_path_link_file_type = log_path_link.symlink_metadata()?.file_type();
if log_path_link_file_type.is_symlink() {
fs::remove_file(&log_path_link).map_err(|e| MainError::DeleteError {
path: log_path_link.clone(),
source: e,
})?;
} else {
bail!(MainError::NotSymlinkError {
path: log_path_link,
file_type: log_path_link_file_type
});
}
}
unix::fs::symlink(&log_path, &log_path_link).map_err(|e| MainError::SymlinkError {
link_path: log_path_link.clone(),
src_path: log_path.clone(),
source: e,
})?;
let log_file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&log_path)
.map_err(|e| MainError::LogFileOpenFailed {
path: log_path.clone(),
source: e,
})?;
Ok(LogPaths {
log_path,
log_file,
log_path_link,
})
}
#[derive(Error, Debug, Display)]
pub enum MainError {
LogFileSetupFailed {
source: color_eyre::eyre::Error,
},
SymlinkError {
link_path: PathBuf,
src_path: PathBuf,
source: io::Error,
},
LogFileOpenFailed { path: PathBuf, source: io::Error },
CreateDirError { path: PathBuf, source: io::Error },
DeleteError { path: PathBuf, source: io::Error },
NotSymlinkError { path: PathBuf, file_type: FileType },
}