1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3
4use std::time::Instant;
5
6use tide::{Middleware, Next, Request};
7use tracing::{error, error_span, field, info, info_span, warn, warn_span};
8use tracing_futures::Instrument;
9#[cfg(feature = "request_id")]
10use uuid::Uuid;
11
12#[derive(Debug, Default, Clone)]
19pub struct TraceMiddleware;
20
21impl TraceMiddleware {
22 #[must_use]
24 pub const fn new() -> Self {
25 Self
26 }
27
28 async fn log<'a, State: Clone + Send + Sync + 'static>(
30 &'a self,
31 ctx: Request<State>,
32 next: Next<'a, State>,
33 ) -> tide::Result {
34 let path = ctx.url().path().to_owned();
35 let method = ctx.method();
36
37 Ok(async {
38 info!("received");
39 let start = Instant::now();
40 let response = next.run(ctx).await;
41 let duration = start.elapsed();
42 let status = response.status();
43
44 info_span!("Response", http.status_code = status as u16, http.duration = ?duration)
45 .in_scope(|| {
46 if status.is_server_error() {
47 let span = error_span!(
48 "Internal error",
49 detail = field::Empty,
50 error = field::Empty
51 );
52 if let Some(error) = response.error() {
53 span.record("error", &field::display(error));
54 span.record("detail", &field::debug(error));
55 }
56 span.in_scope(|| error!("sent"));
57 } else if status.is_client_error() {
58 warn_span!("Client error").in_scope(|| warn!("sent"));
59 } else {
60 info!("sent")
61 }
62 });
63 response
64 }
65 .instrument({
66 let span = info_span!(
67 "Request",
68 http.method = %method,
69 http.target = %path,
70 request_id = field::Empty,
71 );
72
73 #[cfg(feature = "request_id")]
75 span.record("request_id", Uuid::new_v4().to_string());
76
77 span
78 })
79 .await)
80 }
81}
82
83#[async_trait::async_trait]
84impl<State: Clone + Send + Sync + 'static> Middleware<State> for TraceMiddleware {
85 async fn handle(&self, req: Request<State>, next: Next<'_, State>) -> tide::Result {
86 self.log(req, next).await
87 }
88}