rocket/
log.rs

1//! Rocket's logging infrastructure.
2
3use std::fmt;
4use std::str::FromStr;
5use std::sync::atomic::{AtomicBool, Ordering};
6
7use serde::{de, Serialize, Serializer, Deserialize, Deserializer};
8use yansi::{Paint, Painted, Condition};
9
10/// Reexport the `log` crate as `private`.
11pub use log as private;
12
13// Expose logging macros (hidden) for use by core/contrib codegen.
14macro_rules! define_log_macro {
15    ($name:ident: $kind:ident, $target:expr, $d:tt) => (
16        #[doc(hidden)]
17        #[macro_export]
18        macro_rules! $name {
19            ($d ($t:tt)*) => ($crate::log::private::$kind!(target: $target, $d ($t)*))
20        }
21    );
22    ($name:ident ($indented:ident): $kind:ident, $target:expr, $d:tt) => (
23        define_log_macro!($name: $kind, $target, $d);
24        define_log_macro!($indented: $kind, concat!($target, "::_"), $d);
25    );
26    ($kind:ident, $indented:ident) => (
27        define_log_macro!($kind: $kind, module_path!(), $);
28        define_log_macro!($indented: $kind, concat!(module_path!(), "::_"), $);
29
30        pub use $indented;
31    );
32}
33
34define_log_macro!(error, error_);
35define_log_macro!(warn, warn_);
36define_log_macro!(info, info_);
37define_log_macro!(debug, debug_);
38define_log_macro!(trace, trace_);
39define_log_macro!(launch_meta (launch_meta_): info, "rocket::launch", $);
40define_log_macro!(launch_info (launch_msg_): warn, "rocket::launch", $);
41
42// `print!` panics when stdout isn't available, but this macro doesn't. See
43// rwf2/Rocket#2019 and rust-lang/rust#46016 for more.
44//
45// Unfortunately, `libtest` captures output by replacing a special sink that
46// `print!`, and _only_ `print!`, writes to. Using `write!` directly bypasses
47// this sink. As a result, using this better implementation for logging means
48// that test log output isn't captured, muddying `cargo test` output.
49//
50// As a compromise, we only use this better implementation when we're not
51// compiled with `debug_assertions` or running tests, so at least tests run in
52// debug-mode won't spew output. NOTE: `cfg(test)` alone isn't sufficient: the
53// crate is compiled normally for integration tests.
54#[cfg(not(any(debug_assertions, test, doctest)))]
55macro_rules! write_out {
56    ($($arg:tt)*) => ({
57        use std::io::{Write, stdout, stderr};
58        let _ = write!(stdout(), $($arg)*).or_else(|e| write!(stderr(), "{}", e));
59    })
60}
61
62#[cfg(any(debug_assertions, test, doctest))]
63macro_rules! write_out {
64    ($($arg:tt)*) => (print!($($arg)*))
65}
66
67#[derive(Debug)]
68struct RocketLogger;
69
70/// Defines the maximum level of log messages to show.
71#[derive(PartialEq, Eq, Debug, Clone, Copy)]
72pub enum LogLevel {
73    /// Only shows errors and warnings: `"critical"`.
74    Critical,
75    /// Shows everything except debug and trace information: `"normal"`.
76    Normal,
77    /// Shows everything: `"debug"`.
78    Debug,
79    /// Shows nothing: "`"off"`".
80    Off,
81}
82
83pub trait PaintExt: Sized {
84    fn emoji(self) -> Painted<Self>;
85}
86
87// Whether a record is a special `launch_{meta,info}!` record.
88fn is_launch_record(record: &log::Metadata<'_>) -> bool {
89    record.target().contains("rocket::launch")
90}
91
92impl log::Log for RocketLogger {
93    #[inline(always)]
94    fn enabled(&self, record: &log::Metadata<'_>) -> bool {
95        match log::max_level().to_level() {
96            Some(max) => record.level() <= max || is_launch_record(record),
97            None => false
98        }
99    }
100
101    fn log(&self, record: &log::Record<'_>) {
102        // Print nothing if this level isn't enabled and this isn't launch info.
103        if !self.enabled(record.metadata()) {
104            return;
105        }
106
107        // Don't print Hyper, Rustls or r2d2 messages unless debug is enabled.
108        let max = log::max_level();
109        let from = |path| record.module_path().map_or(false, |m| m.starts_with(path));
110        let debug_only = from("hyper") || from("rustls") || from("r2d2");
111        if log::LevelFilter::from(LogLevel::Debug) > max && debug_only {
112            return;
113        }
114
115        // In Rocket, we abuse targets with suffix "_" to indicate indentation.
116        let indented = record.target().ends_with('_');
117        if indented {
118            write_out!("   {} ", ">>".bold());
119        }
120
121        // Downgrade a physical launch `warn` to logical `info`.
122        let level = is_launch_record(record.metadata())
123            .then(|| log::Level::Info)
124            .unwrap_or_else(|| record.level());
125
126        match level {
127            log::Level::Error if !indented => {
128                write_out!("{} {}\n", "Error:".red().bold(), record.args().red().wrap());
129            }
130            log::Level::Warn if !indented => {
131                write_out!("{} {}\n", "Warning:".yellow().bold(), record.args().yellow().wrap());
132            }
133            log::Level::Info => write_out!("{}\n", record.args().blue().wrap()),
134            log::Level::Trace => write_out!("{}\n", record.args().magenta().wrap()),
135            log::Level::Warn => write_out!("{}\n", record.args().yellow().wrap()),
136            log::Level::Error => write_out!("{}\n", &record.args().red().wrap()),
137            log::Level::Debug => {
138                write_out!("\n{} ", "-->".blue().bold());
139                if let Some(file) = record.file() {
140                    write_out!("{}", file.blue());
141                }
142
143                if let Some(line) = record.line() {
144                    write_out!(":{}\n", line.blue());
145                }
146
147                write_out!("\t{}\n", record.args());
148            }
149        }
150    }
151
152    fn flush(&self) {
153        // NOOP: We don't buffer any records.
154    }
155}
156
157pub(crate) fn init_default() {
158    crate::log::init(&crate::Config::debug_default())
159}
160
161pub(crate) fn init(config: &crate::Config) {
162    static ROCKET_LOGGER_SET: AtomicBool = AtomicBool::new(false);
163
164    // Try to initialize Rocket's logger, recording if we succeeded.
165    if log::set_boxed_logger(Box::new(RocketLogger)).is_ok() {
166        ROCKET_LOGGER_SET.store(true, Ordering::Release);
167    }
168
169    // Always disable colors if requested or if the stdout/err aren't TTYs.
170    let should_color = config.cli_colors && Condition::stdouterr_are_tty();
171    yansi::whenever(Condition::cached(should_color));
172
173    // Set Rocket-logger specific settings only if Rocket's logger is set.
174    if ROCKET_LOGGER_SET.load(Ordering::Acquire) {
175        log::set_max_level(config.log_level.into());
176    }
177}
178
179impl From<LogLevel> for log::LevelFilter {
180    fn from(level: LogLevel) -> Self {
181        match level {
182            LogLevel::Critical => log::LevelFilter::Warn,
183            LogLevel::Normal => log::LevelFilter::Info,
184            LogLevel::Debug => log::LevelFilter::Trace,
185            LogLevel::Off => log::LevelFilter::Off
186        }
187    }
188}
189
190impl LogLevel {
191    fn as_str(&self) -> &str {
192        match self {
193            LogLevel::Critical => "critical",
194            LogLevel::Normal => "normal",
195            LogLevel::Debug => "debug",
196            LogLevel::Off => "off",
197        }
198    }
199}
200
201impl FromStr for LogLevel {
202    type Err = &'static str;
203
204    fn from_str(s: &str) -> Result<Self, Self::Err> {
205        let level = match &*s.to_ascii_lowercase() {
206            "critical" => LogLevel::Critical,
207            "normal" => LogLevel::Normal,
208            "debug" => LogLevel::Debug,
209            "off" => LogLevel::Off,
210            _ => return Err("a log level (off, debug, normal, critical)")
211        };
212
213        Ok(level)
214    }
215}
216
217impl fmt::Display for LogLevel {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        write!(f, "{}", self.as_str())
220    }
221}
222
223impl Serialize for LogLevel {
224    fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
225        ser.serialize_str(self.as_str())
226    }
227}
228
229impl<'de> Deserialize<'de> for LogLevel {
230    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
231        let string = String::deserialize(de)?;
232        LogLevel::from_str(&string).map_err(|_| de::Error::invalid_value(
233            de::Unexpected::Str(&string),
234            &figment::error::OneOf( &["critical", "normal", "debug", "off"])
235        ))
236    }
237}
238
239impl PaintExt for &str {
240    /// Paint::masked(), but hidden on Windows due to broken output. See #1122.
241    fn emoji(self) -> Painted<Self> {
242        #[cfg(windows)] { Paint::new("").mask() }
243        #[cfg(not(windows))] { Paint::new(self).mask() }
244    }
245}