tracing_actix_web/
root_span_macro.rs

1#[macro_export]
2/// `root_span!` creates a new [`tracing::Span`].
3/// It empowers you to add custom properties to the root span on top of the HTTP properties tracked
4/// by [`DefaultRootSpanBuilder`].
5///
6/// # Why a macro?
7///
8/// `tracing` requires all the properties attached to a span to be declared upfront, when the span is created.
9/// You cannot add new ones afterwards.
10/// This makes it extremely fast, but it pushes us to reach for macros when we need some level of composition.
11///
12/// # Macro syntax
13///
14/// The first argument passed to `root_span!` must be a reference to an [`actix_web::dev::ServiceRequest`].
15///
16/// ```rust
17/// use actix_web::body::MessageBody;
18/// use actix_web::dev::{ServiceResponse, ServiceRequest};
19/// use actix_web::Error;
20/// use tracing_actix_web::{TracingLogger, DefaultRootSpanBuilder, RootSpanBuilder, root_span};
21/// use tracing::Span;
22///
23/// pub struct CustomRootSpanBuilder;
24///
25/// impl RootSpanBuilder for CustomRootSpanBuilder {
26///     fn on_request_start(request: &ServiceRequest) -> Span {
27///         root_span!(request)
28///     }
29///
30///     fn on_request_end<B: MessageBody>(span: Span, outcome: &Result<ServiceResponse<B>, Error>) {
31///         DefaultRootSpanBuilder::on_request_end(span, outcome);
32///     }
33/// }
34/// ```
35///
36/// If nothing else is specified, the span generated by `root_span!` is identical to the one you'd
37/// get by using `DefaultRootSpanBuilder`.
38///
39/// You can define new fields following the same syntax of `tracing::info_span!` for fields:
40///
41/// ```rust,should_panic
42/// # let request: &actix_web::dev::ServiceRequest = todo!();
43/// use tracing_actix_web::Level;
44///
45/// // Define a `client_id` field as empty. It might be populated later.
46/// tracing_actix_web::root_span!(request, client_id = tracing::field::Empty);
47///
48/// // Define a `name` field with a known value, `AppName`.
49/// tracing_actix_web::root_span!(request, name = "AppName");
50///
51/// // Define an `app_id` field using the variable with the same name as value.
52/// let app_id = "XYZ";
53/// tracing_actix_web::root_span!(request, app_id);
54///
55/// // Use a custom level, `DEBUG`, instead of the default (`INFO`).
56/// tracing_actix_web::root_span!(level = Level::DEBUG, request);
57///
58/// // All together
59/// tracing_actix_web::root_span!(request, client_id = tracing::field::Empty, name = "AppName", app_id);
60/// ```
61///
62/// [`DefaultRootSpanBuilder`]: crate::DefaultRootSpanBuilder
63macro_rules! root_span {
64    // Vanilla root span, with no additional fields
65    ($request:ident) => {
66        $crate::root_span!($request,)
67    };
68    // Vanilla root span, with a level but no additional fields
69    (level = $lvl:expr, $request:ident) => {
70        $crate::root_span!(level = $lvl, $request,)
71    };
72    // One or more additional fields, comma separated, without a level
73    ($request:ident, $($field:tt)*) => {
74        $crate::root_span!(level = $crate::Level::INFO, $request, $($field)*)
75    };
76    // One or more additional fields, comma separated
77    (level = $lvl:expr, $request:ident, $($field:tt)*) => {
78        {
79            let user_agent = $request
80                .headers()
81                .get("User-Agent")
82                .map(|h| h.to_str().unwrap_or(""))
83                .unwrap_or("");
84            let http_route: std::borrow::Cow<'static, str> = $request
85                .match_pattern()
86                .map(Into::into)
87                .unwrap_or_else(|| "default".into());
88            let http_method = $crate::root_span_macro::private::http_method_str($request.method());
89            let connection_info = $request.connection_info();
90            let request_id = $crate::root_span_macro::private::get_request_id($request);
91
92            macro_rules! inner_span {
93                ($level:expr) => {
94                    $crate::root_span_macro::private::tracing::span!(
95                        $level,
96                        "HTTP request",
97                        http.method = %http_method,
98                        http.route = %http_route,
99                        http.flavor = %$crate::root_span_macro::private::http_flavor($request.version()),
100                        http.scheme = %$crate::root_span_macro::private::http_scheme(connection_info.scheme()),
101                        http.host = %connection_info.host(),
102                        http.client_ip = %$request.connection_info().realip_remote_addr().unwrap_or(""),
103                        http.user_agent = %user_agent,
104                        http.target = %$request.uri().path_and_query().map(|p| p.as_str()).unwrap_or(""),
105                        http.status_code = $crate::root_span_macro::private::tracing::field::Empty,
106                        otel.name = %format!("{} {}", http_method, http_route),
107                        otel.kind = "server",
108                        otel.status_code = $crate::root_span_macro::private::tracing::field::Empty,
109                        trace_id = $crate::root_span_macro::private::tracing::field::Empty,
110                        request_id = %request_id,
111                        exception.message = $crate::root_span_macro::private::tracing::field::Empty,
112                        // Not proper OpenTelemetry, but their terminology is fairly exception-centric
113                        exception.details = $crate::root_span_macro::private::tracing::field::Empty,
114                        $($field)*
115                    )
116                };
117            }
118            let span = match $lvl {
119                $crate::Level::TRACE => inner_span!($crate::Level::TRACE),
120                $crate::Level::DEBUG => inner_span!($crate::Level::DEBUG),
121                $crate::Level::INFO => inner_span!($crate::Level::INFO),
122                $crate::Level::WARN => inner_span!($crate::Level::WARN),
123                $crate::Level::ERROR => inner_span!($crate::Level::ERROR),
124            };
125            std::mem::drop(connection_info);
126
127            // Previously, this line was instrumented with an opentelemetry-specific feature
128            // flag check. However, this resulted in the feature flags being resolved in the crate
129            // which called `root_span!` as opposed to being resolved by this crate as expected.
130            // Therefore, this function simply wraps an internal function with the feature flags
131            // to ensure that the flags are resolved against this crate.
132            $crate::root_span_macro::private::set_otel_parent(&$request, &span);
133
134            span
135        }
136    };
137}
138
139#[doc(hidden)]
140pub mod private {
141    //! This module exposes and re-exports various functions and traits as public in order to leverage them
142    //! in the code generated by the `root_span` macro.
143    //! Items in this module are not part of the public interface of `tracing-actix-web` - they are considered
144    //! implementation details and will change without notice in patch, minor and major releases.
145    use crate::RequestId;
146    use actix_web::dev::ServiceRequest;
147    use actix_web::http::{Method, Version};
148    use std::borrow::Cow;
149
150    pub use tracing;
151
152    #[doc(hidden)]
153    // We need to allow unused variables because the function
154    // body is empty if the user of the library chose not to activate
155    // any OTEL feature.
156    #[allow(unused_variables)]
157    pub fn set_otel_parent(req: &ServiceRequest, span: &tracing::Span) {
158        #[cfg(any(
159            feature = "opentelemetry_0_13",
160            feature = "opentelemetry_0_14",
161            feature = "opentelemetry_0_15",
162            feature = "opentelemetry_0_16",
163            feature = "opentelemetry_0_17",
164            feature = "opentelemetry_0_18",
165            feature = "opentelemetry_0_19",
166            feature = "opentelemetry_0_20",
167            feature = "opentelemetry_0_21",
168            feature = "opentelemetry_0_22",
169            feature = "opentelemetry_0_23",
170            feature = "opentelemetry_0_24",
171            feature = "opentelemetry_0_25",
172            feature = "opentelemetry_0_26",
173            feature = "opentelemetry_0_27",
174        ))]
175        crate::otel::set_otel_parent(req, span);
176    }
177
178    #[doc(hidden)]
179    #[inline]
180    pub fn http_method_str(method: &Method) -> Cow<'static, str> {
181        match method {
182            &Method::OPTIONS => "OPTIONS".into(),
183            &Method::GET => "GET".into(),
184            &Method::POST => "POST".into(),
185            &Method::PUT => "PUT".into(),
186            &Method::DELETE => "DELETE".into(),
187            &Method::HEAD => "HEAD".into(),
188            &Method::TRACE => "TRACE".into(),
189            &Method::CONNECT => "CONNECT".into(),
190            &Method::PATCH => "PATCH".into(),
191            other => other.to_string().into(),
192        }
193    }
194
195    #[doc(hidden)]
196    #[inline]
197    pub fn http_flavor(version: Version) -> Cow<'static, str> {
198        match version {
199            Version::HTTP_09 => "0.9".into(),
200            Version::HTTP_10 => "1.0".into(),
201            Version::HTTP_11 => "1.1".into(),
202            Version::HTTP_2 => "2.0".into(),
203            Version::HTTP_3 => "3.0".into(),
204            other => format!("{other:?}").into(),
205        }
206    }
207
208    #[doc(hidden)]
209    #[inline]
210    pub fn http_scheme(scheme: &str) -> Cow<'static, str> {
211        match scheme {
212            "http" => "http".into(),
213            "https" => "https".into(),
214            other => other.to_string().into(),
215        }
216    }
217
218    #[doc(hidden)]
219    pub fn generate_request_id() -> RequestId {
220        RequestId::generate()
221    }
222
223    #[doc(hidden)]
224    pub fn get_request_id(request: &ServiceRequest) -> RequestId {
225        use actix_web::HttpMessage;
226
227        request.extensions().get::<RequestId>().cloned().unwrap()
228    }
229}