diff --git a/contrib/dyn_templates/src/fairing.rs b/contrib/dyn_templates/src/fairing.rs index d8d7ef3e..949fecf3 100644 --- a/contrib/dyn_templates/src/fairing.rs +++ b/contrib/dyn_templates/src/fairing.rs @@ -1,5 +1,6 @@ use rocket::{Rocket, Build, Orbit}; use rocket::fairing::{self, Fairing, Info, Kind}; +use rocket::figment::{Source, value::magic::RelativePathBuf}; use crate::context::{Callback, Context, ContextManager}; use crate::template::DEFAULT_TEMPLATE_DIR; @@ -31,8 +32,6 @@ impl Fairing for TemplateFairing { /// template engines. In debug mode, the `ContextManager::new` method /// initializes a directory watcher for auto-reloading of templates. async fn on_ignite(&self, rocket: Rocket) -> fairing::Result { - use rocket::figment::value::magic::RelativePathBuf; - let configured_dir = rocket.figment() .extract_inner::("template_dir") .map(|path| path.relative()); @@ -55,14 +54,13 @@ impl Fairing for TemplateFairing { } async fn on_liftoff(&self, rocket: &Rocket) { - use rocket::{figment::Source, yansi::Paint}; - let cm = rocket.state::() .expect("Template ContextManager registered in on_ignite"); - info!("{}{}:", "📐 ".emoji(), "Templating".magenta()); - info_!("directory: {}", Source::from(&*cm.context().root).primary()); - info_!("engines: {:?}", Engines::ENABLED_EXTENSIONS.primary()); + info_span!("templating" [icon = "📐"] => { + info!(directory = %Source::from(&*cm.context().root)); + info!(engines = ?Engines::ENABLED_EXTENSIONS); + }); } #[cfg(debug_assertions)] @@ -72,5 +70,4 @@ impl Fairing for TemplateFairing { cm.reload_if_needed(&self.callback); } - } diff --git a/core/codegen/src/attribute/entry/launch.rs b/core/codegen/src/attribute/entry/launch.rs index 9d439814..eb70ceff 100644 --- a/core/codegen/src/attribute/entry/launch.rs +++ b/core/codegen/src/attribute/entry/launch.rs @@ -4,7 +4,7 @@ use proc_macro2::{TokenStream, Span}; use super::EntryAttr; use crate::attribute::suppress::Lint; -use crate::exports::mixed; +use crate::exports::{mixed, _error, _ExitCode}; /// `#[rocket::launch]`: generates a `main` function that calls the attributed /// function to generate a `Rocket` instance. Then calls `.launch()` on the @@ -106,14 +106,14 @@ impl EntryAttr for Launch { let (vis, mut sig) = (&f.vis, f.sig.clone()); sig.ident = syn::Ident::new("main", sig.ident.span()); - sig.output = syn::ReturnType::Default; + sig.output = syn::parse_quote!(-> #_ExitCode); sig.asyncness = None; Ok(quote_spanned!(block.span() => #[allow(dead_code)] #f #vis #sig { - let _ = ::rocket::async_main(#launch); + #_error::Error::report(::rocket::async_main(#launch)) } )) } diff --git a/core/codegen/src/exports.rs b/core/codegen/src/exports.rs index 6dfb4eb3..323576ae 100644 --- a/core/codegen/src/exports.rs +++ b/core/codegen/src/exports.rs @@ -69,6 +69,7 @@ define_exported_paths! { _request => ::rocket::request, _response => ::rocket::response, _route => ::rocket::route, + _error => ::rocket::error, _catcher => ::rocket::catcher, _sentinel => ::rocket::sentinel, _form => ::rocket::form::prelude, @@ -84,6 +85,7 @@ define_exported_paths! { _Box => ::std::boxed::Box, _Vec => ::std::vec::Vec, _Cow => ::std::borrow::Cow, + _ExitCode => ::std::process::ExitCode, BorrowMut => ::std::borrow::BorrowMut, Outcome => ::rocket::outcome::Outcome, FromForm => ::rocket::form::FromForm, diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index 220cabdc..b5f19842 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -433,7 +433,7 @@ pub fn bail_with_config_error(error: figment::Error) -> T { } #[doc(hidden)] -// FIXME: Remove this funtion. +// FIXME: Remove this function. pub fn pretty_print_error(error: figment::Error) { error.trace_error() } diff --git a/core/lib/src/config/tests.rs b/core/lib/src/config/tests.rs index bcb91209..fea5df55 100644 --- a/core/lib/src/config/tests.rs +++ b/core/lib/src/config/tests.rs @@ -340,24 +340,22 @@ fn test_precedence() { #[test] #[cfg(feature = "secrets")] -#[should_panic] fn test_err_on_non_debug_and_no_secret_key() { figment::Jail::expect_with(|jail| { jail.set_env("ROCKET_PROFILE", "release"); let rocket = crate::custom(Config::figment()); - let _result = crate::local::blocking::Client::untracked(rocket); + crate::local::blocking::Client::untracked(rocket).expect_err("release secret key"); Ok(()) }); } #[test] #[cfg(feature = "secrets")] -#[should_panic] fn test_err_on_non_debug2_and_no_secret_key() { figment::Jail::expect_with(|jail| { jail.set_env("ROCKET_PROFILE", "boop"); let rocket = crate::custom(Config::figment()); - let _result = crate::local::blocking::Client::tracked(rocket); + crate::local::blocking::Client::tracked(rocket).expect_err("boop secret key"); Ok(()) }); } diff --git a/core/lib/src/error.rs b/core/lib/src/error.rs index 0fc2dcec..78252d55 100644 --- a/core/lib/src/error.rs +++ b/core/lib/src/error.rs @@ -1,54 +1,20 @@ //! Types representing various errors that can occur in a Rocket application. -use std::{io, fmt}; -use std::sync::{Arc, atomic::{Ordering, AtomicBool}}; +use std::{io, fmt, process}; use std::error::Error as StdError; +use std::sync::Arc; -use yansi::Paint; use figment::Profile; use crate::listener::Endpoint; -use crate::{Ignite, Orbit, Rocket}; +use crate::trace::traceable::Traceable; +use crate::{Ignite, Orbit, Phase, Rocket}; /// An error that occurs during launch. /// /// An `Error` is returned by [`launch()`](Rocket::launch()) when launching an /// application fails or, more rarely, when the runtime fails after launching. /// -/// # Panics -/// -/// A value of this type panics if it is dropped without first being inspected. -/// An _inspection_ occurs when any method is called. For instance, if -/// `println!("Error: {}", e)` is called, where `e: Error`, the `Display::fmt` -/// method being called by `println!` results in `e` being marked as inspected; -/// a subsequent `drop` of the value will _not_ result in a panic. The following -/// snippet illustrates this: -/// -/// ```rust -/// # let _ = async { -/// if let Err(error) = rocket::build().launch().await { -/// // This println "inspects" the error. -/// println!("Launch failed! Error: {}", error); -/// -/// // This call to drop (explicit here for demonstration) will do nothing. -/// drop(error); -/// } -/// # }; -/// ``` -/// -/// When a value of this type panics, the corresponding error message is pretty -/// printed to the console. The following illustrates this: -/// -/// ```rust -/// # let _ = async { -/// let error = rocket::build().launch().await; -/// -/// // This call to drop (explicit here for demonstration) will result in -/// // `error` being pretty-printed to the console along with a `panic!`. -/// drop(error); -/// # }; -/// ``` -/// /// # Usage /// /// An `Error` value should usually be allowed to `drop` without inspection. @@ -61,8 +27,7 @@ use crate::{Ignite, Orbit, Rocket}; /// /// 2. You want to display your own error messages. pub struct Error { - handled: AtomicBool, - kind: ErrorKind + pub(crate) kind: ErrorKind } /// The kind error that occurred. @@ -74,6 +39,7 @@ pub struct Error { /// `FailedFairing` variants, respectively. #[derive(Debug)] #[non_exhaustive] +// FIXME: Don't expose this. Expose access methods from `Error` instead. pub enum ErrorKind { /// Binding to the network interface at `.0` failed with error `.1`. Bind(Option, Box), @@ -93,7 +59,7 @@ pub enum ErrorKind { /// Liftoff failed. Contains the Rocket instance that failed to shutdown. Liftoff( Result>, Arc>>, - Box + tokio::task::JoinError, ), /// Shutdown failed. Contains the Rocket instance that failed to shutdown. Shutdown(Arc>), @@ -103,6 +69,28 @@ pub enum ErrorKind { #[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Empty; +impl Error { + #[inline(always)] + pub(crate) fn new(kind: ErrorKind) -> Error { + Error { kind } + } + + // FIXME: Don't expose this. Expose finer access methods instead. + pub fn kind(&self) -> &ErrorKind { + &self.kind + } + + pub fn report(result: Result, Error>) -> process::ExitCode { + match result { + Ok(_) => process::ExitCode::SUCCESS, + Err(e) => { + error_span!("aborting launch due to error" => e.trace_error()); + process::ExitCode::SUCCESS + } + } + } +} + impl From for Error { fn from(kind: ErrorKind) -> Self { Error::new(kind) @@ -121,140 +109,22 @@ impl From for Error { } } -impl Error { - #[inline(always)] - pub(crate) fn new(kind: ErrorKind) -> Error { - Error { handled: AtomicBool::new(false), kind } - } - - #[inline(always)] - fn was_handled(&self) -> bool { - self.handled.load(Ordering::Acquire) - } - - #[inline(always)] - fn mark_handled(&self) { - self.handled.store(true, Ordering::Release) - } - - /// Retrieve the `kind` of the launch error. - /// - /// # Example - /// - /// ```rust - /// use rocket::error::ErrorKind; - /// - /// # let _ = async { - /// if let Err(error) = rocket::build().launch().await { - /// match error.kind() { - /// ErrorKind::Io(e) => println!("found an i/o launch error: {}", e), - /// e => println!("something else happened: {}", e) - /// } - /// } - /// # }; - /// ``` - #[inline] - pub fn kind(&self) -> &ErrorKind { - self.mark_handled(); - &self.kind - } - - /// Prints the error with color (if enabled) and detail. Returns a string - /// that indicates the abort condition such as "aborting due to i/o error". - /// - /// This function is called on `Drop` to display the error message. By - /// contrast, the `Display` implementation prints a succinct version of the - /// error, without detail. - /// - /// ```rust - /// # let _ = async { - /// if let Err(error) = rocket::build().launch().await { - /// let abort = error.pretty_print(); - /// panic!("{}", abort); - /// } - /// # }; - /// ``` - // FIXME: Remove `Error` panicking behavior. Make display/debug better. - pub fn pretty_print(&self) -> &'static str { - self.mark_handled(); - match self.kind() { - ErrorKind::Bind(ref a, ref e) => { - if let Some(e) = e.downcast_ref::() { - e.pretty_print() - } else { - match a { - Some(a) => error!("Binding to {} failed.", a.primary().underline()), - None => error!("Binding to network interface failed."), - } - - info_!("{}", e); - "aborting due to bind error" - } - } - ErrorKind::Io(ref e) => { - error!("Rocket failed to launch due to an I/O error."); - info_!("{}", e); - "aborting due to i/o error" - } - ErrorKind::Collisions(ref collisions) => { - fn log_collisions(kind: &str, collisions: &[(T, T)]) { - if collisions.is_empty() { return } - - error!("Rocket failed to launch due to the following {} collisions:", kind); - for (a, b) in collisions { - info_!("{} {} {}", a, "collides with".red().italic(), b) - } - } - - log_collisions("route", &collisions.routes); - log_collisions("catcher", &collisions.catchers); - - info_!("Note: Route collisions can usually be resolved by ranking routes."); - "aborting due to detected routing collisions" - } - ErrorKind::FailedFairings(ref failures) => { - error!("Rocket failed to launch due to failing fairings:"); - for fairing in failures { - info_!("{}", fairing.name); - } - - "aborting due to fairing failure(s)" - } - ErrorKind::InsecureSecretKey(profile) => { - error!("secrets enabled in non-debug without `secret_key`"); - info_!("selected profile: {}", profile.primary().bold()); - info_!("disable `secrets` feature or configure a `secret_key`"); - "aborting due to insecure configuration" - } - ErrorKind::Config(error) => { - crate::config::pretty_print_error(error.clone()); - "aborting due to invalid configuration" - } - ErrorKind::SentinelAborts(ref errors) => { - error!("Rocket failed to launch due to aborting sentinels:"); - for sentry in errors { - let name = sentry.type_name.primary().bold(); - let (file, line, col) = sentry.location; - info_!("{} ({}:{}:{})", name, file, line, col); - } - - "aborting due to sentinel-triggered abort(s)" - } - ErrorKind::Liftoff(_, error) => { - error!("Rocket liftoff failed due to panicking liftoff fairing(s)."); - error_!("{error}"); - "aborting due to failed liftoff" - } - ErrorKind::Shutdown(_) => { - error!("Rocket failed to shutdown gracefully."); - "aborting due to failed shutdown" - } +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match &self.kind { + ErrorKind::Bind(_, e) => Some(&**e), + ErrorKind::Io(e) => Some(e), + ErrorKind::Collisions(_) => None, + ErrorKind::FailedFairings(_) => None, + ErrorKind::InsecureSecretKey(_) => None, + ErrorKind::Config(e) => Some(e), + ErrorKind::SentinelAborts(_) => None, + ErrorKind::Liftoff(_, e) => Some(e), + ErrorKind::Shutdown(_) => None, } } } -impl std::error::Error for Error { } - impl fmt::Display for ErrorKind { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -275,27 +145,14 @@ impl fmt::Display for ErrorKind { impl fmt::Debug for Error { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.mark_handled(); - self.kind().fmt(f) + self.kind.fmt(f) } } impl fmt::Display for Error { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.mark_handled(); - write!(f, "{}", self.kind()) - } -} - -impl Drop for Error { - fn drop(&mut self) { - // Don't panic if the message has been seen. Don't double-panic. - if self.was_handled() || std::thread::panicking() { - return - } - - panic!("{}", self.pretty_print()); + write!(f, "{}", self.kind) } } diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs index 31448348..aeb99eec 100644 --- a/core/lib/src/request/request.rs +++ b/core/lib/src/request/request.rs @@ -5,7 +5,6 @@ use std::borrow::Cow; use std::future::Future; use std::net::IpAddr; -use yansi::Paint; use state::{TypeMap, InitCell}; use futures::future::BoxFuture; use ref_swap::OptionRefSwap; @@ -1203,20 +1202,3 @@ impl fmt::Debug for Request<'_> { .finish() } } - -// FIXME: Remov me to identify dependent `TRACE` statements. -impl fmt::Display for Request<'_> { - /// Pretty prints a Request. Primarily used by Rocket's logging. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} {}", self.method().green(), self.uri.blue())?; - - // Print the requests media type when the route specifies a format. - if let Some(mime) = self.format() { - if !mime.is_any() { - write!(f, " {}/{}", mime.top().yellow().linger(), mime.sub().resetting())?; - } - } - - Ok(()) - } -} diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs index b2278ab9..e4908bbf 100644 --- a/core/lib/src/rocket.rs +++ b/core/lib/src/rocket.rs @@ -687,7 +687,7 @@ impl Rocket { rocket.shutdown.spawn_listener(&rocket.config.shutdown); if let Err(e) = tokio::spawn(Rocket::liftoff(rocket.clone())).await { let rocket = rocket.try_wait_shutdown().await.map(Box::new); - return Err(ErrorKind::Liftoff(rocket, Box::new(e)).into()); + return Err(ErrorKind::Liftoff(rocket, e).into()); } Ok(rocket) diff --git a/core/lib/src/server.rs b/core/lib/src/server.rs index 9711963f..2d28c14e 100644 --- a/core/lib/src/server.rs +++ b/core/lib/src/server.rs @@ -125,8 +125,8 @@ impl Rocket { let http12 = tokio::task::spawn(rocket.clone().serve12(listener)); let http3 = tokio::task::spawn(rocket.clone().serve3(h3listener)); let (r1, r2) = tokio::join!(http12, http3); - r1.map_err(|e| ErrorKind::Liftoff(Err(rocket.clone()), Box::new(e)))??; - r2.map_err(|e| ErrorKind::Liftoff(Err(rocket.clone()), Box::new(e)))??; + r1.map_err(|e| ErrorKind::Liftoff(Err(rocket.clone()), e))??; + r2.map_err(|e| ErrorKind::Liftoff(Err(rocket.clone()), e))??; return Ok(rocket); } diff --git a/core/lib/src/trace/macros.rs b/core/lib/src/trace/macros.rs index a3ec72b9..5a0858ca 100644 --- a/core/lib/src/trace/macros.rs +++ b/core/lib/src/trace/macros.rs @@ -18,11 +18,7 @@ macro_rules! declare_macro { ([$d:tt] $name:ident $level:ident) => ( #[macro_export] macro_rules! $name { - ($d ($t:tt)*) => ({ - #[allow(unused_imports)] - use $crate::trace::macros::PaintExt as _; - $crate::tracing::$level!($d ($t)*); - }) + ($d ($t:tt)*) => ($crate::tracing::$level!($d ($t)*)); } ); } @@ -45,14 +41,31 @@ macro_rules! declare_span_macro { #[macro_export] macro_rules! $name { ($n:literal $d ([ $d ($f:tt)* ])? => $in_scope:expr) => ({ - $crate::tracing::span!(tracing::Level::$level, $n $d (, $d ($f)* )?) + $crate::tracing::span!($crate::tracing::Level::$level, $n $d (, $d ($f)* )?) .in_scope(|| $in_scope); }) } ); } -declare_span_macro!(info_span INFO, trace_span TRACE, debug_span DEBUG); +declare_span_macro!(error_span ERROR, info_span INFO, trace_span TRACE, debug_span DEBUG); + +macro_rules! span { + ($level:expr, $($args:tt)*) => {{ + match $level { + $crate::tracing::Level::ERROR => + $crate::tracing::span!($crate::tracing::Level::ERROR, $($args)*), + $crate::tracing::Level::WARN => + $crate::tracing::span!($crate::tracing::Level::WARN, $($args)*), + $crate::tracing::Level::INFO => + $crate::tracing::span!($crate::tracing::Level::INFO, $($args)*), + $crate::tracing::Level::DEBUG => + $crate::tracing::span!($crate::tracing::Level::DEBUG, $($args)*), + $crate::tracing::Level::TRACE => + $crate::tracing::span!($crate::tracing::Level::TRACE, $($args)*), + } + }}; +} macro_rules! event { ($level:expr, $($args:tt)*) => {{ diff --git a/core/lib/src/trace/mod.rs b/core/lib/src/trace/mod.rs index 1f0bb234..3d604fbb 100644 --- a/core/lib/src/trace/mod.rs +++ b/core/lib/src/trace/mod.rs @@ -5,6 +5,8 @@ pub mod subscriber; pub mod level; pub mod traceable; +pub use traceable::Traceable; + pub fn init<'a, T: Into>>(_config: T) { #[cfg(all(feature = "trace", debug_assertions))] subscriber::RocketFmt::::init(_config.into()); diff --git a/core/lib/src/trace/subscriber/pretty.rs b/core/lib/src/trace/subscriber/pretty.rs index 2b5e5469..1026f61f 100644 --- a/core/lib/src/trace/subscriber/pretty.rs +++ b/core/lib/src/trace/subscriber/pretty.rs @@ -89,7 +89,53 @@ impl LookupSpan<'a>> Layer for RocketFmt { println!("{prefix}{}{} {}", self.emoji("🚀 "), "Rocket has launched from".paint(style).primary().bold(), &data["endpoint"].paint(style).primary().bold().underline()); - } + }, + "route" => println!("{}", Formatter(|f| { + write!(f, "{}{}{}: ", self.indent(), self.marker(), "route".paint(style))?; + + let (base, mut relative) = (&data["uri.base"], &data["uri.unmounted"]); + if base.ends_with('/') && relative.starts_with('/') { + relative = &relative[1..]; + } + + write!(f, "{:>3} {} {}{}", + &data["rank"].paint(style.bright().dim()), + &data["method"].paint(style.bold()), + base.paint(style.primary().underline()), + relative.paint(style.primary()), + )?; + + if let Some(name) = data.get("name") { + write!(f, " ({})", name.paint(style.bold().bright()))?; + } + + Ok(()) + })), + "catcher" => println!("{}", Formatter(|f| { + write!(f, "{}{}{}: ", self.indent(), self.marker(), "catcher".paint(style))?; + + match data.get("code") { + Some(code) => write!(f, "{} ", code.paint(style.bold()))?, + None => write!(f, "{} ", "default".paint(style.bold()))?, + } + + write!(f, "{}", &data["uri.base"].paint(style.primary()))?; + if let Some(name) = data.get("name") { + write!(f, " ({})", name.paint(style.bold().bright()))?; + } + + Ok(()) + })), + "header" => println!("{}{}{}: {}: {}", + self.indent(), self.marker(), "header".paint(style), + &data["name"].paint(style.bold()), + &data["value"].paint(style.primary()), + ), + "fairing" => println!("{}{}{}: {} {}", + self.indent(), self.marker(), "fairing".paint(style), + &data["name"].paint(style.bold()), + &data["kind"].paint(style.primary().dim()), + ), _ => self.print_pretty(meta, event), } } @@ -111,14 +157,22 @@ impl LookupSpan<'a>> Layer for RocketFmt { let meta = span.metadata(); let style = self.style(meta); - let prefix = self.prefix(meta); let emoji = self.emoji(icon); let name = name.paint(style).bold(); - if !attrs.fields().is_empty() { - println!("{prefix}{emoji}{name} ({})", self.compact_fields(meta, attrs)) + let fields = self.compact_fields(meta, attrs); + let prefix = self.prefix(meta); + let fieldless_prefix = Formatter(|f| write!(f, "{prefix}{emoji}{name} ")); + let field_prefix = Formatter(|f| write!(f, "{prefix}{emoji}{name} ({fields}) ")); + + if self.has_message(meta) && self.has_data_fields(meta) { + print!("{}", self.message(&field_prefix, &fieldless_prefix, meta, attrs)); + } else if self.has_message(meta) { + print!("{}", self.message(&fieldless_prefix, &fieldless_prefix, meta, attrs)); + } else if self.has_data_fields(meta) { + println!("{field_prefix}"); } else { - println!("{prefix}{emoji}{name}"); + println!("{fieldless_prefix}"); } } diff --git a/core/lib/src/trace/traceable.rs b/core/lib/src/trace/traceable.rs index df45f4cf..eefc4db8 100644 --- a/core/lib/src/trace/traceable.rs +++ b/core/lib/src/trace/traceable.rs @@ -1,6 +1,9 @@ -use crate::fairing::Fairing; -use crate::{route, Catcher, Config, Response, Route}; +use std::error::Error as StdError; + +use crate::sentinel::Sentry; use crate::util::Formatter; +use crate::{route, Catcher, Config, Error, Request, Response, Route}; +use crate::error::ErrorKind; use figment::Figment; use rocket::http::Header; @@ -131,8 +134,8 @@ impl Traceable for Route { fn trace(&self, level: Level) { event! { level, "route", name = self.name.as_ref().map(|n| &**n), - method = %self.method, rank = self.rank, + method = %self.method, uri = %self.uri, uri.base = %self.uri.base(), uri.unmounted = %self.uri.unmounted(), @@ -154,7 +157,7 @@ impl Traceable for Catcher { fn trace(&self, level: Level) { event! { level, "catcher", name = self.name.as_ref().map(|n| &**n), - status = %Formatter(|f| match self.code { + code = %Formatter(|f| match self.code { Some(code) => write!(f, "{}", code), None => write!(f, "default"), }), @@ -164,9 +167,15 @@ impl Traceable for Catcher { } } -impl Traceable for &dyn Fairing { +impl Traceable for &dyn crate::fairing::Fairing { fn trace(&self, level: Level) { - event!(level, "fairing", name = self.info().name, kind = %self.info().kind) + self.info().trace(level) + } +} + +impl Traceable for crate::fairing::Info { + fn trace(&self, level: Level) { + event!(level, "fairing", name = self.name, kind = %self.kind) } } @@ -176,17 +185,17 @@ impl Traceable for figment::error::Kind { match self { Message(message) => error!(message), - InvalidType(actual, expected) => error!(name: "invalid type", %actual, expected), - InvalidValue(actual, expected) => error!(name: "invalid value", %actual, expected), - InvalidLength(actual, expected) => error!(name: "invalid length", %actual, expected), - UnknownVariant(actual, v) => error!(name: "unknown variant", actual, expected = %V(v)), - UnknownField(actual, v) => error!(name: "unknown field", actual, expected = %V(v)), - UnsupportedKey(actual, v) => error!(name: "unsupported key", %actual, expected = &**v), - MissingField(value) => error!(name: "missing field", value = &**value), - DuplicateField(value) => error!(name: "duplicate field", value), - ISizeOutOfRange(value) => error!(name: "out of range signed integer", value), - USizeOutOfRange(value) => error!(name: "out of range unsigned integer", value), - Unsupported(value) => error!(name: "unsupported type", %value), + InvalidType(actual, expected) => error!(%actual, expected, "invalid type"), + InvalidValue(actual, expected) => error!(%actual, expected, "invalid value"), + InvalidLength(actual, expected) => error!(%actual, expected, "invalid length"), + UnknownVariant(actual, v) => error!(actual, expected = %V(v), "unknown variant"), + UnknownField(actual, v) => error!(actual, expected = %V(v), "unknown field"), + UnsupportedKey(actual, v) => error!(%actual, expected = &**v, "unsupported key"), + MissingField(value) => error!(value = &**value, "missing field"), + DuplicateField(value) => error!(value, "duplicate field"), + ISizeOutOfRange(value) => error!(value, "out of range signed integer"), + USizeOutOfRange(value) => error!(value, "out of range unsigned integer"), + Unsupported(value) => error!(%value, "unsupported type"), } } } @@ -237,3 +246,94 @@ impl Traceable for Response<'_> { event!(level, "response", status = self.status().code); } } + +impl Traceable for Error { + fn trace(&self, level: Level) { + self.kind.trace(level); + } +} + +impl Traceable for Sentry { + fn trace(&self, level: Level) { + let (file, line, column) = self.location; + event!(level, "sentry", type_name = self.type_name, file, line, column); + } +} + +impl Traceable for Request<'_> { + fn trace(&self, level: Level) { + event!(level, "request", method = %self.method(), uri = %self.uri()) + } +} + +impl Traceable for ErrorKind { + fn trace(&self, level: Level) { + use ErrorKind::*; + + fn try_downcast<'a, T>(error: &'a (dyn StdError + 'static)) -> Option<&'a T> + where T: StdError + 'static + { + error.downcast_ref().or_else(|| error.source()?.downcast_ref()) + } + + match self { + Bind(endpoint, error) => { + if let Some(e) = try_downcast::(&**error) { + e.trace(level); + } else if let Some(e) = try_downcast::(&**error) { + e.trace(level); + } else { + event!(level, "error::bind", + ?error, + endpoint = endpoint.as_ref().map(display), + "binding to network interface failed" + ) + } + } + Io(reason) => event!(level, "error::io", %reason, "i/o error"), + Config(error) => error.trace(level), + Collisions(collisions) => { + let routes = collisions.routes.len(); + let catchers = collisions.catchers.len(); + + span!(level, "collision", + route.pairs = routes, + catcher.pairs = catchers, + "colliding items detected" + ).in_scope(|| { + let routes = &collisions.routes; + for (a, b) in routes { + span!(level, "colliding route pair").in_scope(|| { + a.trace(level); + b.trace(level); + }) + } + + let catchers = &collisions.catchers; + for (a, b) in catchers { + span!(level, "colliding catcher pair").in_scope(|| { + a.trace(level); + b.trace(level); + }) + } + + span!(Level::INFO, "collisions can usually be resolved by ranking items"); + }); + } + FailedFairings(fairings) => { + let span = span!(level, "fairings", count = fairings.len(), "ignition failure"); + span.in_scope(|| fairings.iter().trace_all(level)); + }, + SentinelAborts(sentries) => { + let span = span!(level, "sentries", "sentry abort"); + span.in_scope(|| sentries.iter().trace_all(level)); + } + InsecureSecretKey(profile) => event!(level, "insecure_key", %profile, + "secrets enabled in a non-debug profile without a stable `secret_key`\n\ + disable the `secrets` feature or configure a `secret_key`" + ), + Liftoff(_, reason) => event!(level, "panic", %reason, "liftoff fairing failed"), + Shutdown(_) => event!(level, "shutdown", "shutdown failed"), + } + } +} diff --git a/core/lib/tests/adhoc-uri-normalizer.rs b/core/lib/tests/adhoc-uri-normalizer.rs index ecd45793..bd5b76ff 100644 --- a/core/lib/tests/adhoc-uri-normalizer.rs +++ b/core/lib/tests/adhoc-uri-normalizer.rs @@ -38,10 +38,7 @@ fn test_adhoc_normalizer_works_as_expected () { .mount("/base", routes![foo, bar, not_bar, baz, doggy, rest]) .attach(AdHoc::uri_normalizer()); - let client = match Client::debug(rocket) { - Ok(client) => client, - Err(e) => { e.pretty_print(); panic!("failed to build client"); } - }; + let client = Client::debug(rocket).unwrap(); assert_response!(client: "/foo" => "foo"); assert_response!(client: "/foo/" => "foo"); diff --git a/examples/fairings/src/main.rs b/examples/fairings/src/main.rs index 6a092cad..61208e77 100644 --- a/examples/fairings/src/main.rs +++ b/examples/fairings/src/main.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use rocket::{Rocket, Request, State, Data, Build}; use rocket::fairing::{self, AdHoc, Fairing, Info, Kind}; +use rocket::trace::Traceable; use rocket::http::Method; struct Token(i64); @@ -63,7 +64,7 @@ fn rocket() -> _ { .mount("/", routes![hello, token]) .attach(Counter::default()) .attach(AdHoc::try_on_ignite("Token State", |rocket| async { - info!("Adding token managed state..."); + info!("adding token managed state"); match rocket.figment().extract_inner("token") { Ok(value) => Ok(rocket.manage(Token(value))), Err(_) => Err(rocket) @@ -74,17 +75,20 @@ fn rocket() -> _ { }))) .attach(AdHoc::on_request("PUT Rewriter", |req, _| { Box::pin(async move { - println!(" => Incoming request: {}", req); if req.uri().path() == "/" { - println!(" => Changing method to `PUT`."); - req.set_method(Method::Put); + info_span!("PUT rewriter" => { + req.trace_info(); + info!("changing method to `PUT`"); + req.set_method(Method::Put); + req.trace_info(); + }) } }) })) .attach(AdHoc::on_response("Response Rewriter", |req, res| { Box::pin(async move { if req.uri().path() == "/" { - println!(" => Rewriting response body."); + info!("rewriting response body"); res.set_sized_body(None, Cursor::new("Hello, fairings!")); } }) diff --git a/examples/hello/src/main.rs b/examples/hello/src/main.rs index 10dbc09e..3b99c310 100644 --- a/examples/hello/src/main.rs +++ b/examples/hello/src/main.rs @@ -1,3 +1,5 @@ +use rocket::fairing::AdHoc; + #[macro_use] extern crate rocket; #[cfg(test)] mod tests; @@ -38,6 +40,9 @@ fn wave(name: &str, age: u8) -> String { format!("👋 Hello, {} year old named {}!", age, name) } +#[get("//")] +fn f(a: usize, b: usize) { } + // Note: without the `..` in `opt..`, we'd need to pass `opt.emoji`, `opt.name`. // // Try visiting: diff --git a/testbench/src/server.rs b/testbench/src/server.rs index fb15589d..15c2119a 100644 --- a/testbench/src/server.rs +++ b/testbench/src/server.rs @@ -9,6 +9,7 @@ use rocket::fairing::AdHoc; use rocket::listener::{Bind, DefaultListener}; use rocket::serde::{Deserialize, DeserializeOwned, Serialize}; use rocket::{Build, Ignite, Rocket}; +use rocket::trace::Traceable; use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender}; @@ -135,7 +136,7 @@ impl Token { let sender = IpcSender::::connect(server).unwrap(); let _ = sender.send(Message::Failure); let _ = sender.send(Message::Failure); - e.pretty_print(); + e.trace_error(); std::process::exit(1); }