Remove 'Error' panic-on-drop behavior.

Instead, the `#[launch]` attribute traces the error and panics,
replicating the old behavior in the common case.
This commit is contained in:
Sergio Benitez 2024-05-14 20:20:06 -07:00
parent 8a1c91b7d5
commit d767694861
17 changed files with 274 additions and 262 deletions

View File

@ -1,5 +1,6 @@
use rocket::{Rocket, Build, Orbit}; use rocket::{Rocket, Build, Orbit};
use rocket::fairing::{self, Fairing, Info, Kind}; use rocket::fairing::{self, Fairing, Info, Kind};
use rocket::figment::{Source, value::magic::RelativePathBuf};
use crate::context::{Callback, Context, ContextManager}; use crate::context::{Callback, Context, ContextManager};
use crate::template::DEFAULT_TEMPLATE_DIR; use crate::template::DEFAULT_TEMPLATE_DIR;
@ -31,8 +32,6 @@ impl Fairing for TemplateFairing {
/// template engines. In debug mode, the `ContextManager::new` method /// template engines. In debug mode, the `ContextManager::new` method
/// initializes a directory watcher for auto-reloading of templates. /// initializes a directory watcher for auto-reloading of templates.
async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result { async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
use rocket::figment::value::magic::RelativePathBuf;
let configured_dir = rocket.figment() let configured_dir = rocket.figment()
.extract_inner::<RelativePathBuf>("template_dir") .extract_inner::<RelativePathBuf>("template_dir")
.map(|path| path.relative()); .map(|path| path.relative());
@ -55,14 +54,13 @@ impl Fairing for TemplateFairing {
} }
async fn on_liftoff(&self, rocket: &Rocket<Orbit>) { async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
use rocket::{figment::Source, yansi::Paint};
let cm = rocket.state::<ContextManager>() let cm = rocket.state::<ContextManager>()
.expect("Template ContextManager registered in on_ignite"); .expect("Template ContextManager registered in on_ignite");
info!("{}{}:", "📐 ".emoji(), "Templating".magenta()); info_span!("templating" [icon = "📐"] => {
info_!("directory: {}", Source::from(&*cm.context().root).primary()); info!(directory = %Source::from(&*cm.context().root));
info_!("engines: {:?}", Engines::ENABLED_EXTENSIONS.primary()); info!(engines = ?Engines::ENABLED_EXTENSIONS);
});
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -72,5 +70,4 @@ impl Fairing for TemplateFairing {
cm.reload_if_needed(&self.callback); cm.reload_if_needed(&self.callback);
} }
} }

View File

@ -4,7 +4,7 @@ use proc_macro2::{TokenStream, Span};
use super::EntryAttr; use super::EntryAttr;
use crate::attribute::suppress::Lint; 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 /// `#[rocket::launch]`: generates a `main` function that calls the attributed
/// function to generate a `Rocket` instance. Then calls `.launch()` on the /// 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()); let (vis, mut sig) = (&f.vis, f.sig.clone());
sig.ident = syn::Ident::new("main", sig.ident.span()); sig.ident = syn::Ident::new("main", sig.ident.span());
sig.output = syn::ReturnType::Default; sig.output = syn::parse_quote!(-> #_ExitCode);
sig.asyncness = None; sig.asyncness = None;
Ok(quote_spanned!(block.span() => Ok(quote_spanned!(block.span() =>
#[allow(dead_code)] #f #[allow(dead_code)] #f
#vis #sig { #vis #sig {
let _ = ::rocket::async_main(#launch); #_error::Error::report(::rocket::async_main(#launch))
} }
)) ))
} }

View File

@ -69,6 +69,7 @@ define_exported_paths! {
_request => ::rocket::request, _request => ::rocket::request,
_response => ::rocket::response, _response => ::rocket::response,
_route => ::rocket::route, _route => ::rocket::route,
_error => ::rocket::error,
_catcher => ::rocket::catcher, _catcher => ::rocket::catcher,
_sentinel => ::rocket::sentinel, _sentinel => ::rocket::sentinel,
_form => ::rocket::form::prelude, _form => ::rocket::form::prelude,
@ -84,6 +85,7 @@ define_exported_paths! {
_Box => ::std::boxed::Box, _Box => ::std::boxed::Box,
_Vec => ::std::vec::Vec, _Vec => ::std::vec::Vec,
_Cow => ::std::borrow::Cow, _Cow => ::std::borrow::Cow,
_ExitCode => ::std::process::ExitCode,
BorrowMut => ::std::borrow::BorrowMut, BorrowMut => ::std::borrow::BorrowMut,
Outcome => ::rocket::outcome::Outcome, Outcome => ::rocket::outcome::Outcome,
FromForm => ::rocket::form::FromForm, FromForm => ::rocket::form::FromForm,

View File

@ -433,7 +433,7 @@ pub fn bail_with_config_error<T>(error: figment::Error) -> T {
} }
#[doc(hidden)] #[doc(hidden)]
// FIXME: Remove this funtion. // FIXME: Remove this function.
pub fn pretty_print_error(error: figment::Error) { pub fn pretty_print_error(error: figment::Error) {
error.trace_error() error.trace_error()
} }

View File

@ -340,24 +340,22 @@ fn test_precedence() {
#[test] #[test]
#[cfg(feature = "secrets")] #[cfg(feature = "secrets")]
#[should_panic]
fn test_err_on_non_debug_and_no_secret_key() { fn test_err_on_non_debug_and_no_secret_key() {
figment::Jail::expect_with(|jail| { figment::Jail::expect_with(|jail| {
jail.set_env("ROCKET_PROFILE", "release"); jail.set_env("ROCKET_PROFILE", "release");
let rocket = crate::custom(Config::figment()); 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(()) Ok(())
}); });
} }
#[test] #[test]
#[cfg(feature = "secrets")] #[cfg(feature = "secrets")]
#[should_panic]
fn test_err_on_non_debug2_and_no_secret_key() { fn test_err_on_non_debug2_and_no_secret_key() {
figment::Jail::expect_with(|jail| { figment::Jail::expect_with(|jail| {
jail.set_env("ROCKET_PROFILE", "boop"); jail.set_env("ROCKET_PROFILE", "boop");
let rocket = crate::custom(Config::figment()); 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(()) Ok(())
}); });
} }

View File

@ -1,54 +1,20 @@
//! Types representing various errors that can occur in a Rocket application. //! Types representing various errors that can occur in a Rocket application.
use std::{io, fmt}; use std::{io, fmt, process};
use std::sync::{Arc, atomic::{Ordering, AtomicBool}};
use std::error::Error as StdError; use std::error::Error as StdError;
use std::sync::Arc;
use yansi::Paint;
use figment::Profile; use figment::Profile;
use crate::listener::Endpoint; 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 that occurs during launch.
/// ///
/// An `Error` is returned by [`launch()`](Rocket::launch()) when launching an /// An `Error` is returned by [`launch()`](Rocket::launch()) when launching an
/// application fails or, more rarely, when the runtime fails after launching. /// 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 /// # Usage
/// ///
/// An `Error` value should usually be allowed to `drop` without inspection. /// 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. /// 2. You want to display your own error messages.
pub struct Error { pub struct Error {
handled: AtomicBool, pub(crate) kind: ErrorKind
kind: ErrorKind
} }
/// The kind error that occurred. /// The kind error that occurred.
@ -74,6 +39,7 @@ pub struct Error {
/// `FailedFairing` variants, respectively. /// `FailedFairing` variants, respectively.
#[derive(Debug)] #[derive(Debug)]
#[non_exhaustive] #[non_exhaustive]
// FIXME: Don't expose this. Expose access methods from `Error` instead.
pub enum ErrorKind { pub enum ErrorKind {
/// Binding to the network interface at `.0` failed with error `.1`. /// Binding to the network interface at `.0` failed with error `.1`.
Bind(Option<Endpoint>, Box<dyn StdError + Send>), Bind(Option<Endpoint>, Box<dyn StdError + Send>),
@ -93,7 +59,7 @@ pub enum ErrorKind {
/// Liftoff failed. Contains the Rocket instance that failed to shutdown. /// Liftoff failed. Contains the Rocket instance that failed to shutdown.
Liftoff( Liftoff(
Result<Box<Rocket<Ignite>>, Arc<Rocket<Orbit>>>, Result<Box<Rocket<Ignite>>, Arc<Rocket<Orbit>>>,
Box<dyn StdError + Send + 'static> tokio::task::JoinError,
), ),
/// Shutdown failed. Contains the Rocket instance that failed to shutdown. /// Shutdown failed. Contains the Rocket instance that failed to shutdown.
Shutdown(Arc<Rocket<Orbit>>), Shutdown(Arc<Rocket<Orbit>>),
@ -103,6 +69,28 @@ pub enum ErrorKind {
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Empty; 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<P: Phase>(result: Result<Rocket<P>, 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<ErrorKind> for Error { impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self { fn from(kind: ErrorKind) -> Self {
Error::new(kind) Error::new(kind)
@ -121,139 +109,21 @@ impl From<io::Error> for Error {
} }
} }
impl Error { impl StdError for Error {
#[inline(always)] fn source(&self) -> Option<&(dyn StdError + 'static)> {
pub(crate) fn new(kind: ErrorKind) -> Error { match &self.kind {
Error { handled: AtomicBool::new(false), kind } ErrorKind::Bind(_, e) => Some(&**e),
} ErrorKind::Io(e) => Some(e),
ErrorKind::Collisions(_) => None,
#[inline(always)] ErrorKind::FailedFairings(_) => None,
fn was_handled(&self) -> bool { ErrorKind::InsecureSecretKey(_) => None,
self.handled.load(Ordering::Acquire) ErrorKind::Config(e) => Some(e),
} ErrorKind::SentinelAborts(_) => None,
ErrorKind::Liftoff(_, e) => Some(e),
#[inline(always)] ErrorKind::Shutdown(_) => None,
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::<Self>() {
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<T: fmt::Display>(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 std::error::Error for Error { }
impl fmt::Display for ErrorKind { impl fmt::Display for ErrorKind {
#[inline] #[inline]
@ -275,27 +145,14 @@ impl fmt::Display for ErrorKind {
impl fmt::Debug for Error { impl fmt::Debug for Error {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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 { impl fmt::Display for Error {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.mark_handled(); write!(f, "{}", self.kind)
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());
} }
} }

View File

@ -5,7 +5,6 @@ use std::borrow::Cow;
use std::future::Future; use std::future::Future;
use std::net::IpAddr; use std::net::IpAddr;
use yansi::Paint;
use state::{TypeMap, InitCell}; use state::{TypeMap, InitCell};
use futures::future::BoxFuture; use futures::future::BoxFuture;
use ref_swap::OptionRefSwap; use ref_swap::OptionRefSwap;
@ -1203,20 +1202,3 @@ impl fmt::Debug for Request<'_> {
.finish() .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(())
}
}

View File

@ -687,7 +687,7 @@ impl Rocket<Ignite> {
rocket.shutdown.spawn_listener(&rocket.config.shutdown); rocket.shutdown.spawn_listener(&rocket.config.shutdown);
if let Err(e) = tokio::spawn(Rocket::liftoff(rocket.clone())).await { if let Err(e) = tokio::spawn(Rocket::liftoff(rocket.clone())).await {
let rocket = rocket.try_wait_shutdown().await.map(Box::new); 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) Ok(rocket)

View File

@ -125,8 +125,8 @@ impl Rocket<Ignite> {
let http12 = tokio::task::spawn(rocket.clone().serve12(listener)); let http12 = tokio::task::spawn(rocket.clone().serve12(listener));
let http3 = tokio::task::spawn(rocket.clone().serve3(h3listener)); let http3 = tokio::task::spawn(rocket.clone().serve3(h3listener));
let (r1, r2) = tokio::join!(http12, http3); let (r1, r2) = tokio::join!(http12, http3);
r1.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()), Box::new(e)))??; r2.map_err(|e| ErrorKind::Liftoff(Err(rocket.clone()), e))??;
return Ok(rocket); return Ok(rocket);
} }

View File

@ -18,11 +18,7 @@ macro_rules! declare_macro {
([$d:tt] $name:ident $level:ident) => ( ([$d:tt] $name:ident $level:ident) => (
#[macro_export] #[macro_export]
macro_rules! $name { macro_rules! $name {
($d ($t:tt)*) => ({ ($d ($t:tt)*) => ($crate::tracing::$level!($d ($t)*));
#[allow(unused_imports)]
use $crate::trace::macros::PaintExt as _;
$crate::tracing::$level!($d ($t)*);
})
} }
); );
} }
@ -45,14 +41,31 @@ macro_rules! declare_span_macro {
#[macro_export] #[macro_export]
macro_rules! $name { macro_rules! $name {
($n:literal $d ([ $d ($f:tt)* ])? => $in_scope:expr) => ({ ($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); .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 { macro_rules! event {
($level:expr, $($args:tt)*) => {{ ($level:expr, $($args:tt)*) => {{

View File

@ -5,6 +5,8 @@ pub mod subscriber;
pub mod level; pub mod level;
pub mod traceable; pub mod traceable;
pub use traceable::Traceable;
pub fn init<'a, T: Into<Option<&'a crate::Config>>>(_config: T) { pub fn init<'a, T: Into<Option<&'a crate::Config>>>(_config: T) {
#[cfg(all(feature = "trace", debug_assertions))] #[cfg(all(feature = "trace", debug_assertions))]
subscriber::RocketFmt::<subscriber::Pretty>::init(_config.into()); subscriber::RocketFmt::<subscriber::Pretty>::init(_config.into());

View File

@ -89,7 +89,53 @@ impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
println!("{prefix}{}{} {}", self.emoji("🚀 "), println!("{prefix}{}{} {}", self.emoji("🚀 "),
"Rocket has launched from".paint(style).primary().bold(), "Rocket has launched from".paint(style).primary().bold(),
&data["endpoint"].paint(style).primary().bold().underline()); &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), _ => self.print_pretty(meta, event),
} }
} }
@ -111,14 +157,22 @@ impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
let meta = span.metadata(); let meta = span.metadata();
let style = self.style(meta); let style = self.style(meta);
let prefix = self.prefix(meta);
let emoji = self.emoji(icon); let emoji = self.emoji(icon);
let name = name.paint(style).bold(); let name = name.paint(style).bold();
if !attrs.fields().is_empty() { let fields = self.compact_fields(meta, attrs);
println!("{prefix}{emoji}{name} ({})", 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 { } else {
println!("{prefix}{emoji}{name}"); println!("{fieldless_prefix}");
} }
} }

View File

@ -1,6 +1,9 @@
use crate::fairing::Fairing; use std::error::Error as StdError;
use crate::{route, Catcher, Config, Response, Route};
use crate::sentinel::Sentry;
use crate::util::Formatter; use crate::util::Formatter;
use crate::{route, Catcher, Config, Error, Request, Response, Route};
use crate::error::ErrorKind;
use figment::Figment; use figment::Figment;
use rocket::http::Header; use rocket::http::Header;
@ -131,8 +134,8 @@ impl Traceable for Route {
fn trace(&self, level: Level) { fn trace(&self, level: Level) {
event! { level, "route", event! { level, "route",
name = self.name.as_ref().map(|n| &**n), name = self.name.as_ref().map(|n| &**n),
method = %self.method,
rank = self.rank, rank = self.rank,
method = %self.method,
uri = %self.uri, uri = %self.uri,
uri.base = %self.uri.base(), uri.base = %self.uri.base(),
uri.unmounted = %self.uri.unmounted(), uri.unmounted = %self.uri.unmounted(),
@ -154,7 +157,7 @@ impl Traceable for Catcher {
fn trace(&self, level: Level) { fn trace(&self, level: Level) {
event! { level, "catcher", event! { level, "catcher",
name = self.name.as_ref().map(|n| &**n), 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), Some(code) => write!(f, "{}", code),
None => write!(f, "default"), 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) { 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 { match self {
Message(message) => error!(message), Message(message) => error!(message),
InvalidType(actual, expected) => error!(name: "invalid type", %actual, expected), InvalidType(actual, expected) => error!(%actual, expected, "invalid type"),
InvalidValue(actual, expected) => error!(name: "invalid value", %actual, expected), InvalidValue(actual, expected) => error!(%actual, expected, "invalid value"),
InvalidLength(actual, expected) => error!(name: "invalid length", %actual, expected), InvalidLength(actual, expected) => error!(%actual, expected, "invalid length"),
UnknownVariant(actual, v) => error!(name: "unknown variant", actual, expected = %V(v)), UnknownVariant(actual, v) => error!(actual, expected = %V(v), "unknown variant"),
UnknownField(actual, v) => error!(name: "unknown field", actual, expected = %V(v)), UnknownField(actual, v) => error!(actual, expected = %V(v), "unknown field"),
UnsupportedKey(actual, v) => error!(name: "unsupported key", %actual, expected = &**v), UnsupportedKey(actual, v) => error!(%actual, expected = &**v, "unsupported key"),
MissingField(value) => error!(name: "missing field", value = &**value), MissingField(value) => error!(value = &**value, "missing field"),
DuplicateField(value) => error!(name: "duplicate field", value), DuplicateField(value) => error!(value, "duplicate field"),
ISizeOutOfRange(value) => error!(name: "out of range signed integer", value), ISizeOutOfRange(value) => error!(value, "out of range signed integer"),
USizeOutOfRange(value) => error!(name: "out of range unsigned integer", value), USizeOutOfRange(value) => error!(value, "out of range unsigned integer"),
Unsupported(value) => error!(name: "unsupported type", %value), Unsupported(value) => error!(%value, "unsupported type"),
} }
} }
} }
@ -237,3 +246,94 @@ impl Traceable for Response<'_> {
event!(level, "response", status = self.status().code); 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::<crate::Error>(&**error) {
e.trace(level);
} else if let Some(e) = try_downcast::<figment::Error>(&**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"),
}
}
}

View File

@ -38,10 +38,7 @@ fn test_adhoc_normalizer_works_as_expected () {
.mount("/base", routes![foo, bar, not_bar, baz, doggy, rest]) .mount("/base", routes![foo, bar, not_bar, baz, doggy, rest])
.attach(AdHoc::uri_normalizer()); .attach(AdHoc::uri_normalizer());
let client = match Client::debug(rocket) { let client = Client::debug(rocket).unwrap();
Ok(client) => client,
Err(e) => { e.pretty_print(); panic!("failed to build client"); }
};
assert_response!(client: "/foo" => "foo"); assert_response!(client: "/foo" => "foo");
assert_response!(client: "/foo/" => "foo"); assert_response!(client: "/foo/" => "foo");

View File

@ -6,6 +6,7 @@ use std::sync::Arc;
use rocket::{Rocket, Request, State, Data, Build}; use rocket::{Rocket, Request, State, Data, Build};
use rocket::fairing::{self, AdHoc, Fairing, Info, Kind}; use rocket::fairing::{self, AdHoc, Fairing, Info, Kind};
use rocket::trace::Traceable;
use rocket::http::Method; use rocket::http::Method;
struct Token(i64); struct Token(i64);
@ -63,7 +64,7 @@ fn rocket() -> _ {
.mount("/", routes![hello, token]) .mount("/", routes![hello, token])
.attach(Counter::default()) .attach(Counter::default())
.attach(AdHoc::try_on_ignite("Token State", |rocket| async { .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") { match rocket.figment().extract_inner("token") {
Ok(value) => Ok(rocket.manage(Token(value))), Ok(value) => Ok(rocket.manage(Token(value))),
Err(_) => Err(rocket) Err(_) => Err(rocket)
@ -74,17 +75,20 @@ fn rocket() -> _ {
}))) })))
.attach(AdHoc::on_request("PUT Rewriter", |req, _| { .attach(AdHoc::on_request("PUT Rewriter", |req, _| {
Box::pin(async move { Box::pin(async move {
println!(" => Incoming request: {}", req);
if req.uri().path() == "/" { if req.uri().path() == "/" {
println!(" => Changing method to `PUT`."); info_span!("PUT rewriter" => {
req.trace_info();
info!("changing method to `PUT`");
req.set_method(Method::Put); req.set_method(Method::Put);
req.trace_info();
})
} }
}) })
})) }))
.attach(AdHoc::on_response("Response Rewriter", |req, res| { .attach(AdHoc::on_response("Response Rewriter", |req, res| {
Box::pin(async move { Box::pin(async move {
if req.uri().path() == "/" { if req.uri().path() == "/" {
println!(" => Rewriting response body."); info!("rewriting response body");
res.set_sized_body(None, Cursor::new("Hello, fairings!")); res.set_sized_body(None, Cursor::new("Hello, fairings!"));
} }
}) })

View File

@ -1,3 +1,5 @@
use rocket::fairing::AdHoc;
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
@ -38,6 +40,9 @@ fn wave(name: &str, age: u8) -> String {
format!("👋 Hello, {} year old named {}!", age, name) format!("👋 Hello, {} year old named {}!", age, name)
} }
#[get("/<a>/<b>")]
fn f(a: usize, b: usize) { }
// Note: without the `..` in `opt..`, we'd need to pass `opt.emoji`, `opt.name`. // Note: without the `..` in `opt..`, we'd need to pass `opt.emoji`, `opt.name`.
// //
// Try visiting: // Try visiting:

View File

@ -9,6 +9,7 @@ use rocket::fairing::AdHoc;
use rocket::listener::{Bind, DefaultListener}; use rocket::listener::{Bind, DefaultListener};
use rocket::serde::{Deserialize, DeserializeOwned, Serialize}; use rocket::serde::{Deserialize, DeserializeOwned, Serialize};
use rocket::{Build, Ignite, Rocket}; use rocket::{Build, Ignite, Rocket};
use rocket::trace::Traceable;
use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender}; use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
@ -135,7 +136,7 @@ impl Token {
let sender = IpcSender::<Message>::connect(server).unwrap(); let sender = IpcSender::<Message>::connect(server).unwrap();
let _ = sender.send(Message::Failure); let _ = sender.send(Message::Failure);
let _ = sender.send(Message::Failure); let _ = sender.send(Message::Failure);
e.pretty_print(); e.trace_error();
std::process::exit(1); std::process::exit(1);
} }