mirror of
https://github.com/TrueLayer/reqwest-middleware.git
synced 2024-12-26 02:46:30 +00:00
Allows adding properties to the span (#43)
Provides reqwest-tracing with a trait ReqwestOtelSpanBackend, allowing custom fields/behaviour to be defined. This is a breaking change as the TracingMiddleware signature now requires you to define a ReqwestOtelSpanBackend to provide it with the desired span behaviour. Co-authored-by: Alessandro Zanin <ale.zanin90@gmail.com> Co-authored-by: tl-flavio-barinas <flavio.barinas@truelayer.com> Co-authored-by: Marco Tormento <tl-marco-tormento@users.noreply.github.com>
This commit is contained in:
parent
8a6b2ea571
commit
13ca870d20
9 changed files with 428 additions and 120 deletions
|
@ -25,9 +25,9 @@ The `reqwest-middleware` client exposes the same interface as a plain `reqwest`
|
|||
# ...
|
||||
[dependencies]
|
||||
reqwest = "0.11"
|
||||
reqwest-middleware = "0.1.1"
|
||||
reqwest-retry = "0.1.1"
|
||||
reqwest-tracing = "0.2.0"
|
||||
reqwest-middleware = "0.1.6"
|
||||
reqwest-retry = "0.1.5"
|
||||
reqwest-tracing = "0.2.3"
|
||||
tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] }
|
||||
```
|
||||
|
||||
|
@ -42,7 +42,7 @@ async fn main() {
|
|||
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3);
|
||||
let client = ClientBuilder::new(reqwest::Client::new())
|
||||
// Trace HTTP requests. See the tracing crate to make use of these traces.
|
||||
.with(TracingMiddleware)
|
||||
.with(TracingMiddleware::default())
|
||||
// Retry failed requests.
|
||||
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
|
||||
.build();
|
||||
|
|
|
@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
# [0.3.0] - 2022-06-10
|
||||
### Breaking
|
||||
- Created `ReqwestOtelSpanBackend` trait with `reqwest_otel_span` macro to provide extendable default request otel fields
|
||||
|
||||
## [0.2.3] - 2022-06-23
|
||||
### Fixed
|
||||
- Fix how we set the OpenTelemetry span status, based on the HTTP response status.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "reqwest-tracing"
|
||||
version = "0.2.3"
|
||||
version = "0.3.0-alpha.0"
|
||||
authors = ["Rodrigo Gryzinski <rodrigo.gryzinski@truelayer.com>"]
|
||||
edition = "2018"
|
||||
description = "Opentracing middleware for reqwest."
|
||||
|
|
|
@ -20,7 +20,7 @@ opentelemetry = "0.17"
|
|||
reqwest = "0.11"
|
||||
reqwest-middleware = "0.1.1"
|
||||
reqwest-retry = "0.1.1"
|
||||
reqwest-tracing = { version = "0.2.1", features = ["opentelemetry_0_17"] }
|
||||
reqwest-tracing = { version = "0.3.0", features = ["opentelemetry_0_17"] }
|
||||
tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] }
|
||||
tracing = "0.1"
|
||||
tracing-opentelemetry = "0.17"
|
||||
|
@ -28,28 +28,47 @@ tracing-subscriber = "0.3"
|
|||
```
|
||||
|
||||
```rust,skip
|
||||
use reqwest_tracing::{default_on_request_end, reqwest_otel_span, ReqwestOtelSpanBackend, TracingMiddleware};
|
||||
use opentelemetry::sdk::export::trace::stdout;
|
||||
use reqwest_middleware::ClientBuilder;
|
||||
use reqwest_tracing::TracingMiddleware;
|
||||
use reqwest::{Request, Response};
|
||||
use reqwest_middleware::{ClientBuilder, Result};
|
||||
use std::time::Instant;
|
||||
use task_local_extensions::Extensions;
|
||||
use tracing::Span;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::Registry;
|
||||
|
||||
pub struct TimeTrace;
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fn on_request_end(span: &Span, outcome: &Result<Response>, extension: &mut Extensions) {
|
||||
let time_elapsed = extension.get::<Instant>().unwrap().elapsed().as_millis() as i64;
|
||||
default_on_request_end(span, outcome);
|
||||
span.record("time_elapsed", &time_elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let tracer = stdout::new_pipeline().install_simple();
|
||||
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
let subscriber = Registry::default().with(telemetry);
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
let tracer = stdout::new_pipeline().install_simple();
|
||||
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
let subscriber = Registry::default().with(telemetry);
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
|
||||
run().await;
|
||||
run().await;
|
||||
}
|
||||
|
||||
async fn run() {
|
||||
let client = ClientBuilder::new(reqwest::Client::new())
|
||||
.with(TracingMiddleware)
|
||||
.build();
|
||||
let client = ClientBuilder::new(reqwest::Client::new())
|
||||
.with(TracingMiddleware::<TimeTrace>::new())
|
||||
.build();
|
||||
|
||||
client.get("https://truelayer.com").send().await.unwrap();
|
||||
client.get("https://truelayer.com").send().await.unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,6 +1,38 @@
|
|||
//! Opentracing middleware implementation for [`reqwest-middleware`].
|
||||
//! Opentracing middleware implementation for [`reqwest_middleware`].
|
||||
//!
|
||||
//! Attach [`TracingMiddleware`] to your client to automatically trace HTTP requests.
|
||||
//!
|
||||
//! In this example we define a custom span builder to calculate the request time elapsed and we register the [`TracingMiddleware`].
|
||||
//! ```rust
|
||||
//! use reqwest_middleware::Result;
|
||||
//! use task_local_extensions::Extensions;
|
||||
//! use reqwest::{Request, Response};
|
||||
//! use reqwest_middleware::ClientBuilder;
|
||||
//! use reqwest_tracing::{
|
||||
//! default_on_request_end, reqwest_otel_span, ReqwestOtelSpanBackend, TracingMiddleware
|
||||
//! };
|
||||
//! use tracing::Span;
|
||||
//! use std::time::{Duration, Instant};
|
||||
//!
|
||||
//! pub struct TimeTrace;
|
||||
//!
|
||||
//! 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)
|
||||
//! }
|
||||
//!
|
||||
//! fn on_request_end(span: &Span, outcome: &Result<Response>, extension: &mut Extensions) {
|
||||
//! let time_elapsed = extension.get::<Instant>().unwrap().elapsed().as_millis() as i64;
|
||||
//! default_on_request_end(span, outcome);
|
||||
//! span.record("time_elapsed", &time_elapsed);
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! let http = ClientBuilder::new(reqwest::Client::new())
|
||||
//! .with(TracingMiddleware::<TimeTrace>::new())
|
||||
//! .build();
|
||||
//! ```
|
||||
|
||||
mod middleware;
|
||||
#[cfg(any(
|
||||
|
@ -11,5 +43,14 @@ mod middleware;
|
|||
feature = "opentelemetry_0_17",
|
||||
))]
|
||||
mod otel;
|
||||
|
||||
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_USER_AGENT, NET_HOST_PORT, OTEL_KIND,
|
||||
OTEL_NAME, OTEL_STATUS_CODE,
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod reqwest_otel_span_macro;
|
||||
|
|
|
@ -1,45 +1,49 @@
|
|||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use reqwest::{Request, Response, StatusCode as RequestStatusCode};
|
||||
use reqwest_middleware::{Error, Middleware, Next, Result};
|
||||
use reqwest::{Request, Response};
|
||||
use reqwest_middleware::{Middleware, Next, Result};
|
||||
use task_local_extensions::Extensions;
|
||||
use tracing::Instrument;
|
||||
|
||||
use crate::{DefaultSpanBackend, ReqwestOtelSpanBackend};
|
||||
|
||||
/// Middleware for tracing requests using the current Opentelemetry Context.
|
||||
pub struct TracingMiddleware;
|
||||
pub struct TracingMiddleware<S: ReqwestOtelSpanBackend> {
|
||||
span_backend: std::marker::PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S: ReqwestOtelSpanBackend> TracingMiddleware<S> {
|
||||
pub fn new() -> TracingMiddleware<S> {
|
||||
TracingMiddleware {
|
||||
span_backend: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TracingMiddleware<DefaultSpanBackend> {
|
||||
fn default() -> Self {
|
||||
TracingMiddleware::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ReqwestOtelSpanBackend> Clone for TracingMiddleware<S> {
|
||||
fn clone(&self) -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Middleware for TracingMiddleware {
|
||||
impl<ReqwestOtelSpan> Middleware for TracingMiddleware<ReqwestOtelSpan>
|
||||
where
|
||||
ReqwestOtelSpan: ReqwestOtelSpanBackend + Sync + Send + 'static,
|
||||
{
|
||||
async fn handle(
|
||||
&self,
|
||||
req: Request,
|
||||
extensions: &mut Extensions,
|
||||
next: Next<'_>,
|
||||
) -> Result<Response> {
|
||||
let request_span = {
|
||||
let method = req.method();
|
||||
let scheme = req.url().scheme();
|
||||
let host = req.url().host_str().unwrap_or("");
|
||||
let host_port = req.url().port().unwrap_or(0) as i64;
|
||||
let path = req.url().path();
|
||||
let otel_name = format!("{} {}", method, path);
|
||||
let request_span = ReqwestOtelSpan::on_request_start(&req, extensions);
|
||||
|
||||
tracing::info_span!(
|
||||
"HTTP request",
|
||||
http.method = %method,
|
||||
http.scheme = %scheme,
|
||||
http.host = %host,
|
||||
net.host.port = %host_port,
|
||||
otel.kind = "client",
|
||||
otel.name = %otel_name,
|
||||
otel.status_code = tracing::field::Empty,
|
||||
http.user_agent = tracing::field::Empty,
|
||||
http.status_code = tracing::field::Empty,
|
||||
error.message = tracing::field::Empty,
|
||||
error.cause_chain = tracing::field::Empty,
|
||||
)
|
||||
};
|
||||
|
||||
async {
|
||||
let outcome_future = async {
|
||||
// Adds tracing headers to the given request to propagate the OpenTelemetry context to downstream revivers of the request.
|
||||
// Spans added by downstream consumers will be part of the same trace.
|
||||
#[cfg(any(
|
||||
|
@ -53,77 +57,10 @@ impl Middleware for TracingMiddleware {
|
|||
|
||||
// Run the request
|
||||
let outcome = next.run(req, extensions).await;
|
||||
match &outcome {
|
||||
Ok(response) => {
|
||||
// The request ran successfully
|
||||
let span_status = get_span_status(response.status());
|
||||
let status_code = response.status().as_u16() as i64;
|
||||
let user_agent = get_header_value("user_agent", response.headers());
|
||||
if let Some(span_status) = span_status {
|
||||
request_span.record("otel.status_code", &span_status);
|
||||
}
|
||||
request_span.record("http.status_code", &status_code);
|
||||
request_span.record("http.user_agent", &user_agent.as_str());
|
||||
}
|
||||
Err(e) => {
|
||||
// The request didn't run successfully
|
||||
let error_message = e.to_string();
|
||||
let error_cause_chain = format!("{:?}", e);
|
||||
request_span.record("otel.status_code", &"ERROR");
|
||||
request_span.record("error.message", &error_message.as_str());
|
||||
request_span.record("error.cause_chain", &error_cause_chain.as_str());
|
||||
if let Error::Reqwest(e) = e {
|
||||
request_span.record(
|
||||
"http.status_code",
|
||||
&e.status()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ReqwestOtelSpan::on_request_end(&request_span, &outcome, extensions);
|
||||
outcome
|
||||
}
|
||||
.instrument(request_span.clone())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn get_header_value(key: &str, headers: &HeaderMap) -> String {
|
||||
let header_default = &HeaderValue::from_static("");
|
||||
format!("{:?}", headers.get(key).unwrap_or(header_default)).replace('"', "")
|
||||
}
|
||||
|
||||
/// HTTP Mapping <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status>
|
||||
///
|
||||
/// Maps the the http status to an Opentelemetry span status following the the specified convention above.
|
||||
fn get_span_status(request_status: RequestStatusCode) -> Option<&'static str> {
|
||||
match request_status.as_u16() {
|
||||
// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, unless there was
|
||||
// another error (e.g., network error receiving the response body; or 3xx codes with max redirects exceeded),
|
||||
// in which case status MUST be set to Error.
|
||||
100..=399 => None,
|
||||
// For HTTP status codes in the 4xx range span status MUST be left unset in case of SpanKind.SERVER and MUST be
|
||||
// set to Error in case of SpanKind.CLIENT.
|
||||
400..=499 => Some("ERROR"),
|
||||
// For HTTP status codes in the 5xx range, as well as any other code the client failed to interpret, span
|
||||
// status MUST be set to Error.
|
||||
_ => Some("ERROR"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_header_value_for_span_attribute() {
|
||||
let expect = "IMPORTANT_HEADER";
|
||||
let mut header_map = HeaderMap::new();
|
||||
header_map.insert("test", expect.parse().unwrap());
|
||||
|
||||
let value = get_header_value("test", &header_map);
|
||||
assert_eq!(value, expect);
|
||||
};
|
||||
|
||||
outcome_future.instrument(request_span.clone()).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ mod test {
|
|||
.await;
|
||||
|
||||
let client = ClientBuilder::new(reqwest::Client::new())
|
||||
.with(TracingMiddleware)
|
||||
.with(TracingMiddleware::default())
|
||||
.build();
|
||||
|
||||
let resp = client
|
||||
|
|
138
reqwest-tracing/src/reqwest_otel_span_builder.rs
Normal file
138
reqwest-tracing/src/reqwest_otel_span_builder.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use reqwest::{Request, Response, StatusCode as RequestStatusCode};
|
||||
use reqwest_middleware::{Error, Result};
|
||||
use task_local_extensions::Extensions;
|
||||
use tracing::Span;
|
||||
|
||||
use crate::reqwest_otel_span;
|
||||
|
||||
/// The `http.method` field added to the span by [`reqwest_otel_span`]
|
||||
pub const HTTP_METHOD: &str = "http.method";
|
||||
/// The `http.scheme` field added to the span by [`reqwest_otel_span`]
|
||||
pub const HTTP_SCHEME: &str = "http.scheme";
|
||||
/// The `http.host` field added to the span by [`reqwest_otel_span`]
|
||||
pub const HTTP_HOST: &str = "http.host";
|
||||
/// The `host.port` field added to the span by [`reqwest_otel_span`]
|
||||
pub const NET_HOST_PORT: &str = "net.host.port";
|
||||
/// The `otel.kind` field added to the span by [`reqwest_otel_span`]
|
||||
pub const OTEL_KIND: &str = "otel.kind";
|
||||
/// The `otel.name` field added to the span by [`reqwest_otel_span`]
|
||||
pub const OTEL_NAME: &str = "otel.name";
|
||||
/// The `otel.status.code` field added to the span by [`reqwest_otel_span`]
|
||||
pub const OTEL_STATUS_CODE: &str = "otel.status_code";
|
||||
/// The `error.message` field added to the span by [`reqwest_otel_span`]
|
||||
pub const ERROR_MESSAGE: &str = "error.message";
|
||||
/// The `error.cause_chain` field added to the span by [`reqwest_otel_span`]
|
||||
pub const ERROR_CAUSE_CHAIN: &str = "error.cause_chain";
|
||||
/// The `http.status_code` field added to the span by [`reqwest_otel_span`]
|
||||
pub const HTTP_STATUS_CODE: &str = "http.status_code";
|
||||
/// The `http.user_agent` added to the span by [`reqwest_otel_span`]
|
||||
pub const HTTP_USER_AGENT: &str = "http.user_agent";
|
||||
|
||||
/// [`ReqwestOtelSpanBackend`] allows you to customise the span attached by
|
||||
/// [`TracingMiddleware`] to incoming requests.
|
||||
///
|
||||
/// Check out [`reqwest_otel_span`] documentation for examples.
|
||||
///
|
||||
/// [`TracingMiddleware`]: crate::middleware::TracingMiddleware.
|
||||
pub trait ReqwestOtelSpanBackend {
|
||||
/// Initalized a new span before the request is executed.
|
||||
fn on_request_start(req: &Request, extension: &mut Extensions) -> Span;
|
||||
|
||||
/// Runs after the request call has executed.
|
||||
fn on_request_end(span: &Span, outcome: &Result<Response>, extension: &mut Extensions);
|
||||
}
|
||||
|
||||
/// Populates default success/failure fields for a given [`reqwest_otel_span!`] span.
|
||||
#[inline]
|
||||
pub fn default_on_request_end(span: &Span, outcome: &Result<Response>) {
|
||||
match outcome {
|
||||
Ok(res) => default_on_request_success(span, res),
|
||||
Err(err) => default_on_request_failure(span, err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Populates default success fields for a given [`reqwest_otel_span!`] span.
|
||||
#[inline]
|
||||
pub fn default_on_request_success(span: &Span, response: &Response) {
|
||||
let span_status = get_span_status(response.status());
|
||||
let status_code = response.status().as_u16() as i64;
|
||||
let user_agent = get_header_value("user_agent", response.headers());
|
||||
if let Some(span_status) = span_status {
|
||||
span.record(OTEL_STATUS_CODE, &span_status);
|
||||
}
|
||||
span.record(HTTP_STATUS_CODE, &status_code);
|
||||
span.record(HTTP_USER_AGENT, &user_agent.as_str());
|
||||
}
|
||||
|
||||
/// Populates default failure fields for a given [`reqwest_otel_span!`] span.
|
||||
#[inline]
|
||||
pub fn default_on_request_failure(span: &Span, e: &Error) {
|
||||
let error_message = e.to_string();
|
||||
let error_cause_chain = format!("{:?}", e);
|
||||
span.record(OTEL_STATUS_CODE, &"ERROR");
|
||||
span.record(ERROR_MESSAGE, &error_message.as_str());
|
||||
span.record(ERROR_CAUSE_CHAIN, &error_cause_chain.as_str());
|
||||
if let Error::Reqwest(e) = e {
|
||||
span.record(
|
||||
HTTP_STATUS_CODE,
|
||||
&e.status()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The default [`ReqwestOtelSpanBackend`] for [`TracingMiddleware`].
|
||||
///
|
||||
/// [`TracingMiddleware`]: crate::middleware::TracingMiddleware
|
||||
pub struct DefaultSpanBackend;
|
||||
|
||||
impl ReqwestOtelSpanBackend for DefaultSpanBackend {
|
||||
fn on_request_start(req: &Request, _: &mut Extensions) -> Span {
|
||||
reqwest_otel_span!(req)
|
||||
}
|
||||
|
||||
fn on_request_end(span: &Span, outcome: &Result<Response>, _: &mut Extensions) {
|
||||
default_on_request_end(span, outcome)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_header_value(key: &str, headers: &HeaderMap) -> String {
|
||||
let header_default = &HeaderValue::from_static("");
|
||||
format!("{:?}", headers.get(key).unwrap_or(header_default)).replace('"', "")
|
||||
}
|
||||
|
||||
/// HTTP Mapping <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status>
|
||||
///
|
||||
/// Maps the the http status to an Opentelemetry span status following the the specified convention above.
|
||||
fn get_span_status(request_status: RequestStatusCode) -> Option<&'static str> {
|
||||
match request_status.as_u16() {
|
||||
// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, unless there was
|
||||
// another error (e.g., network error receiving the response body; or 3xx codes with max redirects exceeded),
|
||||
// in which case status MUST be set to Error.
|
||||
100..=399 => None,
|
||||
// For HTTP status codes in the 4xx range span status MUST be left unset in case of SpanKind.SERVER and MUST be
|
||||
// set to Error in case of SpanKind.CLIENT.
|
||||
400..=499 => Some("ERROR"),
|
||||
// For HTTP status codes in the 5xx range, as well as any other code the client failed to interpret, span
|
||||
// status MUST be set to Error.
|
||||
_ => Some("ERROR"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_header_value_for_span_attribute() {
|
||||
let expect = "IMPORTANT_HEADER";
|
||||
let mut header_map = HeaderMap::new();
|
||||
header_map.insert("test", expect.parse().unwrap());
|
||||
|
||||
let value = get_header_value("test", &header_map);
|
||||
assert_eq!(value, expect);
|
||||
}
|
||||
}
|
169
reqwest-tracing/src/reqwest_otel_span_macro.rs
Normal file
169
reqwest-tracing/src/reqwest_otel_span_macro.rs
Normal file
|
@ -0,0 +1,169 @@
|
|||
#[macro_export]
|
||||
/// [`reqwest_otel_span!`] creates a new [`tracing::Span`].
|
||||
/// It empowers you to add custom properties to the span on top of the default properties provided by the macro
|
||||
///
|
||||
/// Default Fields:
|
||||
/// - http.method
|
||||
/// - http.scheme
|
||||
/// - http.host
|
||||
/// - net.host
|
||||
/// - otel.kind
|
||||
/// - otel.name
|
||||
/// - otel.status_code
|
||||
/// - http.user_agent
|
||||
/// - http.status_code
|
||||
/// - error.message
|
||||
/// - error.cause_chain
|
||||
///
|
||||
/// Here are some convenient functions to checkout [`default_on_request_success`], [`default_on_request_failure`],
|
||||
/// and [`default_on_request_end`].
|
||||
///
|
||||
/// # Why a macro?
|
||||
///
|
||||
/// [`tracing`] requires all the properties attached to a span to be declared upfront, when the span is created.
|
||||
/// You cannot add new ones afterwards.
|
||||
/// This makes it extremely fast, but it pushes us to reach for macros when we need some level of composition.
|
||||
///
|
||||
/// # Macro syntax
|
||||
///
|
||||
/// The first argument passed to [`reqwest_otel_span!`] is a reference to an [`reqwest::Request`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use reqwest_middleware::Result;
|
||||
/// use task_local_extensions::Extensions;
|
||||
/// use reqwest::{Request, Response};
|
||||
/// use reqwest_tracing::{
|
||||
/// default_on_request_end, reqwest_otel_span, ReqwestOtelSpanBackend
|
||||
/// };
|
||||
/// use tracing::Span;
|
||||
///
|
||||
/// pub struct CustomReqwestOtelSpanBackend;
|
||||
///
|
||||
/// impl ReqwestOtelSpanBackend for CustomReqwestOtelSpanBackend {
|
||||
/// fn on_request_start(req: &Request, _extension: &mut Extensions) -> Span {
|
||||
/// reqwest_otel_span!(req)
|
||||
/// }
|
||||
///
|
||||
/// fn on_request_end(span: &Span, outcome: &Result<Response>, _extension: &mut Extensions) {
|
||||
/// default_on_request_end(span, outcome)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// If nothing else is specified, the span generated by `reqwest_otel_span!` is identical to the one you'd
|
||||
/// get by using [`DefaultSpanBackend`].
|
||||
///
|
||||
/// You can define new fields following the same syntax of [`tracing::info_span!`] for fields:
|
||||
///
|
||||
/// ```rust,should_panic
|
||||
/// use reqwest_tracing::reqwest_otel_span;
|
||||
/// # let request: &reqwest::Request = todo!();
|
||||
///
|
||||
/// // Define a `time_elapsed` field as empty. It might be populated later.
|
||||
/// reqwest_otel_span!(request, time_elapsed = tracing::field::Empty);
|
||||
///
|
||||
/// // Define a `name` field with a known value, `AppName`.
|
||||
/// reqwest_otel_span!(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);
|
||||
///
|
||||
/// // All together
|
||||
/// reqwest_otel_span!(request, time_elapsed = tracing::field::Empty, name = "AppName", app_id);
|
||||
/// ```
|
||||
///
|
||||
/// You can also choose to customise the level of the generated span:
|
||||
///
|
||||
/// ```rust,should_panic
|
||||
/// use reqwest_tracing::reqwest_otel_span;
|
||||
/// use tracing::Level;
|
||||
/// # let request: &reqwest::Request = todo!();
|
||||
///
|
||||
/// // Reduce the log level for service endpoints/probes
|
||||
/// let level = if request.method().as_str() == "POST" {
|
||||
/// Level::DEBUG
|
||||
/// } else {
|
||||
/// Level::INFO
|
||||
/// };
|
||||
///
|
||||
/// // `level =` MUST be the first argument.
|
||||
/// reqwest_otel_span!(level = level, request);
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// [`DefaultSpanBackend`]: crate::reqwest_otel_span_builder::DefaultSpanBackend
|
||||
/// [`default_on_request_success`]: crate::reqwest_otel_span_builder::default_on_request_success
|
||||
/// [`default_on_request_failure`]: crate::reqwest_otel_span_builder::default_on_request_failure
|
||||
/// [`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,)
|
||||
};
|
||||
// Vanilla root span, with no additional fields but custom level
|
||||
(level=$level:expr, $request:ident) => {
|
||||
reqwest_otel_span!(level=$level, $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)*)
|
||||
};
|
||||
// Root span with additional fields and custom level
|
||||
(level=$level:expr, $request:ident, $($field:tt)*) => {
|
||||
{
|
||||
let method = $request.method();
|
||||
let scheme = $request.url().scheme();
|
||||
let host = $request.url().host_str().unwrap_or("");
|
||||
let host_port = $request.url().port().unwrap_or(0) as i64;
|
||||
let path = $request.url().path();
|
||||
let otel_name = format!("{} {}", method, path);
|
||||
|
||||
macro_rules! request_span {
|
||||
($lvl:expr) => {
|
||||
$crate::reqwest_otel_span_macro::private::span!(
|
||||
$lvl,
|
||||
"HTTP request",
|
||||
http.method = %method,
|
||||
http.scheme = %scheme,
|
||||
http.host = %host,
|
||||
net.host.port = %host_port,
|
||||
otel.kind = "client",
|
||||
otel.name = %otel_name,
|
||||
otel.status_code = tracing::field::Empty,
|
||||
http.user_agent = tracing::field::Empty,
|
||||
http.status_code = tracing::field::Empty,
|
||||
error.message = tracing::field::Empty,
|
||||
error.cause_chain = tracing::field::Empty,
|
||||
$($field)*
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let span = match $level {
|
||||
$crate::reqwest_otel_span_macro::private::Level::TRACE => {
|
||||
request_span!($crate::reqwest_otel_span_macro::private::Level::TRACE)
|
||||
},
|
||||
$crate::reqwest_otel_span_macro::private::Level::DEBUG => {
|
||||
request_span!($crate::reqwest_otel_span_macro::private::Level::DEBUG)
|
||||
},
|
||||
$crate::reqwest_otel_span_macro::private::Level::INFO => {
|
||||
request_span!($crate::reqwest_otel_span_macro::private::Level::INFO)
|
||||
},
|
||||
$crate::reqwest_otel_span_macro::private::Level::WARN => {
|
||||
request_span!($crate::reqwest_otel_span_macro::private::Level::WARN)
|
||||
},
|
||||
$crate::reqwest_otel_span_macro::private::Level::ERROR => {
|
||||
request_span!($crate::reqwest_otel_span_macro::private::Level::ERROR)
|
||||
},
|
||||
};
|
||||
span
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod private {
|
||||
#[doc(hidden)]
|
||||
pub use tracing::{span, Level};
|
||||
}
|
Loading…
Reference in a new issue