use crate::helpers::{DynamicFormatter, LogWriter};
use anyhow::{Result, bail};
use crossterm::tty::IsTty;
use std::{
fs::File,
io,
path::Path,
str::FromStr,
sync::{Arc, atomic::AtomicBool},
};
use tokio::sync::mpsc;
use tracing_subscriber::{
EnvFilter,
layer::{Layer, SubscriberExt},
util::SubscriberInitExt,
};
fn parse_log_verbosity(verbosity: u8) -> Result<EnvFilter> {
let default_log_str = match verbosity {
0 => "info",
1 => "debug",
2.. => "trace",
};
let filter = EnvFilter::from_str(default_log_str).unwrap();
let filter = if verbosity >= 2 {
filter.add_directive("snarkos_node_sync=trace".parse().unwrap())
} else if verbosity >= 1 {
filter.add_directive("snarkos_node_sync=debug".parse().unwrap())
} else {
filter
};
let filter = if verbosity >= 3 {
filter.add_directive("snarkos_node_bft=trace".parse().unwrap())
} else if verbosity >= 1 {
filter.add_directive("snarkos_node_bft=debug".parse().unwrap())
} else {
filter
};
let filter = if verbosity >= 4 {
filter.add_directive("snarkos_node_bft::gateway=trace".parse().unwrap())
} else if verbosity >= 1 {
filter.add_directive("snarkos_node_bft::gateway=debug".parse().unwrap())
} else {
filter
};
let filter = if verbosity >= 4 {
filter
.add_directive("mio=warn".parse().unwrap())
.add_directive("tokio_util=warn".parse().unwrap())
.add_directive("hyper=warn".parse().unwrap())
.add_directive("reqwest=warn".parse().unwrap())
.add_directive("want=warn".parse().unwrap())
.add_directive("h2=warn".parse().unwrap())
.add_directive("tower=warn".parse().unwrap())
.add_directive("axum=warn".parse().unwrap())
.add_directive("ureq=warn".parse().unwrap())
.add_directive("rustls=warn".parse().unwrap())
} else {
filter
.add_directive("mio=off".parse().unwrap())
.add_directive("tokio_util=off".parse().unwrap())
.add_directive("hyper=off".parse().unwrap())
.add_directive("reqwest=off".parse().unwrap())
.add_directive("want=off".parse().unwrap())
.add_directive("h2=off".parse().unwrap())
.add_directive("tower=off".parse().unwrap())
.add_directive("axum=off".parse().unwrap())
.add_directive("ureq=off".parse().unwrap())
.add_directive("rustls=off".parse().unwrap())
};
let filter = if verbosity >= 5 {
filter.add_directive("snarkos_node_router=trace".parse().unwrap())
} else if verbosity >= 1 {
filter.add_directive("snarkos_node_router=debug".parse().unwrap())
} else {
filter
};
let filter = if verbosity >= 6 {
filter.add_directive("snarkos_node_tcp=trace".parse().unwrap())
} else {
filter.add_directive("snarkos_node_tcp=off".parse().unwrap())
};
Ok(filter)
}
fn parse_log_filter(filter_str: &str) -> Result<EnvFilter> {
EnvFilter::from_str(filter_str).map_err(|err| err.into())
}
pub fn initialize_logger<P: AsRef<Path>>(
verbosity: u8,
log_filter: &Option<String>,
nodisplay: bool,
logfile: P,
shutdown: Arc<AtomicBool>,
) -> Result<mpsc::Receiver<Vec<u8>>> {
let [stdout_filter, logfile_filter] = std::array::from_fn(|_| {
if let Some(filter) = log_filter { parse_log_filter(filter) } else { parse_log_verbosity(verbosity) }
});
let Some(logfile_dir) = logfile.as_ref().parent() else { bail!("Root directory passed as a logfile") };
if !logfile_dir.exists()
&& let Err(err) = std::fs::create_dir_all(logfile_dir)
{
bail!("Failed to create a directory: '{}' ({err})", logfile_dir.display());
}
let logfile = match File::options().append(true).create(true).open(logfile) {
Ok(logfile) => logfile,
Err(err) => bail!("Failed to open the file for writing logs: {err}"),
};
let (log_sender, log_receiver) = mpsc::channel(1024);
let log_sender = match nodisplay {
true => None,
false => Some(log_sender),
};
let show_target = verbosity > 2 || log_filter.is_some();
let layered = tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::Layer::default()
.with_ansi(log_sender.is_none() && io::stdout().is_tty())
.with_writer(move || LogWriter::new(&log_sender))
.with_target(show_target)
.event_format(DynamicFormatter::new(shutdown))
.with_filter(stdout_filter?),
)
.with(
tracing_subscriber::fmt::Layer::default()
.with_ansi(false)
.with_writer(logfile)
.with_target(show_target)
.with_filter(logfile_filter?),
);
#[cfg(feature = "tokio_console")]
let layered = layered.with(console_subscriber::spawn());
let _ = layered.try_init();
Ok(log_receiver)
}
pub fn initialize_terminal_logger(verbosity: u8) -> Result<()> {
let stdout_filter = parse_log_verbosity(verbosity)?;
let show_target = verbosity > 2;
let _ = tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::Layer::default()
.with_ansi(io::stdout().is_tty())
.with_target(show_target)
.event_format(DynamicFormatter::new(Arc::new(AtomicBool::new(false))))
.with_filter(stdout_filter),
)
.try_init();
Ok(())
}
pub fn welcome_message() -> String {
use colored::Colorize;
let mut output = String::new();
output += &r#"
╦╬╬╬╬╬╦
╬╬╬╬╬╬╬╬╬ ▄▄▄▄ ▄▄▄
╬╬╬╬╬╬╬╬╬╬╬ ▐▓▓▓▓▌ ▓▓▓
╬╬╬╬╬╬╬╬╬╬╬╬╬ ▐▓▓▓▓▓▓▌ ▓▓▓ ▄▄▄▄▄▄ ▄▄▄▄▄▄
╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬ ▐▓▓▓ ▓▓▓▌ ▓▓▓ ▄▓▓▀▀▀▀▓▓▄ ▐▓▓▓▓▓▓▓▓▌
╬╬╬╬╬╬╬╜ ╙╬╬╬╬╬╬╬ ▐▓▓▓▌ ▐▓▓▓▌ ▓▓▓ ▐▓▓▓▄▄▄▄▓▓▓▌ ▐▓▓▓ ▓▓▓▌
╬╬╬╬╬╬╣ ╠╬╬╬╬╬╬ ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▐▓▓▀▀▀▀▀▀▀▀▘ ▐▓▓▓ ▓▓▓▌
╬╬╬╬╬╬╣ ╠╬╬╬╬╬╬ ▓▓▓▓▌ ▐▓▓▓▓ ▓▓▓ ▀▓▓▄▄▄▄▓▓▀ ▐▓▓▓▓▓▓▓▓▌
╬╬╬╬╬╬╣ ╠╬╬╬╬╬╬ ▝▀▀▀▀ ▀▀▀▀▘ ▀▀▀ ▀▀▀▀▀▀ ▀▀▀▀▀▀
╚╬╬╬╬╬╩ ╩╬╬╬╬╩
"#
.white()
.bold();
output += &"👋 Welcome to Aleo! We thank you for running a node and supporting privacy.\n".bold();
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn log_filter() {
let result = parse_log_filter("=");
assert!(result.is_err(), "must disallow invalid log filter");
let result = parse_log_filter("snarkos=trace");
assert!(result.is_ok(), "must allow valid log filter");
}
}