#![feature(const_trait_impl)] #[macro_use] extern crate tracing; use nrid::Nrid; use tide::Middleware; use tide::Next; use tide::Request; use tracing::Instrument; /// A traceable component of a [`Request`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, num_enum::FromPrimitive)] #[repr(u8)] pub enum ReqComp { #[num_enum(default)] __Default = 0b00000000, Method = 0b00000001, Path = 0b00000010, IP = 0b00000100, Query = 0b00001000, } /// A selection of traceable components of a [`Request`]. #[derive(Debug, Clone, Copy, Eq, Hash)] #[repr(transparent)] pub struct ReqComps(u8); impl ReqComps { #[inline] pub const fn contains(self, mask: ReqComp) -> bool { (self & mask).0 == mask as u8 } #[inline] pub const fn contains_all(self, mask: ReqComps) -> bool { self & mask == mask } } impl const Default for ReqComps { #[inline] fn default() -> Self { Self(0) } } // unfortunately, the automatic Into impl will not be const. impl const From for ReqComps { #[inline] fn from(comp: ReqComp) -> Self { Self(comp as u8) } } impl const core::cmp::PartialEq for ReqComps { #[inline] fn eq(&self, comp: &Self) -> bool { self.0 == comp.0 } } impl const core::ops::BitOr for ReqComp { type Output = ReqComps; #[inline] fn bitor(self, rhs: Self) -> Self::Output { ReqComps((self as u8) | (rhs as u8)) } } impl const core::ops::BitOr for ReqComps { type Output = ReqComps; #[inline] fn bitor(self, rhs: Self) -> Self::Output { ReqComps((self.0 as u8) | (rhs.0 as u8)) } } impl const core::ops::BitOr for ReqComps { type Output = ReqComps; #[inline] fn bitor(self, rhs: ReqComp) -> Self::Output { ReqComps((self.0 as u8) | (rhs as u8)) } } // TODO make `const` when is "stable" #57349 impl core::ops::BitOrAssign for ReqComps { #[inline] fn bitor_assign(&mut self, rhs: ReqComps) { self.0 = (self.0 as u8) | (rhs.0 as u8) } } // TODO make `const` when is "stable" #57349 impl core::ops::BitOrAssign for ReqComps { #[inline] fn bitor_assign(&mut self, rhs: ReqComp) { self.0 = (self.0 as u8) | (rhs as u8) } } impl const core::ops::BitAnd for ReqComp { type Output = ReqComps; #[inline] fn bitand(self, rhs: Self) -> Self::Output { ReqComps((self as u8) & (rhs as u8)) } } impl const core::ops::BitAnd for ReqComps { type Output = ReqComps; #[inline] fn bitand(self, rhs: Self) -> Self::Output { ReqComps((self.0 as u8) & (rhs.0 as u8)) } } impl const core::ops::BitAnd for ReqComps { type Output = ReqComps; #[inline] fn bitand(self, rhs: ReqComp) -> Self::Output { ReqComps((self.0 as u8) & (rhs as u8)) } } // TODO make `const` when is "stable" #57349 impl core::ops::BitAndAssign for ReqComps { #[inline] fn bitand_assign(&mut self, rhs: ReqComps) { self.0 = (self.0 as u8) & (rhs.0 as u8) } } // TODO make `const` when is "stable" #57349 impl core::ops::BitAndAssign for ReqComps { #[inline] fn bitand_assign(&mut self, rhs: ReqComp) { self.0 = (self.0 as u8) & (rhs as u8) } } #[derive(Debug, Clone)] pub struct LogMiddleware { pub components: ReqComps, } impl const Default for LogMiddleware { #[inline] fn default() -> Self { Self { components: ReqComp::Method | ReqComp::Path, } } } impl LogMiddleware { async fn log<'a, State: Clone + Send + Sync + 'static>( &'a self, req: Request, next: Next<'a, State>, ) -> tide::Result { let id = Nrid::now(); use tracing::field::Empty; let span = debug_span!("Request", id = %id, "http.method" = Empty, "http.path" = Empty, "conn.ip" = Empty, "http.query" = Empty); let url = req.url(); if self.components.contains(ReqComp::Method) { span.record("http.method", &tracing::field::display(req.method())); } if self.components.contains(ReqComp::Path) { span.record("http.path", &url.path()); } if self.components.contains(ReqComp::IP) { span.record( "conn.ip", &tracing::field::display(req.remote().unwrap_or("unknown").to_string()), ); } if self.components.contains(ReqComp::Query) { span.record("http.query", &url.query().unwrap_or("")); } async move { let res = next.run(req).await; let status = res.status(); match res.error() { Some(e) => { error!(status = %status, "Response with internal error: {e:?}"); } None => { debug!(status = %status, "Response"); } } Ok(res) } .instrument(span) .await } } #[async_trait::async_trait] impl Middleware for LogMiddleware { async fn handle(&self, req: Request, next: Next<'_, State>) -> tide::Result { self.log(req, next).await } } #[cfg(test)] mod test { use super::*; #[test] fn test_mask() { const FIELDS: &'static [ReqComp] = &[ReqComp::Path, ReqComp::IP]; let mut comps = ReqComps::default(); for field in FIELDS { comps |= *field; } assert!(comps.contains(ReqComp::Path)); assert!(comps.contains(ReqComp::IP)); assert!(!comps.contains(ReqComp::Query)); } }