Remove old 'log' macros. Color via subscriber.

This commit:
  - Removes painting outside trace subscriber in core.
  - Removes all non-subscriber uses of yansi.
  - Removes all uses of old log macros.
  - Fix trace exports.
This commit is contained in:
Sergio Benitez 2024-05-15 04:07:47 -07:00
parent d767694861
commit b12b7f27d7
46 changed files with 350 additions and 384 deletions

View File

@ -1,13 +1,11 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use rocket::{error, info_, Build, Ignite, Phase, Rocket, Sentinel, Orbit}; use rocket::{error, Build, Ignite, Phase, Rocket, Sentinel, Orbit};
use rocket::fairing::{self, Fairing, Info, Kind}; use rocket::fairing::{self, Fairing, Info, Kind};
use rocket::request::{FromRequest, Outcome, Request}; use rocket::request::{FromRequest, Outcome, Request};
use rocket::http::Status;
use rocket::yansi::Paint;
use rocket::figment::providers::Serialized; use rocket::figment::providers::Serialized;
use rocket::http::Status;
use crate::Pool; use crate::Pool;
@ -122,10 +120,10 @@ pub trait Database: From<Self::Pool> + DerefMut<Target = Self::Pool> + Send + Sy
return Some(db); return Some(db);
} }
let dbtype = std::any::type_name::<Self>().bold().primary(); let conn = std::any::type_name::<Self>();
error!("Attempted to fetch unattached database `{}`.", dbtype); error!("`{conn}::init()` is not attached\n\
info_!("`{}{}` fairing must be attached prior to using this database.", the fairing must be attached to use `{conn}` in routes.");
dbtype.linger(), "::init()".resetting());
None None
} }
} }
@ -267,7 +265,7 @@ impl<D: Database> Fairing for Initializer<D> {
match <D::Pool>::init(&figment).await { match <D::Pool>::init(&figment).await {
Ok(pool) => Ok(rocket.manage(D::from(pool))), Ok(pool) => Ok(rocket.manage(D::from(pool))),
Err(e) => { Err(e) => {
error!("failed to initialize database: {}", e); error!("database initialization failed: {e}");
Err(rocket) Err(rocket)
} }
} }

View File

@ -49,16 +49,17 @@ impl Context {
Ok(_) | Err(_) => continue, Ok(_) | Err(_) => continue,
}; };
let (name, data_type_str) = split_path(&root, entry.path()); let (template, data_type_str) = split_path(&root, entry.path());
if let Some(info) = templates.get(&*name) { if let Some(info) = templates.get(&*template) {
warn_!("Template name '{}' does not have a unique source.", name); warn!(
match info.path { %template,
Some(ref path) => info_!("Existing path: {:?}", path), first_path = %entry.path().display(),
None => info_!("Existing Content-Type: {}", info.data_type), second_path = info.path.as_ref().map(|p| display(p.display())),
} data_type = %info.data_type,
"Template name '{template}' can refer to multiple templates.\n\
First path will be used. Second path is ignored."
);
info_!("Additional path: {:?}", entry.path());
warn_!("Keeping existing template '{}'.", name);
continue; continue;
} }
@ -66,7 +67,7 @@ impl Context {
.and_then(|ext| ContentType::from_extension(ext)) .and_then(|ext| ContentType::from_extension(ext))
.unwrap_or(ContentType::Text); .unwrap_or(ContentType::Text);
templates.insert(name, TemplateInfo { templates.insert(template, TemplateInfo {
path: Some(entry.into_path()), path: Some(entry.into_path()),
engine_ext: ext, engine_ext: ext,
data_type, data_type,
@ -75,9 +76,8 @@ impl Context {
} }
let mut engines = Engines::init(&templates)?; let mut engines = Engines::init(&templates)?;
if let Err(e) = callback(&mut engines) { if let Err(reason) = callback(&mut engines) {
error_!("Template customization callback failed."); error!(%reason, "template customization callback failed");
error_!("{}", e);
return None; return None;
} }
@ -151,9 +151,8 @@ mod manager {
let watcher = match watcher { let watcher = match watcher {
Ok(watcher) => Some((watcher, Mutex::new(rx))), Ok(watcher) => Some((watcher, Mutex::new(rx))),
Err(e) => { Err(e) => {
warn!("Failed to enable live template reloading: {}", e); warn!("live template reloading initialization failed: {e}\n\
debug_!("Reload error: {:?}", e); live template reloading is unavailable");
warn_!("Live template reloading is unavailable.");
None None
} }
}; };
@ -182,13 +181,13 @@ mod manager {
.map(|(_, rx)| rx.lock().expect("fsevents lock").try_iter().count() > 0); .map(|(_, rx)| rx.lock().expect("fsevents lock").try_iter().count() > 0);
if let Some(true) = templates_changes { if let Some(true) = templates_changes {
info_!("Change detected: reloading templates."); debug!("template change detected: reloading templates");
let root = self.context().root.clone(); let root = self.context().root.clone();
if let Some(new_ctxt) = Context::initialize(&root, callback) { if let Some(new_ctxt) = Context::initialize(&root, callback) {
*self.context_mut() = new_ctxt; *self.context_mut() = new_ctxt;
} else { } else {
warn_!("An error occurred while reloading templates."); warn!("error while reloading template\n\
warn_!("Existing templates will remain active."); existing templates will remain active.")
}; };
} }
} }

View File

@ -11,11 +11,11 @@ impl Engine for Handlebars<'static> {
fn init<'a>(templates: impl Iterator<Item = (&'a str, &'a Path)>) -> Option<Self> { fn init<'a>(templates: impl Iterator<Item = (&'a str, &'a Path)>) -> Option<Self> {
let mut hb = Handlebars::new(); let mut hb = Handlebars::new();
let mut ok = true; let mut ok = true;
for (name, path) in templates { for (template, path) in templates {
if let Err(e) = hb.register_template_file(name, path) { if let Err(e) = hb.register_template_file(template, path) {
error!("Handlebars template '{}' failed to register.", name); error!(template, path = %path.display(),
error_!("{}", e); "failed to register Handlebars template: {e}");
info_!("Template path: '{}'.", path.to_string_lossy());
ok = false; ok = false;
} }
} }
@ -23,14 +23,14 @@ impl Engine for Handlebars<'static> {
ok.then_some(hb) ok.then_some(hb)
} }
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> { fn render<C: Serialize>(&self, template: &str, context: C) -> Option<String> {
if self.get_template(name).is_none() { if self.get_template(template).is_none() {
error_!("Handlebars template '{}' does not exist.", name); error!(template, "requested Handlebars template does not exist.");
return None; return None;
} }
Handlebars::render(self, name, &context) Handlebars::render(self, template, &context)
.map_err(|e| error_!("Handlebars: {}", e)) .map_err(|e| error!("Handlebars render error: {}", e))
.ok() .ok()
} }
} }

View File

@ -43,23 +43,22 @@ impl Engine for Environment<'static> {
} }
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> { fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> {
let template = match self.get_template(name) { let Ok(template) = self.get_template(name) else {
Ok(template) => template, error!("Minijinja template '{name}' was not found.");
Err(e) => {
error_!("Minijinja template '{name}' error: {e}");
return None; return None;
}
}; };
match template.render(context) { match template.render(context) {
Ok(result) => Some(result), Ok(result) => Some(result),
Err(e) => { Err(e) => {
error_!("Error rendering Minijinja template '{name}': {e}"); error_span!("Error rendering Minijinja template '{name}': {e}" => {
let mut error = &e as &dyn std::error::Error; let mut error = &e as &dyn std::error::Error;
while let Some(source) = error.source() { while let Some(source) = error.source() {
error_!("caused by: {source}"); error_!("caused by: {source}");
error = source; error = source;
} }
});
None None
} }
} }

View File

@ -21,13 +21,13 @@ impl Engine for Tera {
// Finally try to tell Tera about all of the templates. // Finally try to tell Tera about all of the templates.
if let Err(e) = tera.add_template_files(files) { if let Err(e) = tera.add_template_files(files) {
error!("Failed to initialize Tera templating."); error_span!("Tera templating initialization failed" => {
let mut error = Some(&e as &dyn Error); let mut error = Some(&e as &dyn Error);
while let Some(err) = error { while let Some(err) = error {
info_!("{}", err); error!("{err}");
error = err.source(); error = err.source();
} }
});
None None
} else { } else {
@ -35,25 +35,26 @@ impl Engine for Tera {
} }
} }
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> { fn render<C: Serialize>(&self, template: &str, context: C) -> Option<String> {
if self.get_template(name).is_err() { if self.get_template(template).is_err() {
error_!("Tera template '{}' does not exist.", name); error!(template, "requested template does not exist");
return None; return None;
}; };
let tera_ctx = Context::from_serialize(context) let tera_ctx = Context::from_serialize(context)
.map_err(|e| error_!("Tera context error: {}.", e)) .map_err(|e| error!("Tera context error: {}.", e))
.ok()?; .ok()?;
match Tera::render(self, name, &tera_ctx) { match Tera::render(self, template, &tera_ctx) {
Ok(string) => Some(string), Ok(string) => Some(string),
Err(e) => { Err(e) => {
error_!("Error rendering Tera template '{name}': {e}"); error_span!("failed to render Tera template {name}" [template] => {
let mut error = Some(&e as &dyn Error); let mut error = Some(&e as &dyn Error);
while let Some(err) = error { while let Some(err) = error {
error_!("{}", err); error!("{err}");
error = err.source(); error = err.source();
} }
});
None None
} }

View File

@ -48,7 +48,7 @@ impl Fairing for TemplateFairing {
if let Some(ctxt) = Context::initialize(&path, &self.callback) { if let Some(ctxt) = Context::initialize(&path, &self.callback) {
Ok(rocket.manage(ContextManager::new(ctxt))) Ok(rocket.manage(ContextManager::new(ctxt)))
} else { } else {
error_!("Template initialization failed. Aborting launch."); error!("Template initialization failed. Aborting launch.");
Err(rocket) Err(rocket)
} }
} }
@ -57,7 +57,7 @@ impl Fairing for TemplateFairing {
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_span!("templating" [icon = "📐"] => { info_span!("templating" => {
info!(directory = %Source::from(&*cm.context().root)); info!(directory = %Source::from(&*cm.context().root));
info!(engines = ?Engines::ENABLED_EXTENSIONS); info!(engines = ?Engines::ENABLED_EXTENSIONS);
}); });

View File

@ -5,7 +5,6 @@ use rocket::{Request, Rocket, Ignite, Sentinel};
use rocket::http::{Status, ContentType}; use rocket::http::{Status, ContentType};
use rocket::request::{self, FromRequest}; use rocket::request::{self, FromRequest};
use rocket::serde::Serialize; use rocket::serde::Serialize;
use rocket::yansi::Paint;
use crate::{Template, context::ContextManager}; use crate::{Template, context::ContextManager};
@ -136,11 +135,11 @@ impl fmt::Debug for Metadata<'_> {
impl Sentinel for Metadata<'_> { impl Sentinel for Metadata<'_> {
fn abort(rocket: &Rocket<Ignite>) -> bool { fn abort(rocket: &Rocket<Ignite>) -> bool {
if rocket.state::<ContextManager>().is_none() { if rocket.state::<ContextManager>().is_none() {
let md = "Metadata".primary().bold(); error!(
let fairing = "Template::fairing()".primary().bold(); "uninitialized template context: missing `Template::fairing()`.\n\
error!("requested `{}` guard without attaching `{}`.", md, fairing); To use templates, you must attach `Template::fairing()`."
info_!("To use or query templates, you must attach `{}`.", fairing); );
info_!("See the `Template` documentation for more information.");
return true; return true;
} }
@ -159,9 +158,11 @@ impl<'r> FromRequest<'r> for Metadata<'r> {
request.rocket().state::<ContextManager>() request.rocket().state::<ContextManager>()
.map(|cm| request::Outcome::Success(Metadata(cm))) .map(|cm| request::Outcome::Success(Metadata(cm)))
.unwrap_or_else(|| { .unwrap_or_else(|| {
error_!("Uninitialized template context: missing fairing."); error!(
info_!("To use templates, you must attach `Template::fairing()`."); "uninitialized template context: missing `Template::fairing()`.\n\
info_!("See the `Template` documentation for more information."); To use templates, you must attach `Template::fairing()`."
);
request::Outcome::Error((Status::InternalServerError, ())) request::Outcome::Error((Status::InternalServerError, ()))
}) })
} }

View File

@ -7,8 +7,8 @@ use rocket::fairing::Fairing;
use rocket::response::{self, Responder}; use rocket::response::{self, Responder};
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
use rocket::figment::{value::Value, error::Error}; use rocket::figment::{value::Value, error::Error};
use rocket::trace::Traceable;
use rocket::serde::Serialize; use rocket::serde::Serialize;
use rocket::yansi::Paint;
use crate::Engines; use crate::Engines;
use crate::fairing::TemplateFairing; use crate::fairing::TemplateFairing;
@ -218,10 +218,13 @@ impl Template {
pub fn show<S, C>(rocket: &Rocket<Orbit>, name: S, context: C) -> Option<String> pub fn show<S, C>(rocket: &Rocket<Orbit>, name: S, context: C) -> Option<String>
where S: Into<Cow<'static, str>>, C: Serialize where S: Into<Cow<'static, str>>, C: Serialize
{ {
let ctxt = rocket.state::<ContextManager>().map(ContextManager::context).or_else(|| { let ctxt = rocket.state::<ContextManager>()
warn!("Uninitialized template context: missing fairing."); .map(ContextManager::context)
info!("To use templates, you must attach `Template::fairing()`."); .or_else(|| {
info!("See the `Template` documentation for more information."); error!("Uninitialized template context: missing fairing.\n\
To use templates, you must attach `Template::fairing()`.\n\
See the `Template` documentation for more information.");
None None
})?; })?;
@ -233,22 +236,24 @@ impl Template {
/// `Template::show()`. /// `Template::show()`.
#[inline(always)] #[inline(always)]
pub(crate) fn finalize(self, ctxt: &Context) -> Result<(ContentType, String), Status> { pub(crate) fn finalize(self, ctxt: &Context) -> Result<(ContentType, String), Status> {
let name = &*self.name; let template = &*self.name;
let info = ctxt.templates.get(name).ok_or_else(|| { let info = ctxt.templates.get(template).ok_or_else(|| {
let ts: Vec<_> = ctxt.templates.keys().map(|s| s.as_str()).collect(); let ts: Vec<_> = ctxt.templates.keys().map(|s| s.as_str()).collect();
error_!("Template '{}' does not exist.", name); error!(
info_!("Known templates: {}.", ts.join(", ")); %template, search_path = %ctxt.root.display(), known_templates = ?ts,
info_!("Searched in {:?}.", ctxt.root); "requested template not found"
);
Status::InternalServerError Status::InternalServerError
})?; })?;
let value = self.value.map_err(|e| { let value = self.value.map_err(|e| {
error_!("Template context failed to serialize: {}.", e); error_span!("template context failed to serialize" => e.trace_error());
Status::InternalServerError Status::InternalServerError
})?; })?;
let string = ctxt.engines.render(name, info, value).ok_or_else(|| { let string = ctxt.engines.render(template, info, value).ok_or_else(|| {
error_!("Template '{}' failed to render.", name); error!(template, "template failed to render");
Status::InternalServerError Status::InternalServerError
})?; })?;
@ -264,9 +269,11 @@ impl<'r> Responder<'r, 'static> for Template {
let ctxt = req.rocket() let ctxt = req.rocket()
.state::<ContextManager>() .state::<ContextManager>()
.ok_or_else(|| { .ok_or_else(|| {
error_!("Uninitialized template context: missing fairing."); error!(
info_!("To use templates, you must attach `Template::fairing()`."); "uninitialized template context: missing `Template::fairing()`.\n\
info_!("See the `Template` documentation for more information."); To use templates, you must attach `Template::fairing()`."
);
Status::InternalServerError Status::InternalServerError
})?; })?;
@ -277,11 +284,11 @@ impl<'r> Responder<'r, 'static> for Template {
impl Sentinel for Template { impl Sentinel for Template {
fn abort(rocket: &Rocket<Ignite>) -> bool { fn abort(rocket: &Rocket<Ignite>) -> bool {
if rocket.state::<ContextManager>().is_none() { if rocket.state::<ContextManager>().is_none() {
let template = "Template".primary().bold(); error!(
let fairing = "Template::fairing()".primary().bold(); "Missing `Template::fairing()`.\n\
error!("returning `{}` responder without attaching `{}`.", template, fairing); To use templates, you must attach `Template::fairing()`."
info_!("To use or query templates, you must attach `{}`.", fairing); );
info_!("See the `Template` documentation for more information.");
return true; return true;
} }

View File

@ -1,14 +1,15 @@
use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use std::marker::PhantomData;
use rocket::{Phase, Rocket, Ignite, Sentinel}; use rocket::{Phase, Rocket, Ignite, Sentinel};
use rocket::fairing::{AdHoc, Fairing}; use rocket::fairing::{AdHoc, Fairing};
use rocket::request::{Request, Outcome, FromRequest}; use rocket::request::{Request, Outcome, FromRequest};
use rocket::outcome::IntoOutcome; use rocket::outcome::IntoOutcome;
use rocket::http::Status; use rocket::http::Status;
use rocket::trace::Traceable;
use rocket::tokio::sync::{OwnedSemaphorePermit, Semaphore, Mutex};
use rocket::tokio::time::timeout; use rocket::tokio::time::timeout;
use rocket::tokio::sync::{OwnedSemaphorePermit, Semaphore, Mutex};
use crate::{Config, Poolable, Error}; use crate::{Config, Poolable, Error};
@ -60,45 +61,50 @@ async fn run_blocking<F, R>(job: F) -> R
} }
} }
macro_rules! dberr {
($msg:literal, $db_name:expr, $efmt:literal, $error:expr, $rocket:expr) => ({
rocket::error!(concat!("database ", $msg, " error for pool named `{}`"), $db_name);
error_!($efmt, $error);
return Err($rocket);
});
}
impl<K: 'static, C: Poolable> ConnectionPool<K, C> { impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
pub fn fairing(fairing_name: &'static str, db: &'static str) -> impl Fairing { pub fn fairing(fairing_name: &'static str, database: &'static str) -> impl Fairing {
AdHoc::try_on_ignite(fairing_name, move |rocket| async move { AdHoc::try_on_ignite(fairing_name, move |rocket| async move {
run_blocking(move || { run_blocking(move || {
let config = match Config::from(db, &rocket) { let config = match Config::from(database, &rocket) {
Ok(config) => config, Ok(config) => config,
Err(e) => dberr!("config", db, "{}", e, rocket), Err(e) => {
error_span!("database configuration error" [database] => e.trace_error());
return Err(rocket);
}
}; };
let pool_size = config.pool_size; let pool_size = config.pool_size;
match C::pool(db, &rocket) { match C::pool(database, &rocket) {
Ok(pool) => Ok(rocket.manage(ConnectionPool::<K, C> { Ok(pool) => Ok(rocket.manage(ConnectionPool::<K, C> {
config, config,
pool: Some(pool), pool: Some(pool),
semaphore: Arc::new(Semaphore::new(pool_size as usize)), semaphore: Arc::new(Semaphore::new(pool_size as usize)),
_marker: PhantomData, _marker: PhantomData,
})), })),
Err(Error::Config(e)) => dberr!("config", db, "{}", e, rocket), Err(Error::Config(e)) => {
Err(Error::Pool(e)) => dberr!("pool init", db, "{}", e, rocket), error_span!("database configuration error" [database] => e.trace_error());
Err(Error::Custom(e)) => dberr!("pool manager", db, "{:?}", e, rocket), Err(rocket)
}
Err(Error::Pool(reason)) => {
error!(database, %reason, "database pool initialization failed");
Err(rocket)
}
Err(Error::Custom(reason)) => {
error!(database, ?reason, "database pool failure");
Err(rocket)
}
} }
}).await }).await
}) })
} }
pub async fn get(&self) -> Option<Connection<K, C>> { pub async fn get(&self) -> Option<Connection<K, C>> {
let type_name = std::any::type_name::<K>();
let duration = std::time::Duration::from_secs(self.config.timeout as u64); let duration = std::time::Duration::from_secs(self.config.timeout as u64);
let permit = match timeout(duration, self.semaphore.clone().acquire_owned()).await { let permit = match timeout(duration, self.semaphore.clone().acquire_owned()).await {
Ok(p) => p.expect("internal invariant broken: semaphore should not be closed"), Ok(p) => p.expect("internal invariant broken: semaphore should not be closed"),
Err(_) => { Err(_) => {
error_!("database connection retrieval timed out"); error!(type_name, "database connection retrieval timed out");
return None; return None;
} }
}; };
@ -113,7 +119,7 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
_marker: PhantomData, _marker: PhantomData,
}), }),
Err(e) => { Err(e) => {
error_!("failed to get a database connection: {}", e); error!(type_name, "failed to get a database connection: {}", e);
None None
} }
} }
@ -125,12 +131,12 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
Some(pool) => match pool.get().await { Some(pool) => match pool.get().await {
Some(conn) => Some(conn), Some(conn) => Some(conn),
None => { None => {
error_!("no connections available for `{}`", std::any::type_name::<K>()); error!("no connections available for `{}`", std::any::type_name::<K>());
None None
} }
}, },
None => { None => {
error_!("missing database fairing for `{}`", std::any::type_name::<K>()); error!("missing database fairing for `{}`", std::any::type_name::<K>());
None None
} }
} }
@ -165,6 +171,7 @@ impl<K: 'static, C: Poolable> Connection<K, C> {
let conn = connection.as_mut() let conn = connection.as_mut()
.expect("internal invariant broken: self.connection is Some"); .expect("internal invariant broken: self.connection is Some");
f(conn) f(conn)
}).await }).await
} }
@ -212,7 +219,9 @@ impl<'r, K: 'static, C: Poolable> FromRequest<'r> for Connection<K, C> {
match request.rocket().state::<ConnectionPool<K, C>>() { match request.rocket().state::<ConnectionPool<K, C>>() {
Some(c) => c.get().await.or_error((Status::ServiceUnavailable, ())), Some(c) => c.get().await.or_error((Status::ServiceUnavailable, ())),
None => { None => {
error_!("Missing database fairing for `{}`", std::any::type_name::<K>()); let conn = std::any::type_name::<K>();
error!("`{conn}::fairing()` is not attached\n\
the fairing must be attached to use `{conn} in routes.");
Outcome::Error((Status::InternalServerError, ())) Outcome::Error((Status::InternalServerError, ()))
} }
} }
@ -221,15 +230,10 @@ impl<'r, K: 'static, C: Poolable> FromRequest<'r> for Connection<K, C> {
impl<K: 'static, C: Poolable> Sentinel for Connection<K, C> { impl<K: 'static, C: Poolable> Sentinel for Connection<K, C> {
fn abort(rocket: &Rocket<Ignite>) -> bool { fn abort(rocket: &Rocket<Ignite>) -> bool {
use rocket::yansi::Paint;
if rocket.state::<ConnectionPool<K, C>>().is_none() { if rocket.state::<ConnectionPool<K, C>>().is_none() {
let conn = std::any::type_name::<K>().primary().bold(); let conn = std::any::type_name::<K>();
error!("requesting `{}` DB connection without attaching `{}{}`.", error!("`{conn}::fairing()` is not attached\n\
conn, conn.linger(), "::fairing()".resetting()); the fairing must be attached to use `{conn} in routes.");
info_!("Attach `{}{}` to use database connection pooling.",
conn.linger(), "::fairing()".resetting());
return true; return true;
} }

View File

@ -105,8 +105,10 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
)* )*
if !__e.is_empty() { if !__e.is_empty() {
::rocket::warn_!("Query string failed to match route declaration."); ::rocket::info_span!("query string failed to match route declaration" => {
for _err in __e { ::rocket::warn_!("{}", _err); } for _err in __e { ::rocket::info!("{_err}"); }
});
return #Outcome::Forward((#__data, #Status::UnprocessableEntity)); return #Outcome::Forward((#__data, #Status::UnprocessableEntity));
} }
@ -125,11 +127,11 @@ fn request_guard_decl(guard: &Guard) -> TokenStream {
let #ident: #ty = match <#ty as #FromRequest>::from_request(#__req).await { let #ident: #ty = match <#ty as #FromRequest>::from_request(#__req).await {
#Outcome::Success(__v) => __v, #Outcome::Success(__v) => __v,
#Outcome::Forward(__e) => { #Outcome::Forward(__e) => {
::rocket::warn_!("Request guard `{}` is forwarding.", stringify!(#ty)); ::rocket::info!(type_name = stringify!(#ty), "guard forwarding");
return #Outcome::Forward((#__data, __e)); return #Outcome::Forward((#__data, __e));
}, },
#Outcome::Error((__c, __e)) => { #Outcome::Error((__c, __e)) => {
::rocket::warn_!("Request guard `{}` failed: {:?}.", stringify!(#ty), __e); ::rocket::info!(type_name = stringify!(#ty), "guard failed: {__e:?}");
return #Outcome::Error(__c); return #Outcome::Error(__c);
} }
}; };
@ -145,8 +147,9 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
// Returned when a dynamic parameter fails to parse. // Returned when a dynamic parameter fails to parse.
let parse_error = quote!({ let parse_error = quote!({
::rocket::warn_!("Parameter guard `{}: {}` is forwarding: {:?}.", ::rocket::info!(name: "forward",
#name, stringify!(#ty), __error); reason = %__error, parameter = #name, "type" = stringify!(#ty),
"parameter forwarding");
#Outcome::Forward((#__data, #Status::UnprocessableEntity)) #Outcome::Forward((#__data, #Status::UnprocessableEntity))
}); });
@ -161,9 +164,11 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
#_Err(__error) => return #parse_error, #_Err(__error) => return #parse_error,
}, },
#_None => { #_None => {
::rocket::error_!("Internal invariant broken: dyn param {} not found.", #i); ::rocket::error!(
::rocket::error_!("Please report this to the Rocket issue tracker."); "Internal invariant broken: dyn param {} not found.\n\
::rocket::error_!("https://github.com/rwf2/Rocket/issues"); Please report this to the Rocket issue tracker.\n\
https://github.com/rwf2/Rocket/issues", #i);
return #Outcome::Forward((#__data, #Status::InternalServerError)); return #Outcome::Forward((#__data, #Status::InternalServerError));
} }
} }
@ -188,11 +193,11 @@ fn data_guard_decl(guard: &Guard) -> TokenStream {
let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await { let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await {
#Outcome::Success(__d) => __d, #Outcome::Success(__d) => __d,
#Outcome::Forward((__d, __e)) => { #Outcome::Forward((__d, __e)) => {
::rocket::warn_!("Data guard `{}` is forwarding.", stringify!(#ty)); ::rocket::info!(type_name = stringify!(#ty), "data guard forwarding");
return #Outcome::Forward((__d, __e)); return #Outcome::Forward((__d, __e));
} }
#Outcome::Error((__c, __e)) => { #Outcome::Error((__c, __e)) => {
::rocket::warn_!("Data guard `{}` failed: {:?}.", stringify!(#ty), __e); ::rocket::info!(type_name = stringify!(#ty), "data guard failed: {__e:?}");
return #Outcome::Error(__c); return #Outcome::Error(__c);
} }
}; };

View File

@ -28,3 +28,15 @@ pub enum PathError {
/// The segment ended with the wrapped invalid character. /// The segment ended with the wrapped invalid character.
BadEnd(char), BadEnd(char),
} }
impl fmt::Display for PathError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathError::BadStart(c) => write!(f, "invalid initial character: {c:?}"),
PathError::BadChar(c) => write!(f, "invalid character: {c:?}"),
PathError::BadEnd(c) => write!(f, "invalid terminal character: {c:?}"),
}
}
}
impl std::error::Error for PathError { }

View File

@ -8,8 +8,6 @@ use crate::request::Request;
use crate::http::{Status, ContentType, uri}; use crate::http::{Status, ContentType, uri};
use crate::catcher::{Handler, BoxFuture}; use crate::catcher::{Handler, BoxFuture};
use yansi::Paint;
/// An error catching route. /// An error catching route.
/// ///
/// Catchers are routes that run when errors are produced by the application. /// Catchers are routes that run when errors are produced by the application.
@ -342,21 +340,6 @@ impl From<StaticInfo> for Catcher {
} }
} }
impl fmt::Display for Catcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref n) = self.name {
write!(f, "{}{}{} ", "(".cyan(), n.primary(), ")".cyan())?;
}
write!(f, "{} ", self.base.path().green())?;
match self.code {
Some(code) => write!(f, "{}", code.blue()),
None => write!(f, "{}", "default".blue()),
}
}
}
impl fmt::Debug for Catcher { impl fmt::Debug for Catcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Catcher") f.debug_struct("Catcher")

View File

@ -10,7 +10,7 @@ use crate::config::{ShutdownConfig, Ident, CliColors};
use crate::request::{self, Request, FromRequest}; use crate::request::{self, Request, FromRequest};
use crate::http::uncased::Uncased; use crate::http::uncased::Uncased;
use crate::data::Limits; use crate::data::Limits;
use crate::trace::traceable::Traceable; use crate::trace::Traceable;
/// Rocket server configuration. /// Rocket server configuration.
/// ///

View File

@ -37,7 +37,7 @@ impl<const N: usize, R: AsyncRead + Unpin> Peekable<N, R> {
}, },
Ok(_) => { /* continue */ }, Ok(_) => { /* continue */ },
Err(e) => { Err(e) => {
error_!("Failed to read into peek buffer: {:?}.", e); error!("failed to read into peek buffer: {:?}.", e);
break; break;
} }
} }

View File

@ -7,7 +7,7 @@ use std::sync::Arc;
use figment::Profile; use figment::Profile;
use crate::listener::Endpoint; use crate::listener::Endpoint;
use crate::trace::traceable::Traceable; use crate::trace::Traceable;
use crate::{Ignite, Orbit, Phase, Rocket}; use crate::{Ignite, Orbit, Phase, Rocket};
/// An error that occurs during launch. /// An error that occurs during launch.
@ -170,12 +170,9 @@ impl fmt::Display for Empty {
impl StdError for Empty { } impl StdError for Empty { }
/// Log an error that occurs during request processing struct ServerError<'a>(&'a (dyn StdError + 'static));
#[track_caller]
pub(crate) fn log_server_error(error: &(dyn StdError + 'static)) {
struct ServerError<'a>(&'a (dyn StdError + 'static));
impl fmt::Display for ServerError<'_> { impl fmt::Display for ServerError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let error = &self.0; let error = &self.0;
if let Some(e) = error.downcast_ref::<hyper::Error>() { if let Some(e) = error.downcast_ref::<hyper::Error>() {
@ -196,20 +193,25 @@ pub(crate) fn log_server_error(error: &(dyn StdError + 'static)) {
Ok(()) Ok(())
} }
} }
/// Log an error that occurs during request processing
#[track_caller]
pub(crate) fn log_server_error(error: &(dyn StdError + 'static)) {
let mut error: &(dyn StdError + 'static) = error; let mut error: &(dyn StdError + 'static) = error;
if error.downcast_ref::<hyper::Error>().is_some() { if error.downcast_ref::<hyper::Error>().is_some() {
warn_span!("minor server error" ["{}", ServerError(error)] => {
while let Some(source) = error.source() {
error = source;
warn!("{}", ServerError(error)); warn!("{}", ServerError(error));
while let Some(source) = error.source() {
error = source;
warn_!("{}", ServerError(error));
} }
});
} else { } else {
error!("{}", ServerError(error)); error_span!("server error" ["{}", ServerError(error)] => {
while let Some(source) = error.source() { while let Some(source) = error.source() {
error = source; error = source;
error_!("{}", ServerError(error)); error!("{}", ServerError(error));
} }
});
} }
} }

View File

@ -387,10 +387,10 @@ impl AdHoc {
// This allows incremental compatibility updates. Otherwise, // This allows incremental compatibility updates. Otherwise,
// rewrite the request URI to remove the `/`. // rewrite the request URI to remove the `/`.
if !self.routes(req.rocket()).iter().any(|r| r.matches(req)) { if !self.routes(req.rocket()).iter().any(|r| r.matches(req)) {
let normal = req.uri().clone().into_normalized_nontrailing(); let normalized = req.uri().clone().into_normalized_nontrailing();
warn!("Incoming request URI was normalized for compatibility."); warn!(original = %req.uri(), %normalized,
info_!("{} -> {}", req.uri(), normal); "incoming request URI normalized for compatibility");
req.set_uri(normal); req.set_uri(normalized);
} }
} }
} }

View File

@ -115,7 +115,7 @@ impl<'r> Iterator for RawStrParser<'r> {
} }
}; };
trace_!("url-encoded field: {:?}", (name, value)); trace!(%name, %value, "url-encoded field");
let name_val = match (name.url_decode_lossy(), value.url_decode_lossy()) { let name_val = match (name.url_decode_lossy(), value.url_decode_lossy()) {
(Borrowed(name), Borrowed(val)) => (name, val), (Borrowed(name), Borrowed(val)) => (name, val),
(Borrowed(name), Owned(v)) => (name, self.buffer.push(v)), (Borrowed(name), Owned(v)) => (name, self.buffer.push(v)),
@ -168,7 +168,7 @@ impl<'r, 'i> MultipartParser<'r, 'i> {
}; };
// A field with a content-type is data; one without is "value". // A field with a content-type is data; one without is "value".
trace_!("multipart field: {:?}", field); trace!(?field, "multipart field");
let content_type = field.content_type().and_then(|m| m.as_ref().parse().ok()); let content_type = field.content_type().and_then(|m| m.as_ref().parse().ok());
let field = if let Some(content_type) = content_type { let field = if let Some(content_type) = content_type {
let (name, file_name) = match (field.name(), field.file_name()) { let (name, file_name) = match (field.name(), field.file_name()) {

View File

@ -122,7 +122,6 @@ pub use figment;
pub use time; pub use time;
pub use tracing; pub use tracing;
#[doc(hidden)]
#[macro_use] #[macro_use]
pub mod trace; pub mod trace;
#[macro_use] #[macro_use]

View File

@ -1,7 +1,7 @@
use futures::future::{FutureExt, Future}; use futures::future::{FutureExt, Future};
use crate::{route, catcher, Rocket, Orbit, Request, Response, Data}; use crate::{route, catcher, Rocket, Orbit, Request, Response, Data};
use crate::trace::traceable::Traceable; use crate::trace::Traceable;
use crate::util::Formatter; use crate::util::Formatter;
use crate::data::IoHandler; use crate::data::IoHandler;
use crate::http::{Method, Status, Header}; use crate::http::{Method, Status, Header};
@ -275,7 +275,7 @@ impl Rocket<Orbit> {
.map(|result| result.map_err(Some)) .map(|result| result.map_err(Some))
.unwrap_or_else(|| Err(None)) .unwrap_or_else(|| Err(None))
} else { } else {
info!(name: "catcher", name = "rocket::default", code = status.code, info!(name: "catcher", name = "rocket::default", "uri.base" = "/", code = status.code,
"no registered catcher: using Rocket default"); "no registered catcher: using Rocket default");
Ok(catcher::default_handler(status, req)) Ok(catcher::default_handler(status, req))
} }

View File

@ -204,9 +204,8 @@ impl<'a> FromParam<'a> for String {
#[inline(always)] #[inline(always)]
fn from_param(param: &'a str) -> Result<String, Self::Error> { fn from_param(param: &'a str) -> Result<String, Self::Error> {
#[cfg(debug_assertions)] { #[cfg(debug_assertions)] {
let loc = std::panic::Location::caller(); let location = std::panic::Location::caller();
warn_!("Note: Using `String` as a parameter type is inefficient. Use `&str` instead."); warn!(%location, "`String` as a parameter is inefficient. Use `&str` instead.");
info_!("`String` is used a parameter guard in {}:{}.", loc.file(), loc.line());
} }
if param.is_empty() { if param.is_empty() {

View File

@ -380,7 +380,7 @@ impl<'r> Request<'r> {
.get_one(ip_header) .get_one(ip_header)
.and_then(|ip| { .and_then(|ip| {
ip.parse() ip.parse()
.map_err(|_| warn_!("'{}' header is malformed: {}", ip_header, ip)) .map_err(|_| warn!(value = ip, "'{ip_header}' header is malformed"))
.ok() .ok()
}) })
} }
@ -1158,15 +1158,14 @@ impl<'r> Request<'r> {
} }
// Set the rest of the headers. This is rather unfortunate and slow. // Set the rest of the headers. This is rather unfortunate and slow.
for (name, value) in hyper.headers.iter() { for (header, value) in hyper.headers.iter() {
// FIXME: This is rather unfortunate. Header values needn't be UTF8. // FIXME: This is rather unfortunate. Header values needn't be UTF8.
let Ok(value) = std::str::from_utf8(value.as_bytes()) else { let Ok(value) = std::str::from_utf8(value.as_bytes()) else {
warn!("Header '{}' contains invalid UTF-8", name); warn!(%header, "dropping header with invalid UTF-8");
warn_!("Rocket only supports UTF-8 header values. Dropping header.");
continue; continue;
}; };
request.add_header(Header::new(name.as_str(), value)); request.add_header(Header::new(header.as_str(), value));
} }
match request.errors.is_empty() { match request.errors.is_empty() {

View File

@ -352,7 +352,7 @@ impl<'r> Body<'r> {
let n = match self.read_to_end(&mut vec).await { let n = match self.read_to_end(&mut vec).await {
Ok(n) => n, Ok(n) => n,
Err(e) => { Err(e) => {
error_!("Error reading body: {:?}", e); error!("i/o error reading body: {:?}", e);
return Err(e); return Err(e);
} }
}; };
@ -389,7 +389,7 @@ impl<'r> Body<'r> {
/// ``` /// ```
pub async fn to_string(&mut self) -> io::Result<String> { pub async fn to_string(&mut self) -> io::Result<String> {
String::from_utf8(self.to_bytes().await?).map_err(|e| { String::from_utf8(self.to_bytes().await?).map_err(|e| {
error_!("Body is invalid UTF-8: {}", e); error!("invalid body UTF-8: {}", e);
io::Error::new(io::ErrorKind::InvalidData, e) io::Error::new(io::ErrorKind::InvalidData, e)
}) })
} }

View File

@ -2,8 +2,6 @@ use crate::request::Request;
use crate::response::{self, Responder}; use crate::response::{self, Responder};
use crate::http::Status; use crate::http::Status;
use yansi::Paint;
/// Debug prints the internal value before forwarding to the 500 error catcher. /// Debug prints the internal value before forwarding to the 500 error catcher.
/// ///
/// This value exists primarily to allow handler return types that would not /// This value exists primarily to allow handler return types that would not
@ -78,8 +76,8 @@ impl<E> From<E> for Debug<E> {
impl<'r, E: std::fmt::Debug> Responder<'r, 'static> for Debug<E> { impl<'r, E: std::fmt::Debug> Responder<'r, 'static> for Debug<E> {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
warn_!("Debug: {:?}", self.0.primary()); let type_name = std::any::type_name::<E>();
warn_!("Debug always responds with {}.", Status::InternalServerError.primary()); info!(type_name, value = ?self.0, "debug response (500)");
Err(Status::InternalServerError) Err(Status::InternalServerError)
} }
} }
@ -87,7 +85,7 @@ impl<'r, E: std::fmt::Debug> Responder<'r, 'static> for Debug<E> {
/// Prints a warning with the error and forwards to the `500` error catcher. /// Prints a warning with the error and forwards to the `500` error catcher.
impl<'r> Responder<'r, 'static> for std::io::Error { impl<'r> Responder<'r, 'static> for std::io::Error {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
warn_!("I/O Error: {:?}", self.primary()); warn!("i/o error response: {self}");
Err(Status::InternalServerError) Err(Status::InternalServerError)
} }
} }

View File

@ -542,7 +542,10 @@ impl<'r> Responder<'r, 'static> for Status {
Response::build().status(self).ok() Response::build().status(self).ok()
} }
_ => { _ => {
error_!("Invalid status used as responder: {}.", self); error!(status = self.code,
"invalid status used as responder\n\
status must be one of 100, 200..=205, 400..=599");
Err(Status::InternalServerError) Err(Status::InternalServerError)
} }
} }

View File

@ -4,14 +4,14 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::any::Any; use std::any::Any;
use std::future::Future; use std::future::Future;
use std::panic::Location;
use yansi::Paint;
use either::Either; use either::Either;
use figment::{Figment, Provider}; use figment::{Figment, Provider};
use futures::TryFutureExt; use futures::TryFutureExt;
use crate::shutdown::{Stages, Shutdown}; use crate::shutdown::{Stages, Shutdown};
use crate::trace::traceable::{Traceable, TraceableCollection}; use crate::trace::{Traceable, TraceableCollection};
use crate::{sentinel, shield::Shield, Catcher, Config, Route}; use crate::{sentinel, shield::Shield, Catcher, Config, Route};
use crate::listener::{Bind, DefaultListener, Endpoint, Listener}; use crate::listener::{Bind, DefaultListener, Endpoint, Listener};
use crate::router::Router; use crate::router::Router;
@ -21,7 +21,6 @@ use crate::phase::{Stateful, StateRef, State};
use crate::http::uri::Origin; use crate::http::uri::Origin;
use crate::http::ext::IntoOwned; use crate::http::ext::IntoOwned;
use crate::error::{Error, ErrorKind}; use crate::error::{Error, ErrorKind};
// use crate::log::PaintExt;
/// The application server itself. /// The application server itself.
/// ///
@ -248,20 +247,18 @@ impl Rocket<Build> {
B::Error: fmt::Display, B::Error: fmt::Display,
M: Fn(&Origin<'a>, T) -> T, M: Fn(&Origin<'a>, T) -> T,
F: Fn(&mut Self, T), F: Fn(&mut Self, T),
T: Clone + fmt::Display, T: Clone + Traceable,
{ {
let mut base = match base.clone().try_into() { let mut base = match base.clone().try_into() {
Ok(origin) => origin.into_owned(), Ok(origin) => origin.into_owned(),
Err(e) => { Err(e) => {
error!("invalid {} base: {}", kind, Paint::white(&base)); error!(%base, location = %Location::caller(), "invalid {kind} base uri: {e}");
error_!("{}", e);
info_!("{} {}", "in".primary(), std::panic::Location::caller());
panic!("aborting due to {} base error", kind); panic!("aborting due to {} base error", kind);
} }
}; };
if base.query().is_some() { if base.query().is_some() {
warn!("query in {} base '{}' is ignored", kind, Paint::white(&base)); warn!(%base, location = %Location::caller(), "query in {kind} base is ignored");
base.clear_query(); base.clear_query();
} }
@ -760,9 +757,11 @@ impl Rocket<Orbit> {
rocket.fairings.handle_liftoff(rocket).await; rocket.fairings.handle_liftoff(rocket).await;
if !crate::running_within_rocket_async_rt().await { if !crate::running_within_rocket_async_rt().await {
warn!("Rocket is executing inside of a custom runtime."); warn!(
info_!("Rocket's runtime is enabled via `#[rocket::main]` or `#[launch]`."); "Rocket is executing inside of a custom runtime.\n\
info_!("Forced shutdown is disabled. Runtime settings may be suboptimal."); Rocket's runtime is enabled via `#[rocket::main]` or `#[launch]`\n\
Forced shutdown is disabled. Runtime settings may be suboptimal."
);
} }
tracing::info!(name: "liftoff", endpoint = %rocket.endpoints[0]); tracing::info!(name: "liftoff", endpoint = %rocket.endpoints[0]);

View File

@ -1,8 +1,6 @@
use std::fmt; use std::fmt;
use std::borrow::Cow; use std::borrow::Cow;
use yansi::Paint;
use crate::http::{uri, Method, MediaType}; use crate::http::{uri, Method, MediaType};
use crate::route::{Handler, RouteUri, BoxFuture}; use crate::route::{Handler, RouteUri, BoxFuture};
use crate::sentinel::Sentry; use crate::sentinel::Sentry;
@ -343,27 +341,6 @@ impl Route {
} }
} }
impl fmt::Display for Route {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref n) = self.name {
write!(f, "{}{}{} ", "(".cyan(), n.primary(), ")".cyan())?;
}
write!(f, "{} ", self.method.green())?;
self.uri.color_fmt(f)?;
if self.rank > 1 {
write!(f, " [{}]", self.rank.primary().bold())?;
}
if let Some(ref format) = self.format {
write!(f, " {}", format.yellow())?;
}
Ok(())
}
}
impl fmt::Debug for Route { impl fmt::Debug for Route {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Route") f.debug_struct("Route")

View File

@ -238,22 +238,6 @@ impl<'a> RouteUri<'a> {
// We subtract `3` because `raw_path` is never `0`: 0b0100 = 4 - 3 = 1. // We subtract `3` because `raw_path` is never `0`: 0b0100 = 4 - 3 = 1.
-((raw_weight as isize) - 3) -((raw_weight as isize) - 3)
} }
pub(crate) fn color_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use yansi::Paint;
let (path, base, unmounted) = (self.uri.path(), self.base(), self.unmounted().path());
let unmounted_part = path.strip_prefix(base.as_str())
.map(|raw| raw.as_str())
.unwrap_or(unmounted.as_str());
write!(f, "{}{}", self.base().blue().underline(), unmounted_part.blue())?;
if let Some(q) = self.unmounted().query() {
write!(f, "{}{}", "?".yellow(), q.yellow())?;
}
Ok(())
}
} }
impl Metadata { impl Metadata {

View File

@ -226,7 +226,7 @@ mod tests {
let (a, b) = (dummy_route($ranked, $m1, $p1), dummy_route($ranked, $m2, $p2)); let (a, b) = (dummy_route($ranked, $m1, $p1), dummy_route($ranked, $m2, $p2));
assert! { assert! {
a.collides_with(&b), a.collides_with(&b),
"\nroutes failed to collide:\n{} does not collide with {}\n", a, b "\nroutes failed to collide:\n{:?} does not collide with {:?}\n", a, b
} }
}; };
(ranked $($t:tt)+) => (assert_collision!(true, $($t)+)); (ranked $($t:tt)+) => (assert_collision!(true, $($t)+));
@ -239,7 +239,7 @@ mod tests {
let (a, b) = (dummy_route($ranked, $m1, $p1), dummy_route($ranked, $m2, $p2)); let (a, b) = (dummy_route($ranked, $m1, $p1), dummy_route($ranked, $m2, $p2));
assert! { assert! {
!a.collides_with(&b), !a.collides_with(&b),
"\nunexpected collision:\n{} collides with {}\n", a, b "\nunexpected collision:\n{:?} collides with {:?}\n", a, b
} }
}; };
(ranked $($t:tt)+) => (assert_no_collision!(true, $($t)+)); (ranked $($t:tt)+) => (assert_no_collision!(true, $($t)+));

View File

@ -580,10 +580,10 @@ mod test {
let req_status = Status::from_code(req.0).expect("valid status"); let req_status = Status::from_code(req.0).expect("valid status");
let catcher = catcher(&router, req_status, req.1).expect("some catcher"); let catcher = catcher(&router, req_status, req.1).expect("some catcher");
assert_eq!(catcher.code, expected.0, assert_eq!(catcher.code, expected.0,
"\nmatched {}, expected {:?} for req {:?}", catcher, expected, req); "\nmatched {:?}, expected {:?} for req {:?}", catcher, expected, req);
assert_eq!(catcher.base.path(), expected.1, assert_eq!(catcher.base.path(), expected.1,
"\nmatched {}, expected {:?} for req {:?}", catcher, expected, req); "\nmatched {:?}, expected {:?} for req {:?}", catcher, expected, req);
} }
}) })
} }

View File

@ -219,7 +219,7 @@ impl<'r, T: Serialize> Responder<'r, 'static> for Json<T> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
let string = serde_json::to_string(&self.0) let string = serde_json::to_string(&self.0)
.map_err(|e| { .map_err(|e| {
error_!("JSON failed to serialize: {:?}", e); error!("JSON serialize failure: {}", e);
Status::InternalServerError Status::InternalServerError
})?; })?;

View File

@ -190,7 +190,7 @@ impl<'r, T: Serialize> Responder<'r, 'static> for MsgPack<T> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
let buf = rmp_serde::to_vec(&self.0) let buf = rmp_serde::to_vec(&self.0)
.map_err(|e| { .map_err(|e| {
error_!("MsgPack failed to serialize: {:?}", e); error!("MsgPack serialize failure: {}", e);
Status::InternalServerError Status::InternalServerError
})?; })?;

View File

@ -17,7 +17,7 @@ use crate::error::log_server_error;
use crate::data::{IoStream, RawStream}; use crate::data::{IoStream, RawStream};
use crate::util::{spawn_inspect, FutureExt, ReaderStream}; use crate::util::{spawn_inspect, FutureExt, ReaderStream};
use crate::http::Status; use crate::http::Status;
use crate::trace::traceable::{Traceable, TraceableCollection}; use crate::trace::{Traceable, TraceableCollection};
type Result<T, E = crate::Error> = std::result::Result<T, E>; type Result<T, E = crate::Error> = std::result::Result<T, E>;

View File

@ -5,7 +5,7 @@ use crate::{Rocket, Request, Response, Orbit, Config};
use crate::fairing::{Fairing, Info, Kind}; use crate::fairing::{Fairing, Info, Kind};
use crate::http::{Header, uncased::UncasedStr}; use crate::http::{Header, uncased::UncasedStr};
use crate::shield::*; use crate::shield::*;
use crate::trace::traceable::*; use crate::trace::*;
/// A [`Fairing`] that injects browser security and privacy headers into all /// A [`Fairing`] that injects browser security and privacy headers into all
/// outgoing responses. /// outgoing responses.
@ -59,10 +59,8 @@ use crate::trace::traceable::*;
/// ///
/// If TLS is configured and enabled when the application is launched in a /// If TLS is configured and enabled when the application is launched in a
/// non-debug profile, HSTS is automatically enabled with its default policy and /// non-debug profile, HSTS is automatically enabled with its default policy and
/// a warning is logged. /// a warning is logged. To get rid of this warning, explicitly
/// /// [`Shield::enable()`] an [`Hsts`] policy.
/// To get rid of this warning, explicitly [`Shield::enable()`] an [`Hsts`]
/// policy.
pub struct Shield { pub struct Shield {
/// Enabled policies where the key is the header name. /// Enabled policies where the key is the header name.
policies: HashMap<&'static UncasedStr, Header<'static>>, policies: HashMap<&'static UncasedStr, Header<'static>>,
@ -193,32 +191,19 @@ impl Fairing for Shield {
&& rocket.figment().profile() != Config::DEBUG_PROFILE && rocket.figment().profile() != Config::DEBUG_PROFILE
&& !self.is_enabled::<Hsts>(); && !self.is_enabled::<Hsts>();
if force_hsts {
self.force_hsts.store(true, Ordering::Release);
}
info_span!("shield" [policies = self.policies.len()] => { info_span!("shield" [policies = self.policies.len()] => {
self.policies.values().trace_all_info(); self.policies.values().trace_all_info();
if force_hsts { if force_hsts {
warn!("Detected TLS-enabled liftoff without enabling HSTS."); warn!("Detected TLS-enabled liftoff without enabling HSTS.\n\
warn!("Shield has enabled a default HSTS policy."); Shield has enabled a default HSTS policy.\n\
info!("To remove this warning, configure an HSTS policy."); To remove this warning, configure an HSTS policy.");
} }
}) })
// trace::collection_info!("shield", force_hsts => self.polices.values(), {
// warn!("Detected TLS-enabled liftoff without enabling HSTS.");
// warn!("Shield has enabled a default HSTS policy.");
// info!("To remove this warning, configure an HSTS policy.");
// });
// // tracing::info_span!("shield", force_hsts).in_scope(|| {
// // self.polices.values().trace();
// // for header in self.policies.values() {
// // info!(name: "header", name = header.name().as_str(), value = header.value());
// // }
//
// warn!("Detected TLS-enabled liftoff without enabling HSTS.");
// warn!("Shield has enabled a default HSTS policy.");
// info!("To remove this warning, configure an HSTS policy.");
// });
} }
async fn on_response<'r>(&self, _: &'r Request<'_>, response: &mut Response<'r>) { async fn on_response<'r>(&self, _: &'r Request<'_>, response: &mut Response<'r>) {
@ -226,8 +211,10 @@ impl Fairing for Shield {
// the header is not already in the response. // the header is not already in the response.
for header in self.policies.values() { for header in self.policies.values() {
if response.headers().contains(header.name()) { if response.headers().contains(header.name()) {
warn!("Shield: response contains a '{}' header.", header.name()); warn_span!("shield refusing to overwrite existing response header" => {
warn_!("Refusing to overwrite existing header."); header.trace_warn();
});
continue continue
} }

View File

@ -200,7 +200,10 @@ impl<'r, T: Send + Sync + 'static> FromRequest<'r> for &'r State<T> {
match State::get(req.rocket()) { match State::get(req.rocket()) {
Some(state) => Outcome::Success(state), Some(state) => Outcome::Success(state),
None => { None => {
error_!("Attempted to retrieve unmanaged state `{}`!", type_name::<T>()); error!(type_name = type_name::<T>(),
"retrieving unmanaged state\n\
state must be managed via `rocket.manage()`");
Outcome::Error((Status::InternalServerError, ())) Outcome::Error((Status::InternalServerError, ()))
} }
} }

View File

@ -73,15 +73,12 @@ impl<T: Resolver> fairing::Fairing for Fairing<T> {
} }
async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result { async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
use yansi::Paint;
let result = T::init(&rocket).await; let result = T::init(&rocket).await;
match result { match result {
Ok(resolver) => Ok(rocket.manage(Arc::new(resolver) as Arc<dyn Resolver>)), Ok(resolver) => Ok(rocket.manage(Arc::new(resolver) as Arc<dyn Resolver>)),
Err(e) => { Err(e) => {
let name = std::any::type_name::<T>(); let type_name = std::any::type_name::<T>();
error!("TLS resolver {} failed to initialize.", name.primary().bold()); error!(type_name, reason = %e, "TLS resolver failed to initialize");
error_!("{e}");
Err(rocket) Err(rocket)
} }
} }

View File

@ -1,43 +1,26 @@
pub trait PaintExt: Sized {
fn emoji(self) -> yansi::Painted<Self>;
}
impl PaintExt for &str {
/// Paint::masked(), but hidden on Windows due to broken output. See #1122.
fn emoji(self) -> yansi::Painted<Self> {
#[cfg(windows)] { yansi::Paint::new("").mask() }
#[cfg(not(windows))] { yansi::Paint::new(self).mask() }
}
}
macro_rules! declare_macro { macro_rules! declare_macro {
($($name:ident $level:ident),* $(,)?) => ( ($($name:ident $level:ident),* $(,)?) => (
$(declare_macro!([$] $name $level);)* $(declare_macro!([$] $name $level);)*
); );
([$d:tt] $name:ident $level:ident) => ( ([$d:tt] $name:ident $level:ident) => (
#[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! $name { macro_rules! $name {
($d ($t:tt)*) => ($crate::tracing::$level!($d ($t)*)); ($d ($t:tt)*) => ($crate::tracing::$level!($d ($t)*));
} }
// pub use $name as $name;
); );
} }
declare_macro!(
// launch_meta INFO, launch_meta_ INFO,
error error, error_ error,
info info, info_ info,
trace trace, trace_ trace,
debug debug, debug_ debug,
warn warn, warn_ warn,
);
macro_rules! declare_span_macro { macro_rules! declare_span_macro {
($($name:ident $level:ident),* $(,)?) => ( ($($name:ident $level:ident),* $(,)?) => (
$(declare_span_macro!([$] $name $level);)* $(declare_span_macro!([$] $name $level);)*
); );
([$d:tt] $name:ident $level:ident) => ( ([$d:tt] $name:ident $level:ident) => (
#[doc(hidden)]
#[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) => ({
@ -45,11 +28,12 @@ macro_rules! declare_span_macro {
.in_scope(|| $in_scope); .in_scope(|| $in_scope);
}) })
} }
#[doc(inline)]
pub use $name as $name;
); );
} }
declare_span_macro!(error_span ERROR, info_span INFO, trace_span TRACE, debug_span DEBUG);
macro_rules! span { macro_rules! span {
($level:expr, $($args:tt)*) => {{ ($level:expr, $($args:tt)*) => {{
match $level { match $level {
@ -67,6 +51,8 @@ macro_rules! span {
}}; }};
} }
#[doc(hidden)]
#[macro_export]
macro_rules! event { macro_rules! event {
($level:expr, $($args:tt)*) => {{ ($level:expr, $($args:tt)*) => {{
match $level { match $level {
@ -82,3 +68,22 @@ macro_rules! event {
$crate::tracing::event!(name: $n, target: concat!("rocket::", $n), $level, $($args)*); $crate::tracing::event!(name: $n, target: concat!("rocket::", $n), $level, $($args)*);
}}; }};
} }
#[doc(inline)]
pub use event as event;
declare_macro!(
error error,
info info,
trace trace,
debug debug,
warn warn
);
declare_span_macro!(
error_span ERROR,
warn_span WARN,
info_span INFO,
trace_span TRACE,
debug_span DEBUG,
);

View File

@ -1,11 +1,18 @@
#[macro_use] #[macro_use]
pub mod macros; mod macros;
#[cfg(feature = "trace")] mod traceable;
pub mod subscriber;
pub mod level;
pub mod traceable;
pub use traceable::Traceable; #[cfg(feature = "trace")]
#[cfg_attr(nightly, doc(cfg(feature = "trace")))]
pub mod subscriber;
pub(crate) mod level;
#[doc(inline)]
pub use traceable::{Traceable, TraceableCollection};
#[doc(inline)]
pub use macros::*;
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))]

View File

@ -103,15 +103,15 @@ impl<K: private::FmtKind> RocketFmt<K> {
} }
} }
pub fn has_message(&self, meta: &Metadata<'_>) -> bool { pub(crate) fn has_message(&self, meta: &Metadata<'_>) -> bool {
meta.fields().field("message").is_some() meta.fields().field("message").is_some()
} }
pub fn has_data_fields(&self, meta: &Metadata<'_>) -> bool { pub(crate) fn has_data_fields(&self, meta: &Metadata<'_>) -> bool {
meta.fields().iter().any(|f| f.name() != "message") meta.fields().iter().any(|f| f.name() != "message")
} }
pub fn message<'a, F: RecordFields + 'a>(&self, pub(crate) fn message<'a, F: RecordFields + 'a>(&self,
init_prefix: &'a dyn fmt::Display, init_prefix: &'a dyn fmt::Display,
cont_prefix: &'a dyn fmt::Display, cont_prefix: &'a dyn fmt::Display,
meta: &'a Metadata<'_>, meta: &'a Metadata<'_>,
@ -142,9 +142,11 @@ impl<K: private::FmtKind> RocketFmt<K> {
}) })
} }
pub fn compact_fields<'a, F>(&self, meta: &'a Metadata<'_>, data: F) -> impl fmt::Display + 'a pub(crate) fn compact_fields<'a, F: RecordFields + 'a>(
where F: RecordFields + 'a &self,
{ meta: &'a Metadata<'_>,
data: F
) -> impl fmt::Display + 'a {
let key_style = self.style(meta).bold(); let key_style = self.style(meta).bold();
let val_style = self.style(meta).primary(); let val_style = self.style(meta).primary();
@ -163,7 +165,7 @@ impl<K: private::FmtKind> RocketFmt<K> {
}) })
} }
pub fn print<F: RecordFields>( pub(crate) fn print<F: RecordFields>(
&self, &self,
prefix: &dyn fmt::Display, prefix: &dyn fmt::Display,
cont_prefix: &dyn fmt::Display, cont_prefix: &dyn fmt::Display,

View File

@ -4,10 +4,10 @@ mod compact;
mod common; mod common;
mod request_id; mod request_id;
pub use visit::{RecordDisplay, Data};
pub use pretty::Pretty; pub use pretty::Pretty;
pub use compact::Compact; pub use compact::Compact;
pub use common::RocketFmt; pub use common::RocketFmt;
pub use request_id::{RequestId, RequestIdLayer}; pub use request_id::{RequestId, RequestIdLayer};
pub(crate) use common::Handle; pub(crate) use common::Handle;
pub(crate) use visit::{RecordDisplay, Data};

View File

@ -151,6 +151,7 @@ impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
"catchers" => "🚧 ", "catchers" => "🚧 ",
"fairings" => "📦 ", "fairings" => "📦 ",
"shield" => "🛡️ ", "shield" => "🛡️ ",
"templating" => "📐 ",
"request" => "", "request" => "",
_ => "", _ => "",
}; };

View File

@ -61,10 +61,10 @@ impl RequestId {
impl RequestIdLayer { impl RequestIdLayer {
thread_local! { thread_local! {
pub static CURRENT_REQUEST_ID: Cell<Option<RequestId>> = Cell::new(None); static CURRENT_REQUEST_ID: Cell<Option<RequestId>> = Cell::new(None);
} }
fn current() -> Option<RequestId> { pub fn current() -> Option<RequestId> {
Self::CURRENT_REQUEST_ID.get() Self::CURRENT_REQUEST_ID.get()
} }
} }

View File

@ -142,7 +142,7 @@ impl Traceable for Route {
format = self.format.as_ref().map(display), format = self.format.as_ref().map(display),
} }
event! { Level::DEBUG, "route", event! { Level::DEBUG, "sentinels",
route = self.name.as_ref().map(|n| &**n), route = self.name.as_ref().map(|n| &**n),
sentinels = %Formatter(|f| { sentinels = %Formatter(|f| {
f.debug_set() f.debug_set()
@ -255,8 +255,11 @@ impl Traceable for Error {
impl Traceable for Sentry { impl Traceable for Sentry {
fn trace(&self, level: Level) { fn trace(&self, level: Level) {
let (file, line, column) = self.location; let (file, line, col) = self.location;
event!(level, "sentry", type_name = self.type_name, file, line, column); event!(level, "sentry",
type_name = self.type_name,
location = %Formatter(|f| write!(f, "{file}:{line}:{col}"))
);
} }
} }
@ -325,7 +328,7 @@ impl Traceable for ErrorKind {
span.in_scope(|| fairings.iter().trace_all(level)); span.in_scope(|| fairings.iter().trace_all(level));
}, },
SentinelAborts(sentries) => { SentinelAborts(sentries) => {
let span = span!(level, "sentries", "sentry abort"); let span = span!(level, "sentries", "sentry launch abort");
span.in_scope(|| sentries.iter().trace_all(level)); span.in_scope(|| sentries.iter().trace_all(level));
} }
InsecureSecretKey(profile) => event!(level, "insecure_key", %profile, InsecureSecretKey(profile) => event!(level, "insecure_key", %profile,

View File

@ -1,5 +1,3 @@
use rocket::fairing::AdHoc;
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;

View File

@ -21,12 +21,9 @@ impl<'r> FromRequest<'r> for Guard1 {
type Error = (); type Error = ();
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> { async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> {
rocket::info_!("-- 1 --");
let atomics = try_outcome!(req.guard::<&State<Atomics>>().await); let atomics = try_outcome!(req.guard::<&State<Atomics>>().await);
atomics.uncached.fetch_add(1, Ordering::Relaxed); atomics.uncached.fetch_add(1, Ordering::Relaxed);
req.local_cache(|| { req.local_cache(|| {
rocket::info_!("1: populating cache!");
atomics.cached.fetch_add(1, Ordering::Relaxed) atomics.cached.fetch_add(1, Ordering::Relaxed)
}); });
@ -39,8 +36,6 @@ impl<'r> FromRequest<'r> for Guard2 {
type Error = (); type Error = ();
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> { async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> {
rocket::info_!("-- 2 --");
try_outcome!(req.guard::<Guard1>().await); try_outcome!(req.guard::<Guard1>().await);
Outcome::Success(Guard2) Outcome::Success(Guard2)
} }
@ -51,12 +46,9 @@ impl<'r> FromRequest<'r> for Guard3 {
type Error = (); type Error = ();
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> { async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> {
rocket::info_!("-- 3 --");
let atomics = try_outcome!(req.guard::<&State<Atomics>>().await); let atomics = try_outcome!(req.guard::<&State<Atomics>>().await);
atomics.uncached.fetch_add(1, Ordering::Relaxed); atomics.uncached.fetch_add(1, Ordering::Relaxed);
req.local_cache_async(async { req.local_cache_async(async {
rocket::info_!("3: populating cache!");
atomics.cached.fetch_add(1, Ordering::Relaxed) atomics.cached.fetch_add(1, Ordering::Relaxed)
}).await; }).await;
@ -69,8 +61,6 @@ impl<'r> FromRequest<'r> for Guard4 {
type Error = (); type Error = ();
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> { async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> {
rocket::info_!("-- 4 --");
try_outcome!(Guard3::from_request(req).await); try_outcome!(Guard3::from_request(req).await);
Outcome::Success(Guard4) Outcome::Success(Guard4)
} }

View File

@ -3,12 +3,12 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use rocket::http::Status; use rocket::http::Status;
use rocket::tracing::Level;
use rocket::{route, Error, Request, Data, Route, Orbit, Rocket, Ignite}; use rocket::{route, Error, Request, Data, Route, Orbit, Rocket, Ignite};
use rocket::fairing::{Fairing, Info, Kind}; use rocket::fairing::{Fairing, Info, Kind};
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket::listener::tcp::TcpListener; use rocket::listener::tcp::TcpListener;
use rocket::trace::Traceable;
use yansi::Paint;
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct Redirector(u16); pub struct Redirector(u16);
@ -45,9 +45,9 @@ impl Redirector {
pub async fn try_launch(self, config: Config) -> Result<Rocket<Ignite>, Error> { pub async fn try_launch(self, config: Config) -> Result<Rocket<Ignite>, Error> {
use rocket::http::Method::*; use rocket::http::Method::*;
info!("{}{}", "🔒 ".mask(), "HTTP -> HTTPS Redirector:".magenta()); rocket::info_span!("HTTP -> HTTPS Redirector" => {
info_!("redirecting insecure port {} to TLS port {}", info!(from = self.0, to = config.tls_addr.port(), "redirecting");
self.0.yellow(), config.tls_addr.port().green()); });
// Build a vector of routes to `redirect` on `<path..>` for each method. // Build a vector of routes to `redirect` on `<path..>` for each method.
let redirects = [Get, Put, Post, Delete, Options, Head, Trace, Connect, Patch] let redirects = [Get, Put, Post, Delete, Options, Head, Trace, Connect, Patch]
@ -75,16 +75,18 @@ impl Fairing for Redirector {
async fn on_liftoff(&self, rocket: &Rocket<Orbit>) { async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
let Some(tls_addr) = rocket.endpoints().find_map(|e| e.tls()?.tcp()) else { let Some(tls_addr) = rocket.endpoints().find_map(|e| e.tls()?.tcp()) else {
info!("{}{}", "🔒 ".mask(), "HTTP -> HTTPS Redirector:".magenta()); rocket::warn_span!("HTTP -> HTTPS Redirector" => {
warn_!("Main instance is not being served over TLS/TCP."); warn!("Main instance is not being served over TLS/TCP.\n\
warn_!("Redirector refusing to start."); Redirector refusing to start.");
});
return; return;
}; };
let config = Config { let config = Config {
tls_addr, tls_addr,
server: rocket::Config { server: rocket::Config {
// log_level: LogLevel::Critical, log_level: Some(Level::ERROR),
..rocket.config().clone() ..rocket.config().clone()
}, },
}; };
@ -93,9 +95,11 @@ impl Fairing for Redirector {
let shutdown = rocket.shutdown(); let shutdown = rocket.shutdown();
rocket::tokio::spawn(async move { rocket::tokio::spawn(async move {
if let Err(e) = this.try_launch(config).await { if let Err(e) = this.try_launch(config).await {
error!("Failed to start HTTP -> HTTPS redirector."); error_span!("failed to start HTTP -> HTTPS redirector" => {
info_!("Error: {}", e); e.trace_error();
error_!("Shutting down main instance."); info!("shutting down main instance");
});
shutdown.notify(); shutdown.notify();
} }
}); });

View File

@ -40,7 +40,7 @@ impl Context {
match Task::all(conn).await { match Task::all(conn).await {
Ok(tasks) => Context { flash, tasks }, Ok(tasks) => Context { flash, tasks },
Err(e) => { Err(e) => {
error_!("DB Task::all() error: {}", e); error!("DB Task::all() error: {e}");
Context { Context {
flash: Some(("error".into(), "Fail to access database.".into())), flash: Some(("error".into(), "Fail to access database.".into())),
tasks: vec![] tasks: vec![]
@ -56,7 +56,7 @@ async fn new(todo_form: Form<Todo>, conn: DbConn) -> Flash<Redirect> {
if todo.description.is_empty() { if todo.description.is_empty() {
Flash::error(Redirect::to("/"), "Description cannot be empty.") Flash::error(Redirect::to("/"), "Description cannot be empty.")
} else if let Err(e) = Task::insert(todo, &conn).await { } else if let Err(e) = Task::insert(todo, &conn).await {
error_!("DB insertion error: {}", e); error!("DB insertion error: {e}");
Flash::error(Redirect::to("/"), "Todo could not be inserted due an internal error.") Flash::error(Redirect::to("/"), "Todo could not be inserted due an internal error.")
} else { } else {
Flash::success(Redirect::to("/"), "Todo successfully added.") Flash::success(Redirect::to("/"), "Todo successfully added.")
@ -68,7 +68,7 @@ async fn toggle(id: i32, conn: DbConn) -> Result<Redirect, Template> {
match Task::toggle_with_id(id, &conn).await { match Task::toggle_with_id(id, &conn).await {
Ok(_) => Ok(Redirect::to("/")), Ok(_) => Ok(Redirect::to("/")),
Err(e) => { Err(e) => {
error_!("DB toggle({}) error: {}", id, e); error!("DB toggle({id}) error: {e}");
Err(Template::render("index", Context::err(&conn, "Failed to toggle task.").await)) Err(Template::render("index", Context::err(&conn, "Failed to toggle task.").await))
} }
} }
@ -79,7 +79,7 @@ async fn delete(id: i32, conn: DbConn) -> Result<Flash<Redirect>, Template> {
match Task::delete_with_id(id, &conn).await { match Task::delete_with_id(id, &conn).await {
Ok(_) => Ok(Flash::success(Redirect::to("/"), "Todo was deleted.")), Ok(_) => Ok(Flash::success(Redirect::to("/"), "Todo was deleted.")),
Err(e) => { Err(e) => {
error_!("DB deletion({}) error: {}", id, e); error!("DB deletion({id}) error: {e}");
Err(Template::render("index", Context::err(&conn, "Failed to delete task.").await)) Err(Template::render("index", Context::err(&conn, "Failed to delete task.").await))
} }
} }