use std::{ future::Future, pin::Pin, sync::Arc, task::{Context, Poll}, }; use axum::{body::Body, http::Request}; use pin_project_lite::pin_project; use tower::{Layer, Service}; use tracing::Span; use uuid::Uuid; // Make sure to include `uuid` crate in your Cargo.toml #[derive(Debug, Clone, Default)] pub struct LoggingLayer; impl Layer for LoggingLayer { type Service = LoggingService; fn layer(&self, inner: S) -> Self::Service { LoggingService { inner, } } } #[derive(Clone, Debug)] pub struct LoggingService { inner: T, } impl Service> for LoggingService where T: Service>, { type Error = T::Error; type Future = LoggingServiceFuture; type Response = T::Response; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.inner.poll_ready(cx) } fn call(&mut self, req: Request) -> Self::Future { let request_uuid = Uuid::now_v7(); // Generate UUID v7 let span = tracing::debug_span!("request", ?request_uuid, method=?req.method(), uri=?req.uri()); tracing::debug!(msg = "request started", uuid=?request_uuid); LoggingServiceFuture { inner: self.inner.call(req), uuid: Arc::new(request_uuid), // Store UUID in an Arc for shared ownership span: Arc::new(span), } } } pin_project! { #[derive(Clone, Debug)] pub struct LoggingServiceFuture { #[pin] inner: T, uuid: Arc, // Shared state between LoggingService and LoggingServiceFuture span: Arc, } } impl Future for LoggingServiceFuture where T: Future, { type Output = T::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); match this.inner.poll(cx) { Poll::Pending => Poll::Pending, Poll::Ready(output) => { tracing::debug!(msg = "request finished", uuid=?this.uuid); Poll::Ready(output) } } } }