use clap::ValueEnum;
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum LogLevel {
Off,
Error,
Warn,
Info,
Debug,
Trace,
}
impl LogLevel {
#[must_use]
pub fn directive(self) -> String {
match self {
Self::Off => "off".to_string(),
Self::Error => "acme_proxy=error".to_string(),
Self::Warn => "acme_proxy=warn".to_string(),
Self::Info => "acme_proxy=info".to_string(),
Self::Debug => "acme_proxy=debug".to_string(),
Self::Trace => "acme_proxy=trace".to_string(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoggingPlan {
Silent,
Server,
Command,
}
pub fn plan_logging(
command: Option<&super::Command>,
level: Option<LogLevel>,
rust_log: Option<&str>,
) -> LoggingPlan {
match command {
None | Some(super::Command::Serve { .. }) => LoggingPlan::Server,
Some(super::Command::Completions { .. } | super::Command::Man) => LoggingPlan::Silent,
Some(_) => {
if level.is_some() || rust_log.is_some_and(|value| !value.is_empty()) {
LoggingPlan::Command
} else {
LoggingPlan::Silent
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing_subscriber::EnvFilter;
type PlanCase<'a> = (
&'a [&'a str],
Option<LogLevel>,
Option<&'a str>,
LoggingPlan,
);
fn command_of(argv: &[&str]) -> Option<super::super::Command> {
use clap::Parser;
super::super::Cli::try_parse_from(argv)
.expect("the fixture command line parses")
.command
}
#[test]
fn plan_logging_decides_who_gets_a_subscriber() {
let cases: Vec<PlanCase> = vec![
(&["acme-proxy"], None, None, LoggingPlan::Server),
(&["acme-proxy", "serve"], None, None, LoggingPlan::Server),
(
&["acme-proxy", "serve"],
Some(LogLevel::Debug),
None,
LoggingPlan::Server,
),
(
&["acme-proxy", "account", "list"],
None,
None,
LoggingPlan::Silent,
),
(
&["acme-proxy", "filter", "show"],
None,
None,
LoggingPlan::Silent,
),
(
&["acme-proxy", "audit", "list"],
None,
None,
LoggingPlan::Silent,
),
(
&["acme-proxy", "admin", "user", "list"],
None,
None,
LoggingPlan::Silent,
),
(
&["acme-proxy", "account", "list"],
Some(LogLevel::Debug),
None,
LoggingPlan::Command,
),
(
&["acme-proxy", "account", "list"],
Some(LogLevel::Off),
None,
LoggingPlan::Command,
),
(
&["acme-proxy", "account", "list"],
None,
Some("acme_proxy=debug"),
LoggingPlan::Command,
),
(
&["acme-proxy", "account", "list"],
None,
Some(""),
LoggingPlan::Silent,
),
(&["acme-proxy", "man"], None, None, LoggingPlan::Silent),
(
&["acme-proxy", "completions", "bash"],
Some(LogLevel::Trace),
Some("debug"),
LoggingPlan::Silent,
),
];
for (argv, level, rust_log, expected) in cases {
let command = command_of(argv);
let plan = plan_logging(command.as_ref(), level, rust_log);
assert_eq!(
plan,
expected,
"`{}` with level {level:?} and RUST_LOG {rust_log:?}",
argv.join(" "),
);
}
}
#[test]
fn every_level_renders_a_directive_scoped_to_this_crate() {
assert_eq!(LogLevel::Off.directive(), "off");
for (level, expected) in [
(LogLevel::Error, "acme_proxy=error"),
(LogLevel::Warn, "acme_proxy=warn"),
(LogLevel::Info, "acme_proxy=info"),
(LogLevel::Debug, "acme_proxy=debug"),
(LogLevel::Trace, "acme_proxy=trace"),
] {
assert_eq!(level.directive(), expected);
EnvFilter::try_new(level.directive()).expect("every directive parses");
}
EnvFilter::try_new(LogLevel::Off.directive()).expect("`off` parses");
}
}