trillium_tracing/
lib.rs

1use std::borrow::Cow;
2use std::fmt::Display;
3use std::time::Instant;
4
5use tracing::{error, error_span, field, info, info_span, warn, warn_span};
6use trillium::async_trait;
7use trillium::{Conn, Handler, Info};
8
9struct TracerRun;
10
11#[async_trait]
12impl Handler for Tracer {
13    async fn run(&self, conn: Conn) -> Conn {
14        let path = path(&conn);
15        let method = conn.method();
16        let ip = ip(&conn);
17        info_span!("Request", http.method = ?method, ip = ?ip, path = %path).in_scope(|| {
18            info!("received");
19            conn.with_state(TracerRun)
20        })
21    }
22    async fn init(&mut self, info: &mut Info) {
23        info!("Starting server");
24        info!(server = ?info.server_description(), socker_addr = ?info.tcp_socket_addr().unwrap());
25    }
26
27    async fn before_send(&self, mut conn: Conn) -> Conn {
28        let response_len = conn.response_len().unwrap_or_default();
29        let response_time = response_time(&conn).to_string();
30
31        // TODO: If the conn.status is not set by a route, should we set to NotFound by default if
32        // the user doesn't set the default handler in trillium?
33        let status = conn.status(); //.unwrap_or_else(|| Status::NotFound);
34
35        if conn.state::<TracerRun>().is_some() {
36            conn.inner_mut().after_send(move |s| {
37                let response_span = info_span!("Response", http.status_code = field::Empty, http.duration = ?response_time, http.response_len = ?response_len);
38                response_span.in_scope(|| {
39                    if let Some(status) = status {
40                        response_span.record("http.status_code", &(status as u32));
41                        if s.is_success() {
42                            if status.is_server_error() {
43                                error!("Internal Server error");
44                            } else if status.is_client_error() {
45                                warn_span!("Client error").in_scope(|| warn!("sent"))
46                            } else {
47                                info!("sent")
48                            }
49                        } else {
50                            error_span!("Internal error").in_scope(|| warn!("cannot be sent"))
51                        }
52                    } else {
53                        // This place shouldn't be reached ideally
54                        // Set the default status in the router
55                        error_span!("Internal error").in_scope(|| 
56                            error!("status not set, set the default status")
57                        );
58                    }
59
60                });
61            });
62        }
63        conn
64    }
65}
66
67struct ResponseTimeOutput(Instant);
68impl Display for ResponseTimeOutput {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        f.write_fmt(format_args!("{:?}", Instant::now() - self.0))
71    }
72}
73
74fn response_time(conn: &Conn) -> ResponseTimeOutput {
75    ResponseTimeOutput(conn.inner().start_time())
76}
77
78fn ip(conn: &Conn) -> Cow<'static, str> {
79    match conn.inner().peer_ip() {
80        Some(peer) => format!("{:?}", peer).into(),
81        None => "-".into(),
82    }
83}
84
85fn path(conn: &Conn) -> Cow<'static, str> {
86    let p = conn.inner().path();
87    format!("{p:?}").into()
88}
89
90#[derive(Debug, Default, Clone)]
91pub struct Tracer;
92
93impl Tracer {
94    #[must_use]
95    pub const fn new() -> Self {
96        Self
97    }
98}