diff --git a/benchmarks/src/routing.rs b/benchmarks/src/routing.rs index 8f842896..d5ab6328 100644 --- a/benchmarks/src/routing.rs +++ b/benchmarks/src/routing.rs @@ -2,7 +2,7 @@ use std::collections::hash_set::HashSet; use criterion::{criterion_group, Criterion}; -use rocket::{route, config, Request, Data, Route, Config}; +use rocket::{route, config::{self, CliColors}, Request, Data, Route, Config}; use rocket::http::{Method, RawStr, ContentType, Accept, Status}; use rocket::local::blocking::{Client, LocalRequest}; @@ -81,7 +81,7 @@ fn client(routes: Vec) -> Client { let config = Config { profile: Config::RELEASE_PROFILE, log_level: rocket::config::LogLevel::Off, - cli_colors: false, + cli_colors: CliColors::Never, shutdown: config::Shutdown { ctrlc: false, #[cfg(unix)] diff --git a/core/lib/fuzz/targets/collision-matching.rs b/core/lib/fuzz/targets/collision-matching.rs index 8b9db564..a478bc42 100644 --- a/core/lib/fuzz/targets/collision-matching.rs +++ b/core/lib/fuzz/targets/collision-matching.rs @@ -6,6 +6,7 @@ use rocket::http::QMediaType; use rocket::local::blocking::{LocalRequest, Client}; use rocket::http::{Method, Accept, ContentType, MediaType, uri::Origin}; use rocket::route::{Route, RouteUri, dummy_handler}; +use rocket::config::CliColors; #[derive(Arbitrary)] struct ArbitraryRequestData<'a> { @@ -186,7 +187,7 @@ fn fuzz((route_a, route_b, req): TestData<'_>) { let rocket = rocket::custom(rocket::Config { workers: 2, log_level: rocket::log::LogLevel::Off, - cli_colors: false, + cli_colors: CliColors::Never, ..rocket::Config::debug_default() }); diff --git a/core/lib/src/config/cli_colors.rs b/core/lib/src/config/cli_colors.rs new file mode 100644 index 00000000..863104e2 --- /dev/null +++ b/core/lib/src/config/cli_colors.rs @@ -0,0 +1,80 @@ +use core::fmt; +use serde::{ + de::{self, Unexpected::{Signed, Str}}, + Deserialize, Serialize +}; + +/// Configure color output for logging. +#[derive(Clone, Serialize, PartialEq, Debug, Default)] +pub enum CliColors { + /// Always use colors in logs. + Always, + + /// Use colors in logs if the terminal supports it. + #[default] + Auto, + + /// Never use colors in logs. + Never +} + +impl fmt::Display for CliColors { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + CliColors::Always => write!(f, "always"), + CliColors::Auto => write!(f, "auto"), + CliColors::Never => write!(f, "never") + } + } +} + + +impl<'de> Deserialize<'de> for CliColors { + fn deserialize>(de: D) -> Result { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = CliColors; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("0, 1, false, true, always, auto, never") + } + + fn visit_str(self, val: &str) -> Result { + match val.to_lowercase().as_str() { + "true" => Ok(CliColors::Auto), + "false" => Ok(CliColors::Never), + "1" => Ok(CliColors::Auto), + "0" => Ok(CliColors::Never), + "auto" => Ok(CliColors::Auto), + "never" => Ok(CliColors::Never), + "always" => Ok(CliColors::Always), + _ => Err(E::invalid_value( + Str(val), + &"0, 1, false, true, always, auto, never", + )) + } + } + + fn visit_bool(self, val: bool) -> Result { + match val { + true => Ok(CliColors::Auto), + false => Ok(CliColors::Never) + } + } + + fn visit_i64(self, val: i64) -> Result { + match val { + 0 => Ok(CliColors::Never), + 1 => Ok(CliColors::Auto), + _ => Err(E::invalid_value( + Signed(val), + &"0, 1, false, true, always, auto, never", + )) + } + } + } + + de.deserialize_any(Visitor) + } +} diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index 635f2801..1f86ad34 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use yansi::{Paint, Style, Color::Primary}; use crate::log::PaintExt; -use crate::config::{LogLevel, Shutdown, Ident}; +use crate::config::{LogLevel, Shutdown, Ident, CliColors}; use crate::request::{self, Request, FromRequest}; use crate::http::uncased::Uncased; use crate::data::Limits; @@ -116,9 +116,8 @@ pub struct Config { pub shutdown: Shutdown, /// Max level to log. **(default: _debug_ `normal` / _release_ `critical`)** pub log_level: LogLevel, - /// Whether to use colors and emoji when logging. **(default: `true`)** - #[serde(deserialize_with = "figment::util::bool_from_str_or_int")] - pub cli_colors: bool, + /// Whether to use colors and emoji when logging. **(default: `auto`)** + pub cli_colors: CliColors, /// PRIVATE: This structure may grow (but never change otherwise) in a /// non-breaking release. As such, constructing this structure should /// _always_ be done using a public constructor or update syntax: @@ -198,7 +197,7 @@ impl Config { secret_key: SecretKey::zero(), shutdown: Shutdown::default(), log_level: LogLevel::Normal, - cli_colors: true, + cli_colors: CliColors::Auto, __non_exhaustive: (), } } diff --git a/core/lib/src/config/mod.rs b/core/lib/src/config/mod.rs index 7ea4c653..9049ba86 100644 --- a/core/lib/src/config/mod.rs +++ b/core/lib/src/config/mod.rs @@ -115,6 +115,7 @@ mod ident; mod config; mod shutdown; mod ip_header; +mod cli_colors; #[cfg(feature = "tls")] mod tls; @@ -129,6 +130,7 @@ pub use config::Config; pub use crate::log::LogLevel; pub use shutdown::Shutdown; pub use ident::Ident; +pub use cli_colors::CliColors; #[cfg(feature = "tls")] pub use tls::{TlsConfig, CipherSuite}; @@ -150,7 +152,7 @@ mod tests { use crate::log::LogLevel; use crate::data::{Limits, ToByteUnit}; - use crate::config::Config; + use crate::config::{Config, CliColors}; #[test] fn test_figment_is_default() { @@ -217,7 +219,7 @@ mod tests { ident: ident!("Something Cool"), keep_alive: 10, log_level: LogLevel::Off, - cli_colors: false, + cli_colors: CliColors::Never, ..Config::default() }); @@ -240,7 +242,7 @@ mod tests { ident: ident!("Something Else Cool"), keep_alive: 10, log_level: LogLevel::Off, - cli_colors: false, + cli_colors: CliColors::Never, ..Config::default() }); @@ -262,7 +264,117 @@ mod tests { workers: 20, keep_alive: 10, log_level: LogLevel::Off, - cli_colors: false, + cli_colors: CliColors::Never, + ..Config::default() + }); + + jail.set_env("ROCKET_CONFIG", "Other.toml"); + jail.create_file("Other.toml", r#" + [default] + address = "1.2.3.4" + port = 1234 + workers = 20 + keep_alive = 10 + log_level = "off" + cli_colors = "never" + "#)?; + + let config = Config::from(Config::figment()); + assert_eq!(config, Config { + address: Ipv4Addr::new(1, 2, 3, 4).into(), + port: 1234, + workers: 20, + keep_alive: 10, + log_level: LogLevel::Off, + cli_colors: CliColors::Never, + ..Config::default() + }); + + jail.set_env("ROCKET_CONFIG", "Other.toml"); + jail.create_file("Other.toml", r#" + [default] + address = "1.2.3.4" + port = 1234 + workers = 20 + keep_alive = 10 + log_level = "off" + cli_colors = "auto" + "#)?; + + let config = Config::from(Config::figment()); + assert_eq!(config, Config { + address: Ipv4Addr::new(1, 2, 3, 4).into(), + port: 1234, + workers: 20, + keep_alive: 10, + log_level: LogLevel::Off, + cli_colors: CliColors::Auto, + ..Config::default() + }); + + jail.set_env("ROCKET_CONFIG", "Other.toml"); + jail.create_file("Other.toml", r#" + [default] + address = "1.2.3.4" + port = 1234 + workers = 20 + keep_alive = 10 + log_level = "off" + cli_colors = "always" + "#)?; + + let config = Config::from(Config::figment()); + assert_eq!(config, Config { + address: Ipv4Addr::new(1, 2, 3, 4).into(), + port: 1234, + workers: 20, + keep_alive: 10, + log_level: LogLevel::Off, + cli_colors: CliColors::Always, + ..Config::default() + }); + + jail.set_env("ROCKET_CONFIG", "Other.toml"); + jail.create_file("Other.toml", r#" + [default] + address = "1.2.3.4" + port = 1234 + workers = 20 + keep_alive = 10 + log_level = "off" + cli_colors = true + "#)?; + + let config = Config::from(Config::figment()); + assert_eq!(config, Config { + address: Ipv4Addr::new(1, 2, 3, 4).into(), + port: 1234, + workers: 20, + keep_alive: 10, + log_level: LogLevel::Off, + cli_colors: CliColors::Auto, + ..Config::default() + }); + + jail.set_env("ROCKET_CONFIG", "Other.toml"); + jail.create_file("Other.toml", r#" + [default] + address = "1.2.3.4" + port = 1234 + workers = 20 + keep_alive = 10 + log_level = "off" + cli_colors = 1 + "#)?; + + let config = Config::from(Config::figment()); + assert_eq!(config, Config { + address: Ipv4Addr::new(1, 2, 3, 4).into(), + port: 1234, + workers: 20, + keep_alive: 10, + log_level: LogLevel::Off, + cli_colors: CliColors::Auto, ..Config::default() }); diff --git a/core/lib/src/log.rs b/core/lib/src/log.rs index 2b48dd04..e1143924 100644 --- a/core/lib/src/log.rs +++ b/core/lib/src/log.rs @@ -7,6 +7,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; use serde::{de, Serialize, Serializer, Deserialize, Deserializer}; use yansi::{Paint, Painted, Condition}; +use crate::config::CliColors; + /// Reexport the `log` crate as `private`. pub use log as private; @@ -167,7 +169,12 @@ pub(crate) fn init(config: &crate::Config) { } // Always disable colors if requested or if the stdout/err aren't TTYs. - let should_color = config.cli_colors && Condition::stdouterr_are_tty(); + let should_color = match config.cli_colors { + CliColors::Always => true, + CliColors::Auto => Condition::stdouterr_are_tty(), + CliColors::Never => false + }; + yansi::whenever(Condition::cached(should_color)); // Set Rocket-logger specific settings only if Rocket's logger is set.