diff --git a/reqwest-tracing/CHANGELOG.md b/reqwest-tracing/CHANGELOG.md index 1f616b3..659676c 100644 --- a/reqwest-tracing/CHANGELOG.md +++ b/reqwest-tracing/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - HTTP URL is captured in traces as the `http.url` attribute. + - Require an explicit otel name in the macros. Reduces cardinality and complies + with otel specification for HTTP bindings. + - Permit customisation of the otel name from the non-macro layer. ## [0.3.1] - 2022-09-21 - Added support for `opentelemetry` version `0.18`. diff --git a/reqwest-tracing/src/lib.rs b/reqwest-tracing/src/lib.rs index 527461c..a24ad55 100644 --- a/reqwest-tracing/src/lib.rs +++ b/reqwest-tracing/src/lib.rs @@ -2,6 +2,52 @@ //! //! Attach [`TracingMiddleware`] to your client to automatically trace HTTP requests. //! +//! The simplest possible usage: +//! ```no_run +//! # use reqwest_middleware::Result; +//! use reqwest_middleware::{ClientBuilder}; +//! use reqwest_tracing::TracingMiddleware; +//! +//! # async fn example() -> Result<()> { +//! let reqwest_client = reqwest::Client::builder().build().unwrap(); +//! let client = ClientBuilder::new(reqwest_client) +//! // Insert the tracing middleware +//! .with(TracingMiddleware::default()) +//! .build(); +//! +//! let resp = client.get("https://truelayer.com").send().await.unwrap(); +//! # Ok(()) +//! # } +//! ``` +//! +//! To customise the span names use [`OtelName`]. +//! ```no_run +//! # use reqwest_middleware::Result; +//! use reqwest_middleware::{ClientBuilder, Extension}; +//! use reqwest_tracing::{ +//! TracingMiddleware, OtelName +//! }; +//! # async fn example() -> Result<()> { +//! let reqwest_client = reqwest::Client::builder().build().unwrap(); +//! let client = ClientBuilder::new(reqwest_client) +//! // Inserts the extension before the request is started +//! .with_init(Extension(OtelName("my-client".into()))) +//! // Makes use of that extension to specify the otel name +//! .with(TracingMiddleware::default()) +//! .build(); +//! +//! let resp = client.get("https://truelayer.com").send().await.unwrap(); +//! +//! // Or specify it on the individual request (will take priority) +//! let resp = client.post("https://api.truelayer.com/payment") +//! .with_extension(OtelName("POST /payment".into())) +//! .send() +//! .await +//! .unwrap(); +//! # Ok(()) +//! # } +//! ``` +//! //! In this example we define a custom span builder to calculate the request time elapsed and we register the [`TracingMiddleware`]. //! //! Note that Opentelemetry tracks start and stop already, there is no need to have a custom builder like this. @@ -21,7 +67,7 @@ //! impl ReqwestOtelSpanBackend for TimeTrace { //! fn on_request_start(req: &Request, extension: &mut Extensions) -> Span { //! extension.insert(Instant::now()); -//! reqwest_otel_span!(req, time_elapsed = tracing::field::Empty) +//! reqwest_otel_span!(name="example-request", req, time_elapsed = tracing::field::Empty) //! } //! //! fn on_request_end(span: &Span, outcome: &Result, extension: &mut Extensions) { @@ -50,9 +96,9 @@ mod reqwest_otel_span_builder; pub use middleware::TracingMiddleware; pub use reqwest_otel_span_builder::{ default_on_request_end, default_on_request_failure, default_on_request_success, - DefaultSpanBackend, ReqwestOtelSpanBackend, ERROR_CAUSE_CHAIN, ERROR_MESSAGE, HTTP_HOST, - HTTP_METHOD, HTTP_SCHEME, HTTP_STATUS_CODE, HTTP_URL, HTTP_USER_AGENT, NET_HOST_PORT, - OTEL_KIND, OTEL_NAME, OTEL_STATUS_CODE, + DefaultSpanBackend, OtelName, ReqwestOtelSpanBackend, ERROR_CAUSE_CHAIN, ERROR_MESSAGE, + HTTP_HOST, HTTP_METHOD, HTTP_SCHEME, HTTP_STATUS_CODE, HTTP_URL, HTTP_USER_AGENT, + NET_HOST_PORT, OTEL_KIND, OTEL_NAME, OTEL_STATUS_CODE, }; #[doc(hidden)] diff --git a/reqwest-tracing/src/middleware.rs b/reqwest-tracing/src/middleware.rs index 6342f61..a3c37d7 100644 --- a/reqwest-tracing/src/middleware.rs +++ b/reqwest-tracing/src/middleware.rs @@ -18,18 +18,18 @@ impl TracingMiddleware { } } -impl Default for TracingMiddleware { - fn default() -> Self { - TracingMiddleware::new() - } -} - impl Clone for TracingMiddleware { fn clone(&self) -> Self { Self::new() } } +impl Default for TracingMiddleware { + fn default() -> Self { + TracingMiddleware::new() + } +} + #[async_trait::async_trait] impl Middleware for TracingMiddleware where diff --git a/reqwest-tracing/src/reqwest_otel_span_builder.rs b/reqwest-tracing/src/reqwest_otel_span_builder.rs index 48901d9..9c3dbdc 100644 --- a/reqwest-tracing/src/reqwest_otel_span_builder.rs +++ b/reqwest-tracing/src/reqwest_otel_span_builder.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::{Request, Response, StatusCode as RequestStatusCode}; use reqwest_middleware::{Error, Result}; @@ -92,8 +94,12 @@ pub fn default_on_request_failure(span: &Span, e: &Error) { pub struct DefaultSpanBackend; impl ReqwestOtelSpanBackend for DefaultSpanBackend { - fn on_request_start(req: &Request, _: &mut Extensions) -> Span { - reqwest_otel_span!(req) + fn on_request_start(req: &Request, ext: &mut Extensions) -> Span { + let name = ext + .get::() + .map(|on| on.0.as_ref()) + .unwrap_or("reqwest-http-client"); + reqwest_otel_span!(name = name, req) } fn on_request_end(span: &Span, outcome: &Result, _: &mut Extensions) { @@ -124,6 +130,39 @@ fn get_span_status(request_status: RequestStatusCode) -> Option<&'static str> { } } +/// [`OtelName`] allows customisation of the name of the spans created by +/// DefaultSpanBackend. +/// +/// Usage: +/// ```no_run +/// # use reqwest_middleware::Result; +/// use reqwest_middleware::{ClientBuilder, Extension}; +/// use reqwest_tracing::{ +/// TracingMiddleware, OtelName +/// }; +/// # async fn example() -> Result<()> { +/// let reqwest_client = reqwest::Client::builder().build().unwrap(); +/// let client = ClientBuilder::new(reqwest_client) +/// // Inserts the extension before the request is started +/// .with_init(Extension(OtelName("my-client".into()))) +/// // Makes use of that extension to specify the otel name +/// .with(TracingMiddleware::default()) +/// .build(); +/// +/// let resp = client.get("https://truelayer.com").send().await.unwrap(); +/// +/// // Or specify it on the individual request (will take priority) +/// let resp = client.post("https://api.truelayer.com/payment") +/// .with_extension(OtelName("POST /payment".into())) +/// .send() +/// .await +/// .unwrap(); +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone)] +pub struct OtelName(pub Cow<'static, str>); + #[cfg(test)] mod tests { use super::*; diff --git a/reqwest-tracing/src/reqwest_otel_span_macro.rs b/reqwest-tracing/src/reqwest_otel_span_macro.rs index ced64b2..8925238 100644 --- a/reqwest-tracing/src/reqwest_otel_span_macro.rs +++ b/reqwest-tracing/src/reqwest_otel_span_macro.rs @@ -26,7 +26,8 @@ /// /// # Macro syntax /// -/// The first argument passed to [`reqwest_otel_span!`](crate::reqwest_otel_span) is a reference to an [`reqwest::Request`]. +/// The first argument is a [span name](https://opentelemetry.io/docs/reference/specification/trace/api/#span). +/// The second argument passed to [`reqwest_otel_span!`](crate::reqwest_otel_span) is a reference to an [`reqwest::Request`]. /// /// ```rust /// use reqwest_middleware::Result; @@ -41,7 +42,7 @@ /// /// impl ReqwestOtelSpanBackend for CustomReqwestOtelSpanBackend { /// fn on_request_start(req: &Request, _extension: &mut Extensions) -> Span { -/// reqwest_otel_span!(req) +/// reqwest_otel_span!(name = "reqwest-http-request", req) /// } /// /// fn on_request_end(span: &Span, outcome: &Result, _extension: &mut Extensions) { @@ -61,17 +62,17 @@ /// /// // Define a `time_elapsed` field as empty. It might be populated later. /// // (This example is just to show how to inject data - otel already tracks durations) -/// reqwest_otel_span!(request, time_elapsed = tracing::field::Empty); +/// reqwest_otel_span!(name = "reqwest-http-request", request, time_elapsed = tracing::field::Empty); /// /// // Define a `name` field with a known value, `AppName`. -/// reqwest_otel_span!(request, name = "AppName"); +/// reqwest_otel_span!(name = "reqwest-http-request", request, name = "AppName"); /// /// // Define an `app_id` field using the variable with the same name as value. /// let app_id = "XYZ"; -/// reqwest_otel_span!(request, app_id); +/// reqwest_otel_span!(name = "reqwest-http-request", request, app_id); /// /// // All together -/// reqwest_otel_span!(request, time_elapsed = tracing::field::Empty, name = "AppName", app_id); +/// reqwest_otel_span!(name = "reqwest-http-request", request, time_elapsed = tracing::field::Empty, name = "AppName", app_id); /// ``` /// /// You can also choose to customise the level of the generated span: @@ -88,8 +89,8 @@ /// Level::INFO /// }; /// -/// // `level =` MUST be the first argument. -/// reqwest_otel_span!(level = level, request); +/// // `level =` and name MUST come before the request, in this order +/// reqwest_otel_span!(level = level, name = "reqwest-http-request", request); /// ``` /// /// @@ -99,27 +100,26 @@ /// [`default_on_request_end`]: crate::reqwest_otel_span_builder::default_on_request_end macro_rules! reqwest_otel_span { // Vanilla root span at default INFO level, with no additional fields - ($request:ident) => { - reqwest_otel_span!($request,) + (name=$name:expr, $request:ident) => { + reqwest_otel_span!(name=$name, $request,) }; // Vanilla root span, with no additional fields but custom level - (level=$level:expr, $request:ident) => { - reqwest_otel_span!(level=$level, $request,) + (level=$level:expr, name=$name:expr, $request:ident) => { + reqwest_otel_span!(level=$level, name=$name, $request,) }; // Root span with additional fields, default INFO level - ($request:ident, $($field:tt)*) => { - reqwest_otel_span!(level=$crate::reqwest_otel_span_macro::private::Level::INFO, $request, $($field)*) + (name=$name:expr, $request:ident, $($field:tt)*) => { + reqwest_otel_span!(level=$crate::reqwest_otel_span_macro::private::Level::INFO, name=$name, $request, $($field)*) }; // Root span with additional fields and custom level - (level=$level:expr, $request:ident, $($field:tt)*) => { + (level=$level:expr, name=$name:expr, $request:ident, $($field:tt)*) => { { let method = $request.method(); let url = $request.url(); let scheme = url.scheme(); let host = url.host_str().unwrap_or(""); let host_port = url.port().unwrap_or(0) as i64; - let path = url.path(); - let otel_name = format!("{} {}", method, path); + let otel_name = $name.to_string(); macro_rules! request_span { ($lvl:expr) => {