reqwest_tracing/reqwest_otel_span_macro.rs
1#[macro_export]
2/// [`reqwest_otel_span!`](crate::reqwest_otel_span) creates a new [`tracing::Span`].
3/// It empowers you to add custom properties to the span on top of the default properties provided by the macro
4///
5/// Default Fields:
6/// - http.request.method
7/// - url.scheme
8/// - server.address
9/// - server.port
10/// - otel.kind
11/// - otel.name
12/// - otel.status_code
13/// - user_agent.original
14/// - http.response.status_code
15/// - error.message
16/// - error.cause_chain
17///
18/// Here are some convenient functions to checkout [`default_on_request_success`], [`default_on_request_failure`],
19/// and [`default_on_request_end`].
20///
21/// # Why a macro?
22///
23/// [`tracing`] requires all the properties attached to a span to be declared upfront, when the span is created.
24/// You cannot add new ones afterwards.
25/// This makes it extremely fast, but it pushes us to reach for macros when we need some level of composition.
26///
27/// # Macro syntax
28///
29/// The first argument is a [span name](https://opentelemetry.io/docs/reference/specification/trace/api/#span).
30/// The second argument passed to [`reqwest_otel_span!`](crate::reqwest_otel_span) is a reference to an [`reqwest::Request`].
31///
32/// ```rust
33/// use reqwest_middleware::Result;
34/// use http::Extensions;
35/// use reqwest::{Request, Response};
36/// use reqwest_tracing::{
37/// default_on_request_end, reqwest_otel_span, ReqwestOtelSpanBackend
38/// };
39/// use tracing::Span;
40///
41/// pub struct CustomReqwestOtelSpanBackend;
42///
43/// impl ReqwestOtelSpanBackend for CustomReqwestOtelSpanBackend {
44/// fn on_request_start(req: &Request, _extension: &mut Extensions) -> Span {
45/// reqwest_otel_span!(name = "reqwest-http-request", req)
46/// }
47///
48/// fn on_request_end(span: &Span, outcome: &Result<Response>, _extension: &mut Extensions) {
49/// default_on_request_end(span, outcome)
50/// }
51/// }
52/// ```
53///
54/// If nothing else is specified, the span generated by `reqwest_otel_span!` is identical to the one you'd
55/// get by using [`DefaultSpanBackend`]. Note that to avoid leaking sensitive information, the
56/// macro doesn't include `url.full`, even though it's required by opentelemetry. You can add the
57/// URL attribute explicitly by using [`SpanBackendWithUrl`] instead of `DefaultSpanBackend` or
58/// adding the field on your own implementation.
59///
60/// You can define new fields following the same syntax of [`tracing::info_span!`] for fields:
61///
62/// ```rust,should_panic
63/// use reqwest_tracing::reqwest_otel_span;
64/// # let request: &reqwest::Request = todo!();
65///
66/// // Define a `time_elapsed` field as empty. It might be populated later.
67/// // (This example is just to show how to inject data - otel already tracks durations)
68/// reqwest_otel_span!(name = "reqwest-http-request", request, time_elapsed = tracing::field::Empty);
69///
70/// // Define a `name` field with a known value, `AppName`.
71/// reqwest_otel_span!(name = "reqwest-http-request", request, name = "AppName");
72///
73/// // Define an `app_id` field using the variable with the same name as value.
74/// let app_id = "XYZ";
75/// reqwest_otel_span!(name = "reqwest-http-request", request, app_id);
76///
77/// // All together
78/// reqwest_otel_span!(name = "reqwest-http-request", request, time_elapsed = tracing::field::Empty, name = "AppName", app_id);
79/// ```
80///
81/// You can also choose to customise the level of the generated span:
82///
83/// ```rust,should_panic
84/// use reqwest_tracing::reqwest_otel_span;
85/// use tracing::Level;
86/// # let request: &reqwest::Request = todo!();
87///
88/// // Reduce the log level for service endpoints/probes
89/// let level = if request.method().as_str() == "POST" {
90/// Level::DEBUG
91/// } else {
92/// Level::INFO
93/// };
94///
95/// // `level =` and name MUST come before the request, in this order
96/// reqwest_otel_span!(level = level, name = "reqwest-http-request", request);
97/// ```
98///
99///
100/// [`DefaultSpanBackend`]: crate::reqwest_otel_span_builder::DefaultSpanBackend
101/// [`SpanBackendWithUrl`]: crate::reqwest_otel_span_builder::DefaultSpanBackend
102/// [`default_on_request_success`]: crate::reqwest_otel_span_builder::default_on_request_success
103/// [`default_on_request_failure`]: crate::reqwest_otel_span_builder::default_on_request_failure
104/// [`default_on_request_end`]: crate::reqwest_otel_span_builder::default_on_request_end
105macro_rules! reqwest_otel_span {
106 // Vanilla root span at default INFO level, with no additional fields
107 (name=$name:expr, $request:ident) => {
108 reqwest_otel_span!(name=$name, $request,)
109 };
110 // Vanilla root span, with no additional fields but custom level
111 (level=$level:expr, name=$name:expr, $request:ident) => {
112 reqwest_otel_span!(level=$level, name=$name, $request,)
113 };
114 // Root span with additional fields, default INFO level
115 (name=$name:expr, $request:ident, $($field:tt)*) => {
116 reqwest_otel_span!(level=$crate::reqwest_otel_span_macro::private::Level::INFO, name=$name, $request, $($field)*)
117 };
118 // Root span with additional fields and custom level
119 (level=$level:expr, name=$name:expr, $request:ident, $($field:tt)*) => {
120 {
121 let method = $request.method();
122 let url = $request.url();
123 let scheme = url.scheme();
124 let host = url.host_str().unwrap_or("");
125 let host_port = url.port_or_known_default().unwrap_or(0) as i64;
126 let otel_name = $name.to_string();
127 let header_default = &::http::HeaderValue::from_static("");
128 let user_agent = format!("{:?}", $request.headers().get("user-agent").unwrap_or(header_default)).replace('"', "");
129
130 // The match here is necessary, because tracing expects the level to be static.
131 match $level {
132 $crate::reqwest_otel_span_macro::private::Level::TRACE => {
133 $crate::request_span!($crate::reqwest_otel_span_macro::private::Level::TRACE, method, scheme, host, host_port, user_agent, otel_name, $($field)*)
134 },
135 $crate::reqwest_otel_span_macro::private::Level::DEBUG => {
136 $crate::request_span!($crate::reqwest_otel_span_macro::private::Level::DEBUG, method, scheme, host, host_port, user_agent, otel_name, $($field)*)
137 },
138 $crate::reqwest_otel_span_macro::private::Level::INFO => {
139 $crate::request_span!($crate::reqwest_otel_span_macro::private::Level::INFO, method, scheme, host, host_port, user_agent, otel_name, $($field)*)
140 },
141 $crate::reqwest_otel_span_macro::private::Level::WARN => {
142 $crate::request_span!($crate::reqwest_otel_span_macro::private::Level::WARN, method, scheme, host, host_port, user_agent, otel_name, $($field)*)
143 },
144 $crate::reqwest_otel_span_macro::private::Level::ERROR => {
145 $crate::request_span!($crate::reqwest_otel_span_macro::private::Level::ERROR, method, scheme, host, host_port, user_agent, otel_name, $($field)*)
146 },
147 }
148 }
149 }
150}
151
152#[doc(hidden)]
153pub mod private {
154 #[doc(hidden)]
155 pub use tracing::{span, Level};
156
157 #[cfg(not(feature = "deprecated_attributes"))]
158 #[doc(hidden)]
159 #[macro_export]
160 macro_rules! request_span {
161 ($level:expr, $method:expr, $scheme:expr, $host:expr, $host_port:expr, $user_agent:expr, $otel_name:expr, $($field:tt)*) => {
162 $crate::reqwest_otel_span_macro::private::span!(
163 $level,
164 "HTTP request",
165 http.request.method = %$method,
166 url.scheme = %$scheme,
167 server.address = %$host,
168 server.port = %$host_port,
169 user_agent.original = %$user_agent,
170 otel.kind = "client",
171 otel.name = %$otel_name,
172 otel.status_code = tracing::field::Empty,
173 http.response.status_code = tracing::field::Empty,
174 error.message = tracing::field::Empty,
175 error.cause_chain = tracing::field::Empty,
176 $($field)*
177 )
178 }
179 }
180
181 // With the deprecated attributes flag enabled, we publish both the old and new attributes.
182 #[cfg(feature = "deprecated_attributes")]
183 #[doc(hidden)]
184 #[macro_export]
185 macro_rules! request_span {
186 ($level:expr, $method:expr, $scheme:expr, $host:expr, $host_port:expr, $user_agent:expr, $otel_name:expr, $($field:tt)*) => {
187 $crate::reqwest_otel_span_macro::private::span!(
188 $level,
189 "HTTP request",
190 http.request.method = %$method,
191 url.scheme = %$scheme,
192 server.address = %$host,
193 server.port = %$host_port,
194 user_agent.original = %$user_agent,
195 otel.kind = "client",
196 otel.name = %$otel_name,
197 otel.status_code = tracing::field::Empty,
198 http.response.status_code = tracing::field::Empty,
199 error.message = tracing::field::Empty,
200 error.cause_chain = tracing::field::Empty,
201 // old attributes
202 http.method = %$method,
203 http.scheme = %$scheme,
204 http.host = %$host,
205 net.host.port = %$host_port,
206 http.user_agent = tracing::field::Empty,
207 http.status_code = tracing::field::Empty,
208 $($field)*
209 )
210 }
211 }
212}