Skip to content

Commit

Permalink
feature: loglevel value parser (#8573)
Browse files Browse the repository at this point in the history
Co-authored-by: Matthias Seitz <[email protected]>
  • Loading branch information
JackG-eth and mattsse committed Jun 5, 2024
1 parent 1e0d724 commit 7e60bc4
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 10 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/node-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ reth-primitives.workspace = true
reth-fs-util.workspace = true
reth-db = { workspace = true, features = ["mdbx"] }
reth-db-api.workspace = true
reth-storage-errors = { workspace = true, features = ["clap"] }
reth-storage-errors.workspace = true
reth-provider.workspace = true
reth-network = { workspace = true, features = ["serde"] }
reth-network-p2p.workspace = true
Expand Down
99 changes: 96 additions & 3 deletions crates/node-core/src/args/database.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
//! clap [Args](clap::Args) for database configuration

use crate::version::default_client_version;
use clap::Args;
use clap::{
builder::{PossibleValue, TypedValueParser},
error::ErrorKind,
Arg, Args, Command, Error,
};
use reth_storage_errors::db::LogLevel;

/// Parameters for database configuration
#[derive(Debug, Args, PartialEq, Eq, Default, Clone, Copy)]
#[command(next_help_heading = "Database")]
pub struct DatabaseArgs {
/// Database logging level. Levels higher than "notice" require a debug build.
#[arg(long = "db.log-level", value_enum)]
#[arg(long = "db.log-level", value_parser = LogLevelValueParser::default())]
pub log_level: Option<LogLevel>,
/// Open environment in exclusive/monopolistic mode. Makes it possible to open a database on an
/// NFS volume.
Expand All @@ -26,6 +30,44 @@ impl DatabaseArgs {
}
}

/// clap value parser for [`LogLevel`].
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
struct LogLevelValueParser;

impl TypedValueParser for LogLevelValueParser {
type Value = LogLevel;

fn parse_ref(
&self,
_cmd: &Command,
arg: Option<&Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, Error> {
let val =
value.to_str().ok_or_else(|| Error::raw(ErrorKind::InvalidUtf8, "Invalid UTF-8"))?;

val.parse::<LogLevel>().map_err(|err| {
let arg = arg.map(|a| a.to_string()).unwrap_or_else(|| "...".to_owned());
let possible_values = LogLevel::value_variants()
.iter()
.map(|v| format!("- {:?}: {}", v, v.help_message()))
.collect::<Vec<_>>()
.join("\n");
let msg = format!(
"Invalid value '{val}' for {arg}: {err}.\n Possible values:\n{possible_values}"
);
clap::Error::raw(clap::error::ErrorKind::InvalidValue, msg)
})
}

fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
let values = LogLevel::value_variants()
.iter()
.map(|v| PossibleValue::new(v.variant_name()).help(v.help_message()));
Some(Box::new(values))
}
}
#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -39,9 +81,60 @@ mod tests {
}

#[test]
fn test_parse_database_args() {
fn test_default_database_args() {
let default_args = DatabaseArgs::default();
let args = CommandParser::<DatabaseArgs>::parse_from(["reth"]).args;
assert_eq!(args, default_args);
}

#[test]
fn test_possible_values() {
// Initialize the LogLevelValueParser
let parser = LogLevelValueParser;

// Call the possible_values method
let possible_values: Vec<PossibleValue> = parser.possible_values().unwrap().collect();

// Expected possible values
let expected_values = vec![
PossibleValue::new("fatal")
.help("Enables logging for critical conditions, i.e. assertion failures"),
PossibleValue::new("error").help("Enables logging for error conditions"),
PossibleValue::new("warn").help("Enables logging for warning conditions"),
PossibleValue::new("notice")
.help("Enables logging for normal but significant condition"),
PossibleValue::new("verbose").help("Enables logging for verbose informational"),
PossibleValue::new("debug").help("Enables logging for debug-level messages"),
PossibleValue::new("trace").help("Enables logging for trace debug-level messages"),
PossibleValue::new("extra").help("Enables logging for extra debug-level messages"),
];

// Check that the possible values match the expected values
assert_eq!(possible_values.len(), expected_values.len());
for (actual, expected) in possible_values.iter().zip(expected_values.iter()) {
assert_eq!(actual.get_name(), expected.get_name());
assert_eq!(actual.get_help(), expected.get_help());
}
}

#[test]
fn test_command_parser_with_valid_log_level() {
let cmd =
CommandParser::<DatabaseArgs>::try_parse_from(["reth", "--db.log-level", "Debug"])
.unwrap();
assert_eq!(cmd.args.log_level, Some(LogLevel::Debug));
}

#[test]
fn test_command_parser_with_invalid_log_level() {
let result =
CommandParser::<DatabaseArgs>::try_parse_from(["reth", "--db.log-level", "invalid"]);
assert!(result.is_err());
}

#[test]
fn test_command_parser_without_log_level() {
let cmd = CommandParser::<DatabaseArgs>::try_parse_from(["reth"]).unwrap();
assert_eq!(cmd.args.log_level, None);
}
}
4 changes: 1 addition & 3 deletions crates/storage/errors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,5 @@ reth-primitives.workspace = true
reth-fs-util.workspace = true

thiserror.workspace = true
clap = { workspace = true, features = ["derive"], optional = true }

[features]
clap = ["dep:clap"]

65 changes: 63 additions & 2 deletions crates/storage/errors/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt::Display;
use std::{fmt::Display, str::FromStr};
use thiserror::Error;

/// Database error type.
Expand Down Expand Up @@ -103,7 +103,6 @@ pub enum DatabaseWriteOperation {

/// Database log level.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum LogLevel {
/// Enables logging for critical conditions, i.e. assertion failures.
Fatal,
Expand All @@ -122,3 +121,65 @@ pub enum LogLevel {
/// Enables logging for extra debug-level messages.
Extra,
}

impl LogLevel {
/// All possible variants of the `LogLevel` enum
pub const fn value_variants() -> &'static [Self] {
&[
Self::Fatal,
Self::Error,
Self::Warn,
Self::Notice,
Self::Verbose,
Self::Debug,
Self::Trace,
Self::Extra,
]
}

/// Static str reference to `LogLevel` enum, required for `Clap::Builder::PossibleValue::new()`
pub const fn variant_name(&self) -> &'static str {
match self {
Self::Fatal => "fatal",
Self::Error => "error",
Self::Warn => "warn",
Self::Notice => "notice",
Self::Verbose => "verbose",
Self::Debug => "debug",
Self::Trace => "trace",
Self::Extra => "extra",
}
}

/// Returns all variants descriptions
pub const fn help_message(&self) -> &'static str {
match self {
Self::Fatal => "Enables logging for critical conditions, i.e. assertion failures",
Self::Error => "Enables logging for error conditions",
Self::Warn => "Enables logging for warning conditions",
Self::Notice => "Enables logging for normal but significant condition",
Self::Verbose => "Enables logging for verbose informational",
Self::Debug => "Enables logging for debug-level messages",
Self::Trace => "Enables logging for trace debug-level messages",
Self::Extra => "Enables logging for extra debug-level messages",
}
}
}

impl FromStr for LogLevel {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"fatal" => Ok(Self::Fatal),
"error" => Ok(Self::Error),
"warn" => Ok(Self::Warn),
"notice" => Ok(Self::Notice),
"verbose" => Ok(Self::Verbose),
"debug" => Ok(Self::Debug),
"trace" => Ok(Self::Trace),
"extra" => Ok(Self::Extra),
_ => Err(format!("Invalid log level: {}", s)),
}
}
}

0 comments on commit 7e60bc4

Please sign in to comment.