#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::implicit_return, clippy::missing_docs_in_private_items)]
use camino::Utf8PathBuf;
use chrono::SecondsFormat;
use color_eyre::Section;
use color_eyre::SectionExt;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use color_eyre::eyre::eyre;
use indicatif::ProgressState;
use indicatif::ProgressStyle;
use std::env;
use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
use tracing::debug;
use tracing::info;
use tracing::level_filters::LevelFilter;
use tracing::trace;
use tracing::warn;
use tracing_error::ErrorLayer;
use tracing_indicatif::IndicatifLayer;
use tracing_indicatif::filter::IndicatifFilter;
use tracing_indicatif::filter::hide_indicatif_span_fields;
use tracing_subscriber::filter::EnvFilter;
use tracing_subscriber::fmt::format::DefaultFields;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::prelude::*;
use tracing_subscriber::util::SubscriberInitExt;
use up::opts::Opts;
use up::utils::errors::log_error;
use up::utils::files;
use up::utils::time::human_readable_duration;
const IGNORED_ENV_VARS: [&str; 1] = [
"LS_COLORS",
];
#[allow(clippy::cognitive_complexity)] fn main() -> Result<()> {
let now = Instant::now();
let mut opts = up::opts::parse();
color_eyre::config::HookBuilder::new()
.display_env_section(false)
.install()?;
let log_path = match set_up_logging(&opts) {
Ok((log_path, level_filter)) => {
opts.log = level_filter.to_string();
Some(log_path)
}
Err(e) => {
eprintln!(" WARN Failed to set up logging.{err}", err = log_error(&e));
None
}
};
trace!("Starting up.");
trace!("Received args: {opts:#?}");
trace!(
"Current env: {:?}",
env::vars()
.filter(|(k, _v)| !IGNORED_ENV_VARS.contains(&k.as_str()))
.collect::<Vec<_>>()
);
let mut result = up::run(opts);
if let Some(log_path) = log_path {
result = result.with_section(|| format!("{log_path}").header("Log file:"));
}
result?;
log_elapsed_time(now.elapsed());
Ok(())
}
fn set_up_logging(opts: &Opts) -> Result<(Utf8PathBuf, LevelFilter)> {
let indicatif_layer = IndicatifLayer::new()
.with_progress_style(
ProgressStyle::with_template(
"{color_start}{span_child_prefix}{span_fields} -- {span_name} {wide_msg} \
{elapsed_sec}{color_end}",
)
.unwrap()
.with_key(
"elapsed_sec",
|state: &ProgressState, writer: &mut dyn std::fmt::Write| {
let seconds = state.elapsed().as_secs();
let _ = writer.write_str(&format!("{seconds}s"));
},
)
.with_key(
"color_start",
|state: &ProgressState, writer: &mut dyn std::fmt::Write| {
let elapsed = state.elapsed();
if elapsed > Duration::from_secs(60) {
let _ = write!(writer, "\x1b[{}m", 1 + 30);
} else if elapsed > Duration::from_secs(10) {
let _ = write!(writer, "\x1b[{}m", 3 + 30);
}
},
)
.with_key(
"color_end",
|state: &ProgressState, writer: &mut dyn std::fmt::Write| {
if state.elapsed() > Duration::from_secs(10) {
let _ = write!(writer, "\x1b[0m");
}
},
),
)
.with_span_child_prefix_symbol("↳ ")
.with_span_child_prefix_indent(" ")
.with_span_field_formatter(hide_indicatif_span_fields(DefaultFields::new()))
.with_max_progress_bars(
20,
Some(ProgressStyle::with_template(
"...and {pending_progress_bars} more not shown above.",
)?),
);
let stderr_log = tracing_subscriber::fmt::layer()
.compact()
.with_target(false)
.without_time()
.with_writer(indicatif_layer.get_stderr_writer());
let log_path = files::log_dir()?.join(format!(
"up_{}.log",
opts.start_time
.to_rfc3339_opts(SecondsFormat::AutoSi, true)
.replace(':', "_")
));
let log_file = files::create(&log_path, None).wrap_err("Failed to create log file.")?;
let stderr_envfilter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.parse_lossy(&opts.log);
let log_filter = stderr_envfilter
.max_level_hint()
.ok_or_else(|| eyre!("Failed to work out the max level hint for {}", &opts.log))?;
let file_envfilter = EnvFilter::builder()
.with_default_directive(LevelFilter::TRACE.into())
.parse_lossy("up=trace");
let file_log = tracing_subscriber::fmt::layer()
.with_writer(Arc::new(log_file))
.with_target(true)
.with_file(true)
.with_line_number(true)
.pretty()
.with_ansi(false);
tracing_subscriber::registry()
.with(file_log.with_filter(file_envfilter))
.with(stderr_log.with_filter(stderr_envfilter))
.with(indicatif_layer.with_filter(IndicatifFilter::new(true)))
.with(ErrorLayer::default())
.init();
debug!("Writing trace logs to {log_path:?}");
Ok((log_path, log_filter))
}
fn log_elapsed_time(now_elapsed: Duration) {
debug!("Up ran successfully in {now_elapsed:?}");
if now_elapsed > Duration::from_secs(10) {
info!(
"Up ran successfully in {}",
human_readable_duration(now_elapsed).unwrap_or_else(|e| {
debug!(
"Failed to format duration as human-readable: {err}",
err = log_error(&e)
);
format!("{now_elapsed:?}")
}),
);
}
}