From 5950018e536ca456a0c16a83aab8522116b512fd Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 8 May 2024 03:52:40 -0700 Subject: [PATCH] wip: refactor into traceable trait --- core/lib/src/config/config.rs | 142 +++------------------- core/lib/src/fairing/fairings.rs | 28 +++-- core/lib/src/rocket.rs | 41 ++++++- core/lib/src/shield/shield.rs | 32 +++-- core/lib/src/trace/macros.rs | 55 +++++++++ core/lib/src/trace/mod.rs | 42 +------ core/lib/src/trace/subscriber.rs | 85 +++++++------ core/lib/src/trace/traceable.rs | 199 +++++++++++++++++++++++++++++++ 8 files changed, 390 insertions(+), 234 deletions(-) create mode 100644 core/lib/src/trace/macros.rs create mode 100644 core/lib/src/trace/traceable.rs diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index 30cf1434e8..accac5aefd 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -10,7 +10,7 @@ use crate::config::{ShutdownConfig, Ident, CliColors}; use crate::request::{self, Request, FromRequest}; use crate::http::uncased::Uncased; use crate::data::Limits; -use crate::util::Formatter; +use crate::trace::traceable::Traceable; /// Rocket server configuration. /// @@ -303,98 +303,6 @@ impl Config { pub fn from(provider: T) -> Self { Self::try_from(provider).unwrap_or_else(bail_with_config_error) } - - #[cfg(feature = "secrets")] - fn known_secret_key_used(&self) -> bool { - const KNOWN_SECRET_KEYS: &[&str] = &[ - "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk=" - ]; - - KNOWN_SECRET_KEYS.iter().any(|&key_str| { - let value = figment::value::Value::from(key_str); - self.secret_key == value.deserialize().expect("known key is valid") - }) - } - - #[tracing::instrument(name = "config", skip_all, fields(profile = %figment.profile()))] - pub(crate) fn pretty_print(&self, figment: &Figment) { - info! { - name: "values", - http2 = cfg!(feature = "http2"), - log_level = self.log_level.map(|l| l.as_str()), - cli_colors = %self.cli_colors, - workers = self.workers, - max_blocking = self.max_blocking, - ident = %self.ident, - ip_header = self.ip_header.as_ref().map(|s| s.as_str()), - proxy_proto_header = self.proxy_proto_header.as_ref().map(|s| s.as_str()), - limits = %Formatter(|f| f.debug_map() - .entries(self.limits.limits.iter().map(|(k, v)| (k.as_str(), v.to_string()))) - .finish()), - temp_dir = %self.temp_dir.relative().display(), - keep_alive = (self.keep_alive != 0).then_some(self.keep_alive), - shutdown.ctrlc = self.shutdown.ctrlc, - shutdown.signals = %{ - #[cfg(not(unix))] { - "disabled (not unix)" - } - - #[cfg(unix)] { - Formatter(|f| f.debug_set() - .entries(self.shutdown.signals.iter().map(|s| s.as_str())) - .finish()) - } - }, - shutdown.grace = self.shutdown.grace, - shutdown.mercy = self.shutdown.mercy, - shutdown.force = self.shutdown.force, - }; - - for param in Self::PARAMETERS { - if let Some(source) = figment.find_metadata(param) { - trace! { - param, - %source.name, - source.source = source.source.as_ref().map(|s| s.to_string()), - } - } - } - - // Check for now deprecated config values. - for (key, replacement) in Self::DEPRECATED_KEYS { - if let Some(source) = figment.find_metadata(key) { - warn! { - name: "deprecated", - key, - replacement, - %source.name, - source.source = source.source.as_ref().map(|s| s.to_string()), - "config key `{key}` is deprecated and has no meaning" - } - } - } - - #[cfg(feature = "secrets")] { - if !self.secret_key.is_provided() { - warn! { - name: "volatile_secret_key", - "secrets enabled without configuring a stable `secret_key`; \ - private/signed cookies will become unreadable after restarting; \ - disable the `secrets` feature or configure a `secret_key`; \ - this becomes a hard error in non-debug profiles", - } - } - - if self.known_secret_key_used() { - warn! { - name: "insecure_secret_key", - "The configured `secret_key` is exposed and insecure. \ - The configured key is publicly published and thus insecure. \ - Try generating a new key with `head -c64 /dev/urandom | base64`." - } - } - } - } } /// Associated constants for default profiles. @@ -416,11 +324,6 @@ impl Config { /// Associated constants for stringy versions of configuration parameters. impl Config { - /// The stringy parameter name for setting/extracting [`Config::profile`]. - /// - /// This isn't `pub` because setting it directly does nothing. - const PROFILE: &'static str = "profile"; - /// The stringy parameter name for setting/extracting [`Config::workers`]. pub const WORKERS: &'static str = "workers"; @@ -465,12 +368,22 @@ impl Config { Self::CLI_COLORS, ]; + /// The stringy parameter name for setting/extracting [`Config::profile`]. + /// + /// This isn't `pub` because setting it directly does nothing. + const PROFILE: &'static str = "profile"; + /// An array of deprecated stringy parameter names. - const DEPRECATED_KEYS: &'static [(&'static str, Option<&'static str>)] = &[ + pub(crate) const DEPRECATED_KEYS: &'static [(&'static str, Option<&'static str>)] = &[ ("env", Some(Self::PROFILE)), ("log", Some(Self::LOG_LEVEL)), ("read_timeout", None), ("write_timeout", None), ]; + /// Secret keys that have been used in docs or leaked otherwise. + #[cfg(feature = "secrets")] + pub(crate) const KNOWN_SECRET_KEYS: &'static [&'static str] = &[ + "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk=" + ]; } impl Provider for Config { @@ -520,34 +433,7 @@ pub fn bail_with_config_error(error: figment::Error) -> T { } #[doc(hidden)] +// FIXME: Remove this funtion. pub fn pretty_print_error(error: figment::Error) { - use figment::error::{OneOf as V, Kind::*}; - - for e in error { - let span = tracing::error_span! { - "error: configuration", - key = (!e.path.is_empty()).then_some(&e.path).and_then(|path| { - let (profile, metadata) = (e.profile.as_ref()?, e.metadata.as_ref()?); - Some(metadata.interpolate(profile, path)) - }), - source.name = e.metadata.as_ref().map(|m| &*m.name), - source.source = e.metadata.as_ref().and_then(|m| Some(m.source.as_ref()?.to_string())), - }; - - let _scope = span.enter(); - match e.kind { - 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), - } - } + error.trace() } diff --git a/core/lib/src/fairing/fairings.rs b/core/lib/src/fairing/fairings.rs index 59a1839c70..0d50837277 100644 --- a/core/lib/src/fairing/fairings.rs +++ b/core/lib/src/fairing/fairings.rs @@ -47,6 +47,22 @@ impl Fairings { .chain(self.shutdown.iter()) } + pub fn unique_set(&self) -> Vec<&dyn Fairing> { + iter!(self, self.active().collect::>().into_iter()) + .map(|v| v.1) + .collect() + // .into_iter() + // .map(|i| ) + // if !active_fairings.is_empty() { + // tracing::info_span!("fairings").in_scope(|| { + // for (_, fairing) in iter!(self, active_fairings.into_iter()) { + // let (name, kind) = (fairing.info().name, fairing.info().kind); + // info!(name: "fairing", name, %kind) + // } + // }); + // } + } + pub fn add(&mut self, fairing: Box) { let this = &fairing; let this_info = this.info(); @@ -166,18 +182,6 @@ impl Fairings { false => Err(&self.failures) } } - - pub fn pretty_print(&self) { - let active_fairings = self.active().collect::>(); - if !active_fairings.is_empty() { - tracing::info_span!("fairings").in_scope(|| { - for (_, fairing) in iter!(self, active_fairings.into_iter()) { - let (name, kind) = (fairing.info().name, fairing.info().kind); - info!(name: "fairing", name, %kind) - } - }); - } - } } impl std::fmt::Debug for Fairings { diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs index 41404961c2..1840ff5a1b 100644 --- a/core/lib/src/rocket.rs +++ b/core/lib/src/rocket.rs @@ -11,6 +11,7 @@ use figment::{Figment, Provider}; use futures::TryFutureExt; use crate::shutdown::{Stages, Shutdown}; +use crate::trace::traceable::{Traceable, TraceableCollection}; use crate::{sentinel, shield::Shield, Catcher, Config, Route}; use crate::listener::{Bind, DefaultListener, Endpoint, Listener}; use crate::router::Router; @@ -567,10 +568,40 @@ impl Rocket { // Log everything we know: config, routes, catchers, fairings. // TODO: Store/print managed state type names? - config.pretty_print(self.figment()); - log_items("routes", self.routes(), |r| &r.uri.base, |r| &r.uri); - log_items("catchers", self.catchers(), |c| &c.base, |c| &c.base); - self.fairings.pretty_print(); + let fairings = self.fairings.unique_set(); + info_span!("config" [profile = %self.figment().profile()] => { + config.trace(); + self.figment().trace(); + }); + + info_span!("routes" [count = self.routes.len()] => self.routes().trace_all()); + info_span!("catchers" [count = self.catchers.len()] => self.catchers().trace_all()); + info_span!("fairings" [count = fairings.len()] => fairings.trace_all()); + + // trace::all("routes", self.routes()); + // tracing::info_span!("routes").in_scope(|| self.routes().for_each(|r| r.trace())); + // tracing::info_span!("catchers").in_scope(|| self.catchers().for_each(|c| c.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."); + // }); + + // tracing::info_span!("routes") + // .in_scope(|| self.routes().for_each(|r| r.trace())); + // // self.polices.values().trace(); + // // for header in self.policies.values() { + // // info!(name: "header", name = header.name().as_str(), value = header.value()); + // // } + + // TODO: Store/print managed state type names? + // trace::collection_info!("routes" => self.routes()); + // trace::collection_info!("catchers" => self.catchers()); + // trace::collection_info!("fairings" => self.fairings.active_set()); + // trace::collection_info!("state" => self.active_set()); // Ignite the rocket. let rocket: Rocket = Rocket(Igniting { @@ -759,7 +790,7 @@ impl Rocket { info_!("Forced shutdown is disabled. Runtime settings may be suboptimal."); } - tracing::info!(target: "rocket::liftoff", endpoint = %rocket.endpoints[0]); + tracing::info!(name: "liftoff", endpoint = %rocket.endpoints[0]); } /// Returns the finalized, active configuration. This is guaranteed to diff --git a/core/lib/src/shield/shield.rs b/core/lib/src/shield/shield.rs index 61b97caa10..0b92602093 100644 --- a/core/lib/src/shield/shield.rs +++ b/core/lib/src/shield/shield.rs @@ -5,6 +5,7 @@ use crate::{Rocket, Request, Response, Orbit, Config}; use crate::fairing::{Fairing, Info, Kind}; use crate::http::{Header, uncased::UncasedStr}; use crate::shield::*; +use crate::trace::traceable::*; /// A [`Fairing`] that injects browser security and privacy headers into all /// outgoing responses. @@ -192,15 +193,32 @@ impl Fairing for Shield { && rocket.figment().profile() != Config::DEBUG_PROFILE && !self.is_enabled::(); - tracing::info_span!("shield", force_hsts).in_scope(|| { - for header in self.policies.values() { - info!(name: "header", name = header.name().as_str(), value = header.value()); + info_span!("shield" [policies = self.policies.len()] => { + self.policies.values().trace_all(); + + if force_hsts { + 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."); } + }) + + // 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."); + // }); - 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>) { diff --git a/core/lib/src/trace/macros.rs b/core/lib/src/trace/macros.rs new file mode 100644 index 0000000000..b5dc28561b --- /dev/null +++ b/core/lib/src/trace/macros.rs @@ -0,0 +1,55 @@ +pub trait PaintExt: Sized { + fn emoji(self) -> yansi::Painted; +} + +impl PaintExt for &str { + /// Paint::masked(), but hidden on Windows due to broken output. See #1122. + fn emoji(self) -> yansi::Painted { + #[cfg(windows)] { yansi::Paint::new("").mask() } + #[cfg(not(windows))] { yansi::Paint::new(self).mask() } + } +} + +macro_rules! declare_macro { + ($($name:ident $level:ident),* $(,)?) => ( + $(declare_macro!([$] $name $level);)* + ); + + ([$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)*); + }) + } + ); +} + +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 { + ($($name:ident $level:ident),* $(,)?) => ( + $(declare_span_macro!([$] $name $level);)* + ); + + ([$d:tt] $name:ident $level:ident) => ( + #[macro_export] + macro_rules! $name { + ($n:literal $d ([ $d ($f:tt)* ])? => $in_scope:expr) => ({ + $crate::tracing::span!(tracing::Level::$level, $n $d (, $d ($f)* )?).in_scope(|| $in_scope); + }) + } + ); +} + +declare_span_macro!(info_span INFO, trace_span TRACE); diff --git a/core/lib/src/trace/mod.rs b/core/lib/src/trace/mod.rs index 0f88780f53..4a160a4aaf 100644 --- a/core/lib/src/trace/mod.rs +++ b/core/lib/src/trace/mod.rs @@ -1,47 +1,11 @@ use rocket::Config; +#[macro_use] +pub mod macros; #[cfg(feature = "trace")] pub mod subscriber; pub mod level; - -pub trait PaintExt: Sized { - fn emoji(self) -> yansi::Painted; -} - -impl PaintExt for &str { - /// Paint::masked(), but hidden on Windows due to broken output. See #1122. - fn emoji(self) -> yansi::Painted { - #[cfg(windows)] { yansi::Paint::new("").mask() } - #[cfg(not(windows))] { yansi::Paint::new(self).mask() } - } -} - -macro_rules! declare_macro { - ($($name:ident $level:ident),* $(,)?) => ( - $(declare_macro!([$] $name $level);)* - ); - - ([$d:tt] $name:ident $level:ident) => ( - #[macro_export] - macro_rules! $name { - ($d ($t:tt)*) => ({ - #[allow(unused_imports)] - use $crate::trace::PaintExt as _; - - $crate::tracing::$level!($d ($t)*); - }) - } - ); -} - -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, -); +pub mod traceable; pub fn init<'a, T: Into>>(_config: T) { #[cfg(feature = "trace")] diff --git a/core/lib/src/trace/subscriber.rs b/core/lib/src/trace/subscriber.rs index aa2020f78f..9699989e93 100644 --- a/core/lib/src/trace/subscriber.rs +++ b/core/lib/src/trace/subscriber.rs @@ -116,9 +116,9 @@ struct RocketFmt { // } macro_rules! log { - ($this:expr, $event:expr => $fmt:expr $(, $($t:tt)*)?) => { - let metadata = $event.metadata(); - let (i, s, t) = ($this.indent(), $this.style($event), metadata.target()); + ($this:expr, $metadata:expr => $fmt:expr $(, $($t:tt)*)?) => { + let metadata = $metadata; + let (i, s, t) = ($this.indent(), $this.style(metadata), metadata.target()); match *metadata.level() { Level::WARN => print!( concat!("{}{} ", $fmt), @@ -146,8 +146,8 @@ macro_rules! log { } macro_rules! logln { - ($this:expr, $event:expr => $fmt:literal $($t:tt)*) => { - log!($this, $event => concat!($fmt, "\n") $($t)*); + ($this:expr, $meta:expr => $fmt:literal $($t:tt)*) => { + log!($this, $meta => concat!($fmt, "\n") $($t)*); }; } @@ -208,8 +208,8 @@ impl LookupSpan<'a>> RocketFmt { } } - fn style(&self, event: &Event<'_>) -> Style { - match *event.metadata().level() { + fn style(&self, metadata: &Metadata<'_>) -> Style { + match *metadata.level() { Level::ERROR => self.default_style.red(), Level::WARN => self.default_style.yellow(), Level::INFO => self.default_style.blue(), @@ -219,62 +219,52 @@ impl LookupSpan<'a>> RocketFmt { } fn print(&self, event: &Event<'_>) { - let style = self.style(event); - let fields = event.metadata().fields(); + let metadata = event.metadata(); + let style = self.style(metadata); + let fields = metadata.fields(); if let Some(msg) = fields.field("message") { event.record_display(|field: &Field, value: &dyn Display| { if field == &msg { - log!(self, event => "{}", value.paint(style)); + log!(self, metadata => "{}", value.paint(style)); } }); - if fields.len() > 1 { print!(" ("); } - self.print_fields_compact(false, event); - if fields.len() > 1 { print!(")"); } + self.print_fields_compact(false, metadata, event); } else if !fields.is_empty() { - self.print_fields_compact(true, event); - } - - if !fields.is_empty() { - println!(""); + self.print_fields_compact(true, metadata, event); } } - fn print_fields_compact(&self, prefix: bool, event: &Event<'_>) { - let key_style = self.style(event).bold(); - let val_style = self.style(event).primary(); + fn print_fields_compact(&self, prefix: bool, metadata: &Metadata<'_>, fields: F) + where F: RecordFields + { + let key_style = self.style(metadata).bold(); + let val_style = self.style(metadata).primary(); let mut printed = false; - event.record_display(|field: &Field, val: &dyn Display| { + fields.record_display(|field: &Field, val: &dyn Display| { let key = field.name(); if key != "message" { if !printed && prefix { - log!(self, event => "{}: {}", key.paint(key_style), val.paint(val_style)); + log!(self, metadata => "{}: {}", key.paint(key_style), val.paint(val_style)); } else { - if printed { print!(" "); } - print!("{}: {}", key.paint(key_style), val.paint(val_style)); + print!(" {}: {}", key.paint(key_style), val.paint(val_style)); } printed = true; } }); + + println!(); } - fn print_fields(&self, event: &Event<'_>) { - let style = self.style(event); - event.record_display(|key: &Field, value: &dyn Display| { + fn print_fields(&self, metadata: &Metadata<'_>, fields: F) { + let style = self.style(metadata); + fields.record_display(|key: &Field, value: &dyn Display| { if key.name() != "message" { - logln!(self, event => "{}: {}", key.paint(style), value.paint(style).primary()); + logln!(self, metadata => "{}: {}", key.paint(style), value.paint(style).primary()); } }) } - - fn write_config(&self, event: &Event<'_>) { - // eprintln!(" > config [name = {}]", event.metadata().name()); - match event.metadata().name() { - "values" => self.print_fields(event), - _ => self.print(event), - } - } } impl LookupSpan<'a>> Layer for RocketFmt { @@ -287,13 +277,20 @@ impl LookupSpan<'a>> Layer for RocketFmt { // eprintln!("[name = {}, target = {}]", metadata.name(), metadata.target()); if let Some(span) = ctxt.event_span(event) { // eprintln!(" > [name = {}, target = {}]", span.name(), span.metadata().target()); - return match span.name() { - "config" => self.write_config(event), + return match (span.name(), event.metadata().name()) { + ("config", "config") => self.print_fields(event.metadata(), event), _ => self.print(event), }; } - self.print(event); + match event.metadata().name() { + "liftoff" => { + let data = Data::new(event); + logln!(self, event.metadata() => "rocket has launched from {}", &data["endpoint"]); + + } + _ => self.print(event), + } } fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { @@ -301,9 +298,11 @@ impl LookupSpan<'a>> Layer for RocketFmt { let data = Data::new(attrs); match span.metadata().name() { "config" => println!("configured for {}", &data["profile"]), - name => println!("{name} {:?}", Formatter(|f| { - f.debug_map().entries(data.map.iter().map(|(k, v)| (k, v))).finish() - })) + name => { + log!(self, span.metadata() => "{}", name); + self.print_fields_compact(false, span.metadata(), attrs); + // f.debug_map().entries(data.map.iter().map(|(k, v)| (k, v))).finish() + } } span.extensions_mut().replace(data); diff --git a/core/lib/src/trace/traceable.rs b/core/lib/src/trace/traceable.rs new file mode 100644 index 0000000000..c5987190dd --- /dev/null +++ b/core/lib/src/trace/traceable.rs @@ -0,0 +1,199 @@ +use crate::fairing::Fairing; +use crate::{Catcher, Config, Route}; +use crate::util::Formatter; + +use figment::Figment; +use rocket::http::Header; + +pub trait Traceable { + fn trace(&self); +} + +pub trait TraceableCollection { + fn trace_all(self); +} + +impl> TraceableCollection for I { + fn trace_all(self) { + self.into_iter().for_each(|i| i.trace()) + } +} + +impl Traceable for &T { + fn trace(&self) { + T::trace(self) + } +} + +impl Traceable for Figment { + fn trace(&self) { + for param in Config::PARAMETERS { + if let Some(source) = self.find_metadata(param) { + tracing::trace! { + param, + %source.name, + source.source = source.source.as_ref().map(|s| s.to_string()), + } + } + } + + // Check for now deprecated config values. + for (key, replacement) in Config::DEPRECATED_KEYS { + if let Some(source) = self.find_metadata(key) { + warn! { + name: "deprecated", + key, + replacement, + %source.name, + source.source = source.source.as_ref().map(|s| s.to_string()), + "config key `{key}` is deprecated and has no meaning" + } + } + } + } +} + +impl Traceable for Config { + fn trace(&self) { + info! { + name: "config", + http2 = cfg!(feature = "http2"), + log_level = self.log_level.map(|l| l.as_str()), + cli_colors = %self.cli_colors, + workers = self.workers, + max_blocking = self.max_blocking, + ident = %self.ident, + ip_header = self.ip_header.as_ref().map(|s| s.as_str()), + proxy_proto_header = self.proxy_proto_header.as_ref().map(|s| s.as_str()), + limits = %Formatter(|f| f.debug_map() + .entries(self.limits.limits.iter().map(|(k, v)| (k.as_str(), v.to_string()))) + .finish()), + temp_dir = %self.temp_dir.relative().display(), + keep_alive = (self.keep_alive != 0).then_some(self.keep_alive), + shutdown.ctrlc = self.shutdown.ctrlc, + shutdown.signals = %{ + #[cfg(not(unix))] { + "disabled (not unix)" + } + + #[cfg(unix)] { + Formatter(|f| f.debug_set() + .entries(self.shutdown.signals.iter().map(|s| s.as_str())) + .finish()) + } + }, + shutdown.grace = self.shutdown.grace, + shutdown.mercy = self.shutdown.mercy, + shutdown.force = self.shutdown.force, + } + + #[cfg(feature = "secrets")] { + if !self.secret_key.is_provided() { + warn! { + name: "volatile_secret_key", + "secrets enabled without configuring a stable `secret_key`; \ + private/signed cookies will become unreadable after restarting; \ + disable the `secrets` feature or configure a `secret_key`; \ + this becomes a hard error in non-debug profiles", + } + } + + let secret_key_is_known = Config::KNOWN_SECRET_KEYS.iter().any(|&key_str| { + let value = figment::value::Value::from(key_str); + self.secret_key == value.deserialize().expect("known key is valid") + }); + + if secret_key_is_known { + warn! { + name: "insecure_secret_key", + "The configured `secret_key` is exposed and insecure. \ + The configured key is publicly published and thus insecure. \ + Try generating a new key with `head -c64 /dev/urandom | base64`." + } + } + } + } +} + +impl Traceable for Route { + fn trace(&self) { + info! { + name: "route", + name = self.name.as_ref().map(|n| &**n), + method = %self.method, + rank = self.rank, + uri = %self.uri, + uri.base = %self.uri.base(), + uri.unmounted = %self.uri.unmounted(), + format = self.format.as_ref().map(display), + sentinels = %Formatter(|f|{ + f.debug_set() + .entries(self.sentinels.iter().filter(|s| s.specialized).map(|s| s.type_name)) + .finish() + }) + } + } +} + +impl Traceable for Catcher { + fn trace(&self) { + info! { + name: "catcher", + name = self.name.as_ref().map(|n| &**n), + code = self.code, + rank = self.rank, + uri.base = %self.base(), + } + } +} + +impl Traceable for &dyn Fairing { + fn trace(&self) { + info!(name: "fairing", name = self.info().name, kind = %self.info().kind) + } +} + +impl Traceable for figment::error::Kind { + fn trace(&self) { + use figment::error::{OneOf as V, 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), + } + } +} + +impl Traceable for figment::Error { + fn trace(&self) { + for e in self.clone() { + let span = tracing::error_span! { + "config", + key = (!e.path.is_empty()).then_some(&e.path).and_then(|path| { + let (profile, metadata) = (e.profile.as_ref()?, e.metadata.as_ref()?); + Some(metadata.interpolate(profile, path)) + }), + source.name = e.metadata.as_ref().map(|m| &*m.name), + source.source = e.metadata.as_ref().and_then(|m| Some(m.source.as_ref()?.to_string())), + }; + + span.in_scope(|| e.kind.trace()); + } + } +} + +impl Traceable for Header<'_> { + fn trace(&self) { + info!(name: "header", name = self.name().as_str(), value = self.value()); + } +}