use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Instant;
use tower::{Layer, Service};
use tracing::Instrument;
#[derive(Clone, Copy, Debug, Default)]
pub struct TraceLayer {
_private: (),
}
impl TraceLayer {
pub fn new() -> Self {
Self::default()
}
}
impl<S> Layer<S> for TraceLayer {
type Service = TraceService<S>;
fn layer(&self, inner: S) -> Self::Service {
TraceService { inner }
}
}
#[derive(Clone, Debug)]
pub struct TraceService<S> {
inner: S,
}
impl<S, ReqBody, ResBody> Service<http::Request<ReqBody>> for TraceService<S>
where
S: Service<http::Request<ReqBody>, Response = http::Response<ResBody>>,
S::Error: fmt::Display,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<S::Response, S::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: http::Request<ReqBody>) -> Self::Future {
let uri = request.uri();
let url = format!(
"{}://{}{}",
uri.scheme_str().unwrap_or("http"),
uri.authority().map(|a| a.as_str()).unwrap_or(""),
uri.path()
);
let span = tracing::debug_span!(
"http.request",
http.request.method = %request.method(),
url.full = %url,
http.response.status_code = tracing::field::Empty,
);
let start = Instant::now();
let future = {
let _entered = span.enter();
self.inner.call(request)
};
let instrumented = span.clone();
Box::pin(
async move {
let result = future.await;
let elapsed_ms = start.elapsed().as_millis() as u64;
match &result {
Ok(response) => {
let status = response.status();
span.record("http.response.status_code", status.as_u16());
if status.is_server_error() {
tracing::warn!(status = status.as_u16(), elapsed_ms, "response");
} else {
tracing::debug!(status = status.as_u16(), elapsed_ms, "response");
}
}
Err(error) => tracing::warn!(%error, elapsed_ms, "request failed"),
}
result
}
.instrument(instrumented),
)
}
}