tide_tracing/
lib.rs

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/// Log all incoming requests and responses with tracing spans.
13///
14/// ```
15/// let mut app = tide::Server::new();
16/// app.with(tide_tracing::TraceMiddleware::new());
17/// ```
18#[derive(Debug, Default, Clone)]
19pub struct TraceMiddleware;
20
21impl TraceMiddleware {
22    /// Create a new instance of `TraceMiddleware`.
23    #[must_use]
24    pub const fn new() -> Self {
25        Self
26    }
27
28    /// Log a request and a response.
29    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            // If the request_id feature is enabled, add a new request_id record to the span.
74            #[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}