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            // Defer logging this span until there is child information attached.
38            defer = true,
39        )
40    }
41}
42
43#[derive(Clone, Debug)]
44pub(crate) struct DefaultOnResponseKanidmd {
45    #[allow(dead_code)]
46    level: Level,
47    #[allow(dead_code)]
48    latency_unit: LatencyUnit,
49    #[allow(dead_code)]
50    include_headers: bool,
51}
52
53impl DefaultOnResponseKanidmd {
54    #[allow(dead_code)]
55    pub fn new() -> Self {
56        Self::default()
57    }
58}
59
60impl Default for DefaultOnResponseKanidmd {
61    fn default() -> Self {
62        Self {
63            level: Level::INFO,
64            latency_unit: LatencyUnit::Millis,
65            include_headers: false,
66        }
67    }
68}
69
70impl<B> tower_http::trace::OnResponse<B> for DefaultOnResponseKanidmd {
71    fn on_response(
72        self,
73        response: &axum::response::Response<B>,
74        latency: std::time::Duration,
75        _span: &Span,
76    ) {
77        let kopid = match response.headers().get(KOPID) {
78            Some(val) => val.to_str().unwrap_or("<invalid kopid>"),
79            None => "<unknown>",
80        };
81        let (level, msg) =
82            match response.status().is_success() || response.status().is_informational() {
83                true => (Level::DEBUG, "response sent"),
84                false => {
85                    if response.status().is_redirection() {
86                        (Level::INFO, "client redirection sent")
87                    } else if response.status().is_client_error() {
88                        if response.status() == StatusCode::NOT_FOUND {
89                            (Level::INFO, "client error")
90                        } else {
91                            (Level::WARN, "client error") // it worked, but there was an input error
92                        }
93                    } else {
94                        (Level::ERROR, "error handling request") // oh no the server failed
95                    }
96                }
97            };
98        event_dynamic_lvl!(
99            level,
100            ?latency,
101            status_code = response.status().as_u16(),
102            kopid = kopid,
103            msg
104        );
105    }
106}