mirror of
https://github.com/TrueLayer/reqwest-middleware.git
synced 2024-12-27 03:16:32 +00:00
Custom otel names (#65)
* Breaking change(macros): require explicit name for tracing middleware Closes: #52 This is suggested by the Opentelemetry spec, which requires "Therefore, HTTP client spans SHOULD be using conservative, low cardinality names formed from the available parameters of an HTTP request, such as "HTTP {METHOD_NAME}". Instrumentation MUST NOT default to using URI path as span name, but MAY provide hooks to allow custom logic to override the default span name. " * Permit customisation of otel span names via OtelName
This commit is contained in:
parent
920cb5ac16
commit
07d154cadf
5 changed files with 117 additions and 29 deletions
|
@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
- HTTP URL is captured in traces as the `http.url` attribute.
|
- 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
|
## [0.3.1] - 2022-09-21
|
||||||
- Added support for `opentelemetry` version `0.18`.
|
- Added support for `opentelemetry` version `0.18`.
|
||||||
|
|
|
@ -2,6 +2,52 @@
|
||||||
//!
|
//!
|
||||||
//! Attach [`TracingMiddleware`] to your client to automatically trace HTTP requests.
|
//! 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`].
|
//! 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.
|
//! 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 {
|
//! impl ReqwestOtelSpanBackend for TimeTrace {
|
||||||
//! fn on_request_start(req: &Request, extension: &mut Extensions) -> Span {
|
//! fn on_request_start(req: &Request, extension: &mut Extensions) -> Span {
|
||||||
//! extension.insert(Instant::now());
|
//! 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<Response>, extension: &mut Extensions) {
|
//! fn on_request_end(span: &Span, outcome: &Result<Response>, extension: &mut Extensions) {
|
||||||
|
@ -50,9 +96,9 @@ mod reqwest_otel_span_builder;
|
||||||
pub use middleware::TracingMiddleware;
|
pub use middleware::TracingMiddleware;
|
||||||
pub use reqwest_otel_span_builder::{
|
pub use reqwest_otel_span_builder::{
|
||||||
default_on_request_end, default_on_request_failure, default_on_request_success,
|
default_on_request_end, default_on_request_failure, default_on_request_success,
|
||||||
DefaultSpanBackend, ReqwestOtelSpanBackend, ERROR_CAUSE_CHAIN, ERROR_MESSAGE, HTTP_HOST,
|
DefaultSpanBackend, OtelName, ReqwestOtelSpanBackend, ERROR_CAUSE_CHAIN, ERROR_MESSAGE,
|
||||||
HTTP_METHOD, HTTP_SCHEME, HTTP_STATUS_CODE, HTTP_URL, HTTP_USER_AGENT, NET_HOST_PORT,
|
HTTP_HOST, HTTP_METHOD, HTTP_SCHEME, HTTP_STATUS_CODE, HTTP_URL, HTTP_USER_AGENT,
|
||||||
OTEL_KIND, OTEL_NAME, OTEL_STATUS_CODE,
|
NET_HOST_PORT, OTEL_KIND, OTEL_NAME, OTEL_STATUS_CODE,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
|
|
@ -18,18 +18,18 @@ impl<S: ReqwestOtelSpanBackend> TracingMiddleware<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TracingMiddleware<DefaultSpanBackend> {
|
|
||||||
fn default() -> Self {
|
|
||||||
TracingMiddleware::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: ReqwestOtelSpanBackend> Clone for TracingMiddleware<S> {
|
impl<S: ReqwestOtelSpanBackend> Clone for TracingMiddleware<S> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TracingMiddleware<DefaultSpanBackend> {
|
||||||
|
fn default() -> Self {
|
||||||
|
TracingMiddleware::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<ReqwestOtelSpan> Middleware for TracingMiddleware<ReqwestOtelSpan>
|
impl<ReqwestOtelSpan> Middleware for TracingMiddleware<ReqwestOtelSpan>
|
||||||
where
|
where
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use reqwest::header::{HeaderMap, HeaderValue};
|
use reqwest::header::{HeaderMap, HeaderValue};
|
||||||
use reqwest::{Request, Response, StatusCode as RequestStatusCode};
|
use reqwest::{Request, Response, StatusCode as RequestStatusCode};
|
||||||
use reqwest_middleware::{Error, Result};
|
use reqwest_middleware::{Error, Result};
|
||||||
|
@ -92,8 +94,12 @@ pub fn default_on_request_failure(span: &Span, e: &Error) {
|
||||||
pub struct DefaultSpanBackend;
|
pub struct DefaultSpanBackend;
|
||||||
|
|
||||||
impl ReqwestOtelSpanBackend for DefaultSpanBackend {
|
impl ReqwestOtelSpanBackend for DefaultSpanBackend {
|
||||||
fn on_request_start(req: &Request, _: &mut Extensions) -> Span {
|
fn on_request_start(req: &Request, ext: &mut Extensions) -> Span {
|
||||||
reqwest_otel_span!(req)
|
let name = ext
|
||||||
|
.get::<OtelName>()
|
||||||
|
.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<Response>, _: &mut Extensions) {
|
fn on_request_end(span: &Span, outcome: &Result<Response>, _: &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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
///
|
///
|
||||||
/// # Macro syntax
|
/// # 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
|
/// ```rust
|
||||||
/// use reqwest_middleware::Result;
|
/// use reqwest_middleware::Result;
|
||||||
|
@ -41,7 +42,7 @@
|
||||||
///
|
///
|
||||||
/// impl ReqwestOtelSpanBackend for CustomReqwestOtelSpanBackend {
|
/// impl ReqwestOtelSpanBackend for CustomReqwestOtelSpanBackend {
|
||||||
/// fn on_request_start(req: &Request, _extension: &mut Extensions) -> Span {
|
/// 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<Response>, _extension: &mut Extensions) {
|
/// fn on_request_end(span: &Span, outcome: &Result<Response>, _extension: &mut Extensions) {
|
||||||
|
@ -61,17 +62,17 @@
|
||||||
///
|
///
|
||||||
/// // Define a `time_elapsed` field as empty. It might be populated later.
|
/// // 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)
|
/// // (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`.
|
/// // 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.
|
/// // Define an `app_id` field using the variable with the same name as value.
|
||||||
/// let app_id = "XYZ";
|
/// let app_id = "XYZ";
|
||||||
/// reqwest_otel_span!(request, app_id);
|
/// reqwest_otel_span!(name = "reqwest-http-request", request, app_id);
|
||||||
///
|
///
|
||||||
/// // All together
|
/// // 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:
|
/// You can also choose to customise the level of the generated span:
|
||||||
|
@ -88,8 +89,8 @@
|
||||||
/// Level::INFO
|
/// Level::INFO
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// // `level =` MUST be the first argument.
|
/// // `level =` and name MUST come before the request, in this order
|
||||||
/// reqwest_otel_span!(level = level, request);
|
/// 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
|
/// [`default_on_request_end`]: crate::reqwest_otel_span_builder::default_on_request_end
|
||||||
macro_rules! reqwest_otel_span {
|
macro_rules! reqwest_otel_span {
|
||||||
// Vanilla root span at default INFO level, with no additional fields
|
// Vanilla root span at default INFO level, with no additional fields
|
||||||
($request:ident) => {
|
(name=$name:expr, $request:ident) => {
|
||||||
reqwest_otel_span!($request,)
|
reqwest_otel_span!(name=$name, $request,)
|
||||||
};
|
};
|
||||||
// Vanilla root span, with no additional fields but custom level
|
// Vanilla root span, with no additional fields but custom level
|
||||||
(level=$level:expr, $request:ident) => {
|
(level=$level:expr, name=$name:expr, $request:ident) => {
|
||||||
reqwest_otel_span!(level=$level, $request,)
|
reqwest_otel_span!(level=$level, name=$name, $request,)
|
||||||
};
|
};
|
||||||
// Root span with additional fields, default INFO level
|
// Root span with additional fields, default INFO level
|
||||||
($request:ident, $($field:tt)*) => {
|
(name=$name:expr, $request:ident, $($field:tt)*) => {
|
||||||
reqwest_otel_span!(level=$crate::reqwest_otel_span_macro::private::Level::INFO, $request, $($field)*)
|
reqwest_otel_span!(level=$crate::reqwest_otel_span_macro::private::Level::INFO, name=$name, $request, $($field)*)
|
||||||
};
|
};
|
||||||
// Root span with additional fields and custom level
|
// 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 method = $request.method();
|
||||||
let url = $request.url();
|
let url = $request.url();
|
||||||
let scheme = url.scheme();
|
let scheme = url.scheme();
|
||||||
let host = url.host_str().unwrap_or("");
|
let host = url.host_str().unwrap_or("");
|
||||||
let host_port = url.port().unwrap_or(0) as i64;
|
let host_port = url.port().unwrap_or(0) as i64;
|
||||||
let path = url.path();
|
let otel_name = $name.to_string();
|
||||||
let otel_name = format!("{} {}", method, path);
|
|
||||||
|
|
||||||
macro_rules! request_span {
|
macro_rules! request_span {
|
||||||
($lvl:expr) => {
|
($lvl:expr) => {
|
||||||
|
|
Loading…
Reference in a new issue