Skip to main content

rama_http/layer/trace/
make_span.rs

1use crate::Request;
2use crate::header::USER_AGENT;
3use crate::opentelemetry::version_as_protocol_version;
4use rama_core::telemetry::tracing::{self, Level, Span};
5use rama_net::Protocol;
6use rama_utils::str::arcstr::{ArcStr, arcstr};
7
8use super::DEFAULT_MESSAGE_LEVEL;
9
10/// Trait used to generate [`Span`]s from requests. [`Trace`] wraps all request handling in this
11/// span.
12///
13/// [`Span`]: tracing::Span
14/// [`Trace`]: super::Trace
15pub trait MakeSpan<B>: Send + Sync + 'static {
16    /// Make a span from a request.
17    fn make_span(&self, request: &Request<B>) -> Span;
18}
19
20impl<B> MakeSpan<B> for Span {
21    fn make_span(&self, _request: &Request<B>) -> Span {
22        self.clone()
23    }
24}
25
26impl<F, B> MakeSpan<B> for F
27where
28    F: Fn(&Request<B>) -> Span + Send + Sync + 'static,
29{
30    fn make_span(&self, request: &Request<B>) -> Span {
31        self(request)
32    }
33}
34
35/// The default way [`Span`]s will be created for [`Trace`].
36///
37/// [`Span`]: tracing::Span
38/// [`Trace`]: super::Trace
39#[derive(Debug, Clone)]
40pub struct DefaultMakeSpan {
41    level: Level,
42    include_headers: bool,
43    otel_name: ArcStr,
44}
45
46impl DefaultMakeSpan {
47    /// Create a new `DefaultMakeSpan`.
48    #[must_use]
49    pub const fn new() -> Self {
50        Self {
51            level: DEFAULT_MESSAGE_LEVEL,
52            include_headers: false,
53            otel_name: arcstr!("request"),
54        }
55    }
56
57    rama_utils::macros::generate_set_and_with! {
58        /// Set the [`Level`] used for the [tracing span].
59        ///
60        /// Defaults to [`Level::DEBUG`].
61        ///
62        /// [tracing span]: https://docs.rs/tracing/latest/tracing/#spans
63        pub fn level(mut self, level: Level) -> Self {
64            self.level = level;
65            self
66        }
67    }
68
69    rama_utils::macros::generate_set_and_with! {
70        /// Include request headers on the [`Span`].
71        ///
72        /// By default headers are not included.
73
74        pub fn include_headers(mut self, include_headers: bool) -> Self {
75            self.include_headers = include_headers;
76            self
77        }
78    }
79
80    rama_utils::macros::generate_set_and_with! {
81        /// Set the otel.name field of this [`Span`].
82        pub fn name(mut self, otel_name: ArcStr) -> Self {
83            self.otel_name = otel_name;
84            self
85        }
86    }
87}
88
89impl Default for DefaultMakeSpan {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95impl<B> MakeSpan<B> for DefaultMakeSpan {
96    fn make_span(&self, request: &Request<B>) -> Span {
97        let full_uri = request.request_uri();
98
99        // to ensure that we always log authority even if not included in full protocol
100        let protocol = full_uri.scheme().unwrap_or(&Protocol::HTTP);
101
102        // according to OTEL spec domain can be domain or IP, so host is fine
103        let (found_domain, found_port, found_scheme) = if let Some(host) = full_uri.host() {
104            (
105                Some(host),
106                full_uri.port_u16().or_else(|| protocol.default_port()),
107                Some(protocol.as_str()),
108            )
109        } else {
110            tracing::debug!("no authority could be resolved for request");
111            (None, None, None)
112        };
113
114        let found_domain_cow_str = found_domain.as_ref().map(|d| d.to_str());
115        let found_domain_str = found_domain_cow_str.as_deref();
116
117        let url_path = request.uri().path_or_root();
118        let url_query = request.uri().query_or_empty();
119
120        // This ugly macro is needed, unfortunately, because `tracing::span!`
121        // required the level argument to be static. Meaning we can't just pass
122        // `self.level`.
123        macro_rules! make_span {
124            ($level:expr) => {
125                if self.include_headers {
126                    tracing::span!(
127                        $level,
128                        "request",
129                        otel.name = self.otel_name.as_str(),
130                        http.request.method = %request.method(),
131                        url.full = %request.request_uri(),
132                        url.domain = found_domain_str,
133                        url.port = found_port,
134                        url.path = %url_path.as_ref(),
135                        url.query = %url_query.as_ref(),
136                        url.scheme = found_scheme,
137                        network.protocol.name = "http",
138                        network.protocol.version = version_as_protocol_version(request.version()),
139                        user_agent.original = %request.headers().get(USER_AGENT).and_then(|v| v.to_str().ok()).unwrap_or_default(),
140                        headers = ?request.headers(),
141                    )
142                } else {
143                    tracing::span!(
144                        $level,
145                        "request",
146                        otel.name = self.otel_name.as_str(),
147                        http.request.method = %request.method(),
148                        url.full = %request.request_uri(),
149                        url.domain = found_domain_str,
150                        url.port = found_port,
151                        url.path = %url_path.as_ref(),
152                        url.query = %url_query.as_ref(),
153                        url.scheme = found_scheme,
154                        network.protocol.name = "http",
155                        network.protocol.version = version_as_protocol_version(request.version()),
156                        user_agent.original = %request.headers().get(USER_AGENT).and_then(|v| v.to_str().ok()).unwrap_or_default(),
157                    )
158                }
159            }
160        }
161
162        match self.level {
163            Level::ERROR => make_span!(Level::ERROR),
164            Level::WARN => make_span!(Level::WARN),
165            Level::INFO => make_span!(Level::INFO),
166            Level::DEBUG => make_span!(Level::DEBUG),
167            Level::TRACE => make_span!(Level::TRACE),
168        }
169    }
170}