disable context propagation (#94)

* disable context propagation

* bump version

* removes leftover feature

* fix new test

* add back disabled test
This commit is contained in:
Conrad Ludgate 2023-06-20 18:07:12 +01:00 committed by GitHub
parent 594075583c
commit b8b9400858
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 119 additions and 28 deletions

View file

@ -18,6 +18,7 @@ jobs:
- opentelemetry_0_16 - opentelemetry_0_16
- opentelemetry_0_17 - opentelemetry_0_17
- opentelemetry_0_18 - opentelemetry_0_18
- opentelemetry_0_19
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -44,6 +45,7 @@ jobs:
- opentelemetry_0_16 - opentelemetry_0_16
- opentelemetry_0_17 - opentelemetry_0_17
- opentelemetry_0_18 - opentelemetry_0_18
- opentelemetry_0_19
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -90,6 +92,7 @@ jobs:
- opentelemetry_0_16 - opentelemetry_0_16
- opentelemetry_0_17 - opentelemetry_0_17
- opentelemetry_0_18 - opentelemetry_0_18
- opentelemetry_0_19
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -118,6 +121,7 @@ jobs:
- opentelemetry_0_16 - opentelemetry_0_16
- opentelemetry_0_17 - opentelemetry_0_17
- opentelemetry_0_18 - opentelemetry_0_18
- opentelemetry_0_19
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2

View file

@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.4.5] - 2023-06-20
### Added
- A new extension `DisableOtelPropagation` which stops opentelemetry contexts propagating
- Support for opentelemetry 0.19
## [0.4.4] - 2023-05-15 ## [0.4.4] - 2023-05-15
### Added ### Added

View file

@ -1,6 +1,6 @@
[package] [package]
name = "reqwest-tracing" name = "reqwest-tracing"
version = "0.4.4" version = "0.4.5"
authors = ["Rodrigo Gryzinski <rodrigo.gryzinski@truelayer.com>"] authors = ["Rodrigo Gryzinski <rodrigo.gryzinski@truelayer.com>"]
edition = "2018" edition = "2018"
description = "Opentracing middleware for reqwest." description = "Opentracing middleware for reqwest."
@ -16,6 +16,7 @@ opentelemetry_0_15 = ["opentelemetry_0_15_pkg", "tracing-opentelemetry_0_14_pkg"
opentelemetry_0_16 = ["opentelemetry_0_16_pkg", "tracing-opentelemetry_0_16_pkg"] opentelemetry_0_16 = ["opentelemetry_0_16_pkg", "tracing-opentelemetry_0_16_pkg"]
opentelemetry_0_17 = ["opentelemetry_0_17_pkg", "tracing-opentelemetry_0_17_pkg"] opentelemetry_0_17 = ["opentelemetry_0_17_pkg", "tracing-opentelemetry_0_17_pkg"]
opentelemetry_0_18 = ["opentelemetry_0_18_pkg", "tracing-opentelemetry_0_18_pkg"] opentelemetry_0_18 = ["opentelemetry_0_18_pkg", "tracing-opentelemetry_0_18_pkg"]
opentelemetry_0_19 = ["opentelemetry_0_19_pkg", "tracing-opentelemetry_0_19_pkg"]
[dependencies] [dependencies]
@ -34,12 +35,14 @@ opentelemetry_0_15_pkg = { package = "opentelemetry", version = "0.15.0", option
opentelemetry_0_16_pkg = { package = "opentelemetry", version = "0.16.0", optional = true } opentelemetry_0_16_pkg = { package = "opentelemetry", version = "0.16.0", optional = true }
opentelemetry_0_17_pkg = { package = "opentelemetry", version = "0.17.0", optional = true } opentelemetry_0_17_pkg = { package = "opentelemetry", version = "0.17.0", optional = true }
opentelemetry_0_18_pkg = { package = "opentelemetry", version = "0.18.0", optional = true } opentelemetry_0_18_pkg = { package = "opentelemetry", version = "0.18.0", optional = true }
opentelemetry_0_19_pkg = { package = "opentelemetry", version = "0.19.0", optional = true }
tracing-opentelemetry_0_12_pkg = { package = "tracing-opentelemetry",version = "0.12.0", optional = true } tracing-opentelemetry_0_12_pkg = { package = "tracing-opentelemetry",version = "0.12.0", optional = true }
tracing-opentelemetry_0_13_pkg = { package = "tracing-opentelemetry", version = "0.13.0", optional = true } tracing-opentelemetry_0_13_pkg = { package = "tracing-opentelemetry", version = "0.13.0", optional = true }
tracing-opentelemetry_0_14_pkg = { package = "tracing-opentelemetry",version = "0.14.0", optional = true } tracing-opentelemetry_0_14_pkg = { package = "tracing-opentelemetry",version = "0.14.0", optional = true }
tracing-opentelemetry_0_16_pkg = { package = "tracing-opentelemetry",version = "0.16.0", optional = true } tracing-opentelemetry_0_16_pkg = { package = "tracing-opentelemetry",version = "0.16.0", optional = true }
tracing-opentelemetry_0_17_pkg = { package = "tracing-opentelemetry",version = "0.17.0", optional = true } tracing-opentelemetry_0_17_pkg = { package = "tracing-opentelemetry",version = "0.17.0", optional = true }
tracing-opentelemetry_0_18_pkg = { package = "tracing-opentelemetry",version = "0.18.0", optional = true } tracing-opentelemetry_0_18_pkg = { package = "tracing-opentelemetry",version = "0.18.0", optional = true }
tracing-opentelemetry_0_19_pkg = { package = "tracing-opentelemetry",version = "0.19.0", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.0", features = ["js"] } getrandom = { version = "0.2.0", features = ["js"] }

View file

@ -90,16 +90,17 @@ mod middleware;
feature = "opentelemetry_0_16", feature = "opentelemetry_0_16",
feature = "opentelemetry_0_17", feature = "opentelemetry_0_17",
feature = "opentelemetry_0_18", feature = "opentelemetry_0_18",
feature = "opentelemetry_0_19",
))] ))]
mod otel; mod otel;
mod reqwest_otel_span_builder; 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,
default_span_name, DefaultSpanBackend, OtelName, OtelPathNames, ReqwestOtelSpanBackend, default_span_name, DefaultSpanBackend, DisableOtelPropagation, OtelName, OtelPathNames,
SpanBackendWithUrl, ERROR_CAUSE_CHAIN, ERROR_MESSAGE, HTTP_HOST, HTTP_METHOD, HTTP_SCHEME, ReqwestOtelSpanBackend, SpanBackendWithUrl, ERROR_CAUSE_CHAIN, ERROR_MESSAGE, HTTP_HOST,
HTTP_STATUS_CODE, HTTP_URL, HTTP_USER_AGENT, NET_HOST_PORT, OTEL_KIND, OTEL_NAME, HTTP_METHOD, HTTP_SCHEME, HTTP_STATUS_CODE, HTTP_URL, HTTP_USER_AGENT, NET_HOST_PORT,
OTEL_STATUS_CODE, OTEL_KIND, OTEL_NAME, OTEL_STATUS_CODE,
}; };
#[doc(hidden)] #[doc(hidden)]

View file

@ -45,8 +45,6 @@ where
let request_span = ReqwestOtelSpan::on_request_start(&req, extensions); let request_span = ReqwestOtelSpan::on_request_start(&req, extensions);
let outcome_future = 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( #[cfg(any(
feature = "opentelemetry_0_13", feature = "opentelemetry_0_13",
feature = "opentelemetry_0_14", feature = "opentelemetry_0_14",
@ -54,8 +52,15 @@ where
feature = "opentelemetry_0_16", feature = "opentelemetry_0_16",
feature = "opentelemetry_0_17", feature = "opentelemetry_0_17",
feature = "opentelemetry_0_18", feature = "opentelemetry_0_18",
feature = "opentelemetry_0_19",
))] ))]
let req = crate::otel::inject_opentelemetry_context_into_request(req); let req = if !extensions.contains::<crate::DisableOtelPropagation>() {
// 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.
crate::otel::inject_opentelemetry_context_into_request(req)
} else {
req
};
// Run the request // Run the request
let outcome = next.run(req, extensions).await; let outcome = next.run(req, extensions).await;

View file

@ -21,6 +21,9 @@ use opentelemetry_0_17_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_18")] #[cfg(feature = "opentelemetry_0_18")]
use opentelemetry_0_18_pkg as opentelemetry; use opentelemetry_0_18_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_19")]
use opentelemetry_0_19_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_13")] #[cfg(feature = "opentelemetry_0_13")]
pub use tracing_opentelemetry_0_12_pkg as tracing_opentelemetry; pub use tracing_opentelemetry_0_12_pkg as tracing_opentelemetry;
@ -39,6 +42,9 @@ pub use tracing_opentelemetry_0_17_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_18")] #[cfg(feature = "opentelemetry_0_18")]
pub use tracing_opentelemetry_0_18_pkg as tracing_opentelemetry; pub use tracing_opentelemetry_0_18_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_19")]
pub use tracing_opentelemetry_0_19_pkg as tracing_opentelemetry;
use opentelemetry::global; use opentelemetry::global;
use opentelemetry::propagation::Injector; use opentelemetry::propagation::Injector;
use tracing_opentelemetry::OpenTelemetrySpanExt; use tracing_opentelemetry::OpenTelemetrySpanExt;
@ -80,10 +86,13 @@ impl<'a> Injector for RequestCarrier<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::sync::OnceLock;
use super::*; use super::*;
use crate::TracingMiddleware; use crate::{DisableOtelPropagation, TracingMiddleware};
use opentelemetry::sdk::propagation::TraceContextPropagator; use opentelemetry::sdk::propagation::TraceContextPropagator;
use reqwest_middleware::ClientBuilder; use reqwest::Response;
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, Extension};
use tracing::{info_span, Instrument, Level}; use tracing::{info_span, Instrument, Level};
#[cfg(any( #[cfg(any(
feature = "opentelemetry_0_13", feature = "opentelemetry_0_13",
@ -99,17 +108,22 @@ mod test {
use tracing_subscriber_0_3::{filter, layer::SubscriberExt, Registry}; use tracing_subscriber_0_3::{filter, layer::SubscriberExt, Registry};
use wiremock::{matchers::any, Mock, MockServer, ResponseTemplate}; use wiremock::{matchers::any, Mock, MockServer, ResponseTemplate};
#[tokio::test] async fn make_echo_request_in_otel_context(client: ClientWithMiddleware) -> Response {
async fn tracing_middleware_propagates_otel_data_even_when_the_span_is_disabled() { static TELEMETRY: OnceLock<()> = OnceLock::new();
let tracer = opentelemetry::sdk::export::trace::stdout::new_pipeline()
.with_writer(std::io::sink()) TELEMETRY.get_or_init(|| {
.install_simple(); let tracer = opentelemetry::sdk::export::trace::stdout::new_pipeline()
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); .with_writer(std::io::sink())
let subscriber = Registry::default() .install_simple();
.with(filter::Targets::new().with_target("reqwest_tracing::otel::test", Level::DEBUG)) let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
.with(telemetry); let subscriber = Registry::default()
tracing::subscriber::set_global_default(subscriber).unwrap(); .with(
global::set_text_map_propagator(TraceContextPropagator::new()); filter::Targets::new().with_target("reqwest_tracing::otel::test", Level::DEBUG),
)
.with(telemetry);
tracing::subscriber::set_global_default(subscriber).unwrap();
global::set_text_map_propagator(TraceContextPropagator::new());
});
// Mock server - sends all request headers back in the response // Mock server - sends all request headers back in the response
let server = MockServer::start().await; let server = MockServer::start().await;
@ -124,17 +138,40 @@ mod test {
.mount(&server) .mount(&server)
.await; .await;
let client = ClientBuilder::new(reqwest::Client::new()) client
.with(TracingMiddleware::default())
.build();
let resp = client
.get(server.uri()) .get(server.uri())
.send() .send()
.instrument(info_span!("some_span")) .instrument(info_span!("some_span"))
.await .await
.unwrap(); .unwrap()
}
assert!(resp.headers().contains_key("traceparent")); #[tokio::test]
async fn tracing_middleware_propagates_otel_data_even_when_the_span_is_disabled() {
let client = ClientBuilder::new(reqwest::Client::new())
.with(TracingMiddleware::default())
.build();
let resp = make_echo_request_in_otel_context(client).await;
assert!(
resp.headers().contains_key("traceparent"),
"by default, the tracing middleware will propagate otel contexts"
);
}
#[tokio::test]
async fn context_no_propagated() {
let client = ClientBuilder::new(reqwest::Client::new())
.with_init(Extension(DisableOtelPropagation))
.with(TracingMiddleware::default())
.build();
let resp = make_echo_request_in_otel_context(client).await;
assert!(
!resp.headers().contains_key("traceparent"),
"request should not contain traceparent if context propagation is disabled"
);
} }
} }

View file

@ -276,6 +276,41 @@ impl OtelPathNames {
} }
} }
/// `DisableOtelPropagation` disables opentelemetry header propagation, while still tracing the HTTP request.
///
/// By default, the [`TracingMiddleware`](super::TracingMiddleware) middleware will also propagate any opentelemtry
/// contexts to the server. For any external facing requests, this can be problematic and it should be disabled.
///
/// Usage:
/// ```no_run
/// # use reqwest_middleware::Result;
/// use reqwest_middleware::{ClientBuilder, Extension};
/// use reqwest_tracing::{
/// TracingMiddleware, DisableOtelPropagation
/// };
/// # 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(DisableOtelPropagation))
/// // 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(DisableOtelPropagation)
/// .send()
/// .await
/// .unwrap();
/// # Ok(())
/// # }
/// ```
#[derive(Clone)]
pub struct DisableOtelPropagation;
/// Removes the username and/or password parts of the url, if present. /// Removes the username and/or password parts of the url, if present.
fn remove_credentials(url: &Url) -> Cow<'_, str> { fn remove_credentials(url: &Url) -> Cow<'_, str> {
if !url.username().is_empty() || url.password().is_some() { if !url.username().is_empty() || url.password().is_some() {