use crate::dirs::{LogsDir, PlatformPath};
use clap::{ArgAction, Args, ValueEnum};
use reth_tracing::{
tracing_subscriber::filter::Directive, FileInfo, FileWorkerGuard, LayerInfo, LogFormat,
RethTracer, Tracer,
};
use std::{fmt, fmt::Display};
use tracing::{level_filters::LevelFilter, Level};
const MB_TO_BYTES: u64 = 1024 * 1024;
#[derive(Debug, Args)]
#[command(next_help_heading = "Logging")]
pub struct LogArgs {
#[arg(long = "log.stdout.format", value_name = "FORMAT", global = true, default_value_t = LogFormat::Terminal)]
pub log_stdout_format: LogFormat,
#[arg(long = "log.stdout.filter", value_name = "FILTER", global = true, default_value = "")]
pub log_stdout_filter: String,
#[arg(long = "log.file.format", value_name = "FORMAT", global = true, default_value_t = LogFormat::Terminal)]
pub log_file_format: LogFormat,
#[arg(long = "log.file.filter", value_name = "FILTER", global = true, default_value = "debug")]
pub log_file_filter: String,
#[arg(long = "log.file.directory", value_name = "PATH", global = true, default_value_t)]
pub log_file_directory: PlatformPath<LogsDir>,
#[arg(long = "log.file.max-size", value_name = "SIZE", global = true, default_value_t = 200)]
pub log_file_max_size: u64,
#[arg(long = "log.file.max-files", value_name = "COUNT", global = true, default_value_t = 5)]
pub log_file_max_files: usize,
#[arg(long = "log.journald", global = true)]
pub journald: bool,
#[arg(
long = "log.journald.filter",
value_name = "FILTER",
global = true,
default_value = "error"
)]
pub journald_filter: String,
#[arg(
long,
value_name = "COLOR",
global = true,
default_value_t = ColorMode::Always
)]
pub color: ColorMode,
#[command(flatten)]
pub verbosity: Verbosity,
}
impl LogArgs {
fn layer(&self, format: LogFormat, filter: String, use_color: bool) -> LayerInfo {
LayerInfo::new(
format,
self.verbosity.directive().to_string(),
filter,
use_color.then(|| self.color.to_string()),
)
}
fn file_info(&self) -> FileInfo {
FileInfo::new(
self.log_file_directory.clone().into(),
self.log_file_max_size * MB_TO_BYTES,
self.log_file_max_files,
)
}
pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
let mut tracer = RethTracer::new();
let stdout = self.layer(self.log_stdout_format, self.log_stdout_filter.clone(), true);
tracer = tracer.with_stdout(stdout);
if self.journald {
tracer = tracer.with_journald(self.journald_filter.clone());
}
if self.log_file_max_files > 0 {
let info = self.file_info();
let file = self.layer(self.log_file_format, self.log_file_filter.clone(), false);
tracer = tracer.with_file(file, info);
}
let guard = tracer.init()?;
Ok(guard)
}
}
#[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq)]
pub enum ColorMode {
Always,
Auto,
Never,
}
impl Display for ColorMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Always => write!(f, "always"),
Self::Auto => write!(f, "auto"),
Self::Never => write!(f, "never"),
}
}
}
#[derive(Debug, Copy, Clone, Args)]
#[command(next_help_heading = "Display")]
pub struct Verbosity {
#[arg(short, long, action = ArgAction::Count, global = true, default_value_t = 3, verbatim_doc_comment, help_heading = "Display")]
verbosity: u8,
#[arg(long, alias = "silent", short = 'q', global = true, help_heading = "Display")]
quiet: bool,
}
impl Verbosity {
pub fn directive(&self) -> Directive {
if self.quiet {
LevelFilter::OFF.into()
} else {
let level = match self.verbosity - 1 {
0 => Level::ERROR,
1 => Level::WARN,
2 => Level::INFO,
3 => Level::DEBUG,
_ => Level::TRACE,
};
level.into()
}
}
}