diff --git a/Cargo.lock b/Cargo.lock index 37b46c2fb6..71017ec44c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5940,6 +5940,8 @@ dependencies = [ "nym-http-api-common", "nym-network-defaults", "once_cell", + "opentelemetry", + "opentelemetry_sdk", "reqwest 0.12.24", "serde", "serde_json", diff --git a/common/bin-common/src/opentelemetry/context.rs b/common/bin-common/src/opentelemetry/context.rs index f6f59a85b2..eec89b61ec 100644 --- a/common/bin-common/src/opentelemetry/context.rs +++ b/common/bin-common/src/opentelemetry/context.rs @@ -1,8 +1,7 @@ use opentelemetry::{Context, TraceFlags}; use opentelemetry::propagation::{Injector, Extractor, TextMapPropagator}; use opentelemetry::trace::{SpanContext, TraceContextExt, TraceId}; -use opentelemetry_sdk::propagation::TraceContextPropagator; -use opentelemetry_sdk::trace::IdGenerator; +use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::IdGenerator}; use tracing_opentelemetry::OpenTelemetrySpanExt; use std::collections::HashMap; @@ -19,15 +18,24 @@ impl ContextCarrier { } pub fn new_with_data(data: HashMap) -> Self { + if data.is_empty() { + return ContextCarrier::new_empty(); + } + ContextCarrier { data } } + pub fn new_with_current_context(context: Context) -> Self { - let propagator = TraceContextPropagator::new(); let mut carrier = ContextCarrier::new_empty(); + let propagator = TraceContextPropagator::new(); propagator.inject_context(&context, &mut carrier); carrier } + pub fn iter(&self) -> impl Iterator { + self.data.iter() + } + pub fn from_map(data: HashMap) -> Self { ContextCarrier { data } } @@ -46,6 +54,10 @@ impl ContextCarrier { } }) } + + pub fn extract_traceparent(&self) -> Option { + self.get("traceparent").map(|s| s.to_string()) + } } impl Injector for ContextCarrier { diff --git a/common/http-api-client/Cargo.toml b/common/http-api-client/Cargo.toml index 35da8e0e11..e41f4cdf00 100644 --- a/common/http-api-client/Cargo.toml +++ b/common/http-api-client/Cargo.toml @@ -11,9 +11,12 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default=["tunneling"] +# TODO: Remove otel from default before release +# Opentelemetry should remain an opt-in for debug purposes only +default=["tunneling", "otel"] tunneling=[] network-defaults = ["dep:nym-network-defaults"] +otel = ["nym-bin-common/otel", "opentelemetry", "opentelemetry_sdk"] debug-inventory = ["nym-http-api-client-macro/debug-inventory"] [dependencies] @@ -24,6 +27,8 @@ reqwest = { workspace = true, features = ["json", "gzip", "deflate", "brotli", " http.workspace = true url = { workspace = true } once_cell = { workspace = true } +opentelemetry = { workspace = true, optional = true } +opentelemetry_sdk = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true} diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index 3ac1ef6ea9..86e87e917e 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -986,6 +986,24 @@ impl ApiClientCore for Client { self.apply_hosts_to_req(&mut req); + // if opentelemetry is activated add the current trace context to the request + // TODO featurize opentelemetry + use opentelemetry::Context; + use opentelemetry::propagation::TextMapPropagator; + use opentelemetry_sdk::propagation::TraceContextPropagator; + use nym_bin_common::opentelemetry::context::ContextCarrier; + + let context = Context::current(); + let propagator = TraceContextPropagator::new(); + let mut carrier = ContextCarrier::new_empty(); + propagator.inject_context(&context, &mut carrier); + if let Some(traceparent) = carrier.extract_traceparent() { + if let Ok(header_value) = HeaderValue::from_str(&traceparent) { + req.headers_mut() + .insert("traceparent", header_value); + } + } + let mut rb = RequestBuilder::from_parts(self.reqwest_client.clone(), req); rb = rb diff --git a/common/http-api-common/src/middleware/logging.rs b/common/http-api-common/src/middleware/logging.rs index dfa03d5b50..0e9b63d6a2 100644 --- a/common/http-api-common/src/middleware/logging.rs +++ b/common/http-api-common/src/middleware/logging.rs @@ -56,6 +56,13 @@ async fn log_request( let host = header_map(request.headers().get(HOST), "Unknown Host".to_string()); + // Extract traceparent from headers if it exists + let traceparent = request + .headers() + .get("traceparent") + .and_then(|h| h.to_str().ok()) + .map(|s| s.to_string()); + let start = Instant::now(); // run request through all middleware, incl. extractors let res = next.run(request).await; @@ -82,10 +89,10 @@ async fn log_request( match level { LogLevel::Debug => debug!( - "[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}" + "[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent} traceparent: {traceparent:?}" ), LogLevel::Info => info!( - "[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}" + "[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent} traceparent: {traceparent:?}" ), }