kanidmd_core/https/
trace.rs

1//! Reimplementation of tower-http's DefaultMakeSpan that only runs at "INFO" level for our own needs.
2
3use axum::http::{Request, StatusCode};
4use kanidm_proto::constants::KOPID;
5use sketching::event_dynamic_lvl;
6use tower_http::LatencyUnit;
7use tracing::{Level, Span};
8
9/// The default way Spans will be created for Trace.
10///
11#[derive(Debug, Clone)]
12pub struct DefaultMakeSpanKanidmd {}
13
14impl DefaultMakeSpanKanidmd {
15    /// Create a new `DefaultMakeSpanKanidmd`.
16    pub fn new() -> Self {
17        Self {}
18    }
19}
20
21impl Default for DefaultMakeSpanKanidmd {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl<B> tower_http::trace::MakeSpan<B> for DefaultMakeSpanKanidmd {
28    fn make_span(&mut self, request: &Request<B>) -> Span {
29        // Needs to be at info to ensure that there is always a span for each
30        // tracing event to hook into.
31        tracing::span!(
32            Level::INFO,
33            "request",
34            method = %request.method(),
35            uri = %request.uri(),
36            version = ?request.version(),
37        )
38    }
39}
40
41#[derive(Clone, Debug)]
42pub(crate) struct DefaultOnResponseKanidmd {
43    #[allow(dead_code)]
44    level: Level,
45    #[allow(dead_code)]
46    latency_unit: LatencyUnit,
47    #[allow(dead_code)]
48    include_headers: bool,
49}
50
51impl DefaultOnResponseKanidmd {
52    #[allow(dead_code)]
53    pub fn new() -> Self {
54        Self::default()
55    }
56}
57
58impl Default for DefaultOnResponseKanidmd {
59    fn default() -> Self {
60        Self {
61            level: Level::INFO,
62            latency_unit: LatencyUnit::Millis,
63            include_headers: false,
64        }
65    }
66}
67
68impl<B> tower_http::trace::OnResponse<B> for DefaultOnResponseKanidmd {
69    fn on_response(
70        self,
71        response: &axum::response::Response<B>,
72        latency: std::time::Duration,
73        _span: &Span,
74    ) {
75        let kopid = match response.headers().get(KOPID) {
76            Some(val) => val.to_str().unwrap_or("<invalid kopid>"),
77            None => "<unknown>",
78        };
79        let (level, msg) =
80            match response.status().is_success() || response.status().is_informational() {
81                true => (Level::DEBUG, "response sent"),
82                false => {
83                    if response.status().is_redirection() {
84                        (Level::INFO, "client redirection sent")
85                    } else if response.status().is_client_error() {
86                        if response.status() == StatusCode::NOT_FOUND {
87                            (Level::INFO, "client error")
88                        } else {
89                            (Level::WARN, "client error") // it worked, but there was an input error
90                        }
91                    } else {
92                        (Level::ERROR, "error handling request") // oh no the server failed
93                    }
94                }
95            };
96        event_dynamic_lvl!(
97            level,
98            ?latency,
99            status_code = response.status().as_u16(),
100            kopid = kopid,
101            msg
102        );
103    }
104}