use std::path::PathBuf;
use clap::{ArgAction, Args, ValueEnum};
use serde::{Deserialize, Serialize};
use tracing::level_filters::LevelFilter;
use crate::tracing::LogFormat;
#[derive(Args, Debug, Default, Serialize, Deserialize, Clone)]
pub struct LogArgs {
#[arg(
short = 'v',
global = true,
default_value = "3",
env = "KONA_NODE_LOG_LEVEL",
action = ArgAction::Count,
)]
pub level: u8,
#[arg(
long = "logs.stdout.quiet",
short = 'q',
global = true,
default_value = "false",
env = "KONA_NODE_STDOUT_LOG_QUIET"
)]
pub stdout_quiet: bool,
#[arg(
long = "logs.stdout.format",
default_value = "full",
env = "KONA_NODE_LOG_STDOUT_FORMAT"
)]
pub stdout_format: LogFormat,
#[arg(long = "logs.file.directory", env = "KONA_NODE_LOG_FILE_DIRECTORY")]
pub file_directory: Option<PathBuf>,
#[arg(long = "logs.file.format", default_value = "full", env = "KONA_NODE_LOG_FILE_FORMAT")]
pub file_format: LogFormat,
#[arg(
long = "logs.file.rotation",
default_value = "never",
env = "KONA_NODE_LOG_FILE_ROTATION"
)]
pub file_rotation: LogRotation,
}
#[derive(Debug, Clone, Serialize, Deserialize, ValueEnum, Default)]
#[serde(rename_all = "lowercase")]
pub enum LogRotation {
Minutely,
Hourly,
Daily,
#[default]
Never,
}
#[derive(Debug, Clone)]
pub struct FileLogConfig {
pub directory_path: PathBuf,
pub format: LogFormat,
pub rotation: LogRotation,
}
#[derive(Debug, Clone)]
pub struct StdoutLogConfig {
pub format: LogFormat,
}
#[derive(Debug, Clone)]
pub struct LogConfig {
pub global_level: LevelFilter,
pub stdout_logs: Option<StdoutLogConfig>,
pub file_logs: Option<FileLogConfig>,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
global_level: LevelFilter::INFO,
stdout_logs: Some(StdoutLogConfig { format: LogFormat::Full }),
file_logs: None,
}
}
}
impl From<LogArgs> for LogConfig {
fn from(args: LogArgs) -> Self {
Self::new(args)
}
}
impl LogConfig {
pub fn new(args: LogArgs) -> Self {
let level = match args.level {
1 => LevelFilter::ERROR,
2 => LevelFilter::WARN,
3 => LevelFilter::INFO,
4 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
};
let stdout_logs = if args.stdout_quiet {
None
} else {
Some(StdoutLogConfig { format: args.stdout_format })
};
let file_logs = args.file_directory.as_ref().map(|path| FileLogConfig {
directory_path: path.clone(),
format: args.file_format,
rotation: args.file_rotation,
});
Self { global_level: level, stdout_logs, file_logs }
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[derive(Parser, Debug)]
struct TestCli {
#[command(flatten)]
global: LogArgs,
}
#[test]
fn test_default_verbosity_level() {
let cli = TestCli::parse_from(["test_app"]);
assert_eq!(
cli.global.level, 3,
"Default verbosity should be 3 when no -v flag is present."
);
}
#[test]
fn test_verbosity_count() {
let cli_v1 = TestCli::parse_from(["test_app", "-v"]);
assert_eq!(cli_v1.global.level, 1, "Verbosity with a single -v should be 1.");
let cli_v3 = TestCli::parse_from(["test_app", "-vvv"]);
assert_eq!(cli_v3.global.level, 3, "Verbosity with -vvv should be 3.");
let cli_v5 = TestCli::parse_from(["test_app", "-vvvvv"]);
assert_eq!(cli_v5.global.level, 5, "Verbosity with -vvvvv should be 5.");
}
}