mirror of https://github.com/rwf2/Rocket.git
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:
parent
8a1c91b7d5
commit
d767694861
|
@ -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<Build>) -> fairing::Result {
|
||||
use rocket::figment::value::magic::RelativePathBuf;
|
||||
|
||||
let configured_dir = rocket.figment()
|
||||
.extract_inner::<RelativePathBuf>("template_dir")
|
||||
.map(|path| path.relative());
|
||||
|
@ -55,14 +54,13 @@ impl Fairing for TemplateFairing {
|
|||
}
|
||||
|
||||
async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
|
||||
use rocket::{figment::Source, yansi::Paint};
|
||||
|
||||
let cm = rocket.state::<ContextManager>()
|
||||
.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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -433,7 +433,7 @@ pub fn bail_with_config_error<T>(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()
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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<Endpoint>, Box<dyn StdError + Send>),
|
||||
|
@ -93,7 +59,7 @@ pub enum ErrorKind {
|
|||
/// Liftoff failed. Contains the Rocket instance that failed to shutdown.
|
||||
Liftoff(
|
||||
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(Arc<Rocket<Orbit>>),
|
||||
|
@ -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<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 {
|
||||
fn from(kind: ErrorKind) -> Self {
|
||||
Error::new(kind)
|
||||
|
@ -121,139 +109,21 @@ impl From<io::Error> 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::<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 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]
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -687,7 +687,7 @@ impl Rocket<Ignite> {
|
|||
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)
|
||||
|
|
|
@ -125,8 +125,8 @@ impl Rocket<Ignite> {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)*) => {{
|
||||
|
|
|
@ -5,6 +5,8 @@ pub mod subscriber;
|
|||
pub mod level;
|
||||
pub mod traceable;
|
||||
|
||||
pub use traceable::Traceable;
|
||||
|
||||
pub fn init<'a, T: Into<Option<&'a crate::Config>>>(_config: T) {
|
||||
#[cfg(all(feature = "trace", debug_assertions))]
|
||||
subscriber::RocketFmt::<subscriber::Pretty>::init(_config.into());
|
||||
|
|
|
@ -89,7 +89,53 @@ impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
|
|||
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<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
|
|||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::<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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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`.");
|
||||
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!"));
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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("/<a>/<b>")]
|
||||
fn f(a: usize, b: usize) { }
|
||||
|
||||
// Note: without the `..` in `opt..`, we'd need to pass `opt.emoji`, `opt.name`.
|
||||
//
|
||||
// Try visiting:
|
||||
|
|
|
@ -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::<Message>::connect(server).unwrap();
|
||||
let _ = sender.send(Message::Failure);
|
||||
let _ = sender.send(Message::Failure);
|
||||
e.pretty_print();
|
||||
e.trace_error();
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue