From 95710238399c8141c7ea3f42a65b0e8b731eabde Mon Sep 17 00:00:00 2001 From: filip Date: Mon, 22 Dec 2025 13:41:47 +0100 Subject: [PATCH 1/9] wip : libs++ --- Cargo.toml | 22 ++++++++-------- rust-toolchain.toml | 4 +++ rustfmt.toml | 1 + src/axum/http_server.rs | 2 +- src/formatter.rs | 2 +- src/init.rs | 56 ++++++++++++++++++++++++++++------------- src/lib.rs | 1 - src/shutdown.rs | 10 +++++--- src/tracer.rs | 41 +++++++++++++++--------------- 9 files changed, 84 insertions(+), 55 deletions(-) create mode 100644 rust-toolchain.toml create mode 100644 rustfmt.toml diff --git a/Cargo.toml b/Cargo.toml index df0f121..c1380d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = [ "Fernando Goncalves ", "Alefh Sousa ", ] -edition = "2021" +edition = "2024" license = "MIT" description = "utilities for integrating Datadog with tracing" readme = "README.md" @@ -31,17 +31,17 @@ axum = [ axum = { version = "^0.8", optional = true } http = { version = "1", optional = true } pin-project-lite = { version = "0.2", optional = true } -futures-util = { version = "0.3", default-features = false, features = [ -], optional = true } -axum-tracing-opentelemetry = { version = "0.25", optional = true } -tracing-opentelemetry-instrumentation-sdk = { version = "0.16.0", features = ["http"], optional = true } +futures-util = { version = "0.3", default-features = false, features = [], optional = true } +axum-tracing-opentelemetry = { version = "0.30", optional = true } +tracing-opentelemetry-instrumentation-sdk = { version = "0.31.0", features = ["http"], optional = true } tower = { version = "0.4", optional = true } chrono = "^0.4.33" -opentelemetry = { version = "^0.21.0" } -opentelemetry_sdk = { version = "^0.21.2", features = ["rt-tokio"] } -opentelemetry-http = { version = "^0.10.0" } -opentelemetry-datadog = { version = "0.9.0", features = ["reqwest-client"] } -reqwest = { version = "0.11", default-features = false } +opentelemetry = { version = "^0.31.0" } +opentelemetry_sdk = { version = "^0.31.0", features = ["rt-tokio"] } +opentelemetry-http = { version = "^0.31.0" } +# opentelemetry-otlp = { version = "0.31.0", features = ["http", "metrics", "trace"] } +opentelemetry-datadog = { version = "0.19.0", features = ["reqwest-client"] } +reqwest = { version = "0.12", default-features = false } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = [ @@ -50,6 +50,6 @@ tokio = { version = "1", features = [ ], optional = true } tracing = "^0.1.40" tracing-appender = "0.2.3" -tracing-opentelemetry = "^0.22.0" +tracing-opentelemetry = "^0.32.0" tracing-serde = "^0.1.3" tracing-subscriber = { version = "^0.3.18", features = ["env-filter", "json"] } diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..df17573 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +targets = [ "wasm32-unknown-unknown" ] +components = [ "rustfmt", "clippy", "rust-src" ] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..0fb71e6 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +edition = "2024" \ No newline at end of file diff --git a/src/axum/http_server.rs b/src/axum/http_server.rs index 698057e..022f43d 100644 --- a/src/axum/http_server.rs +++ b/src/axum/http_server.rs @@ -4,10 +4,10 @@ use std::error::Error; use tracing::field::Empty; +use tracing_opentelemetry_instrumentation_sdk::TRACING_TARGET; use tracing_opentelemetry_instrumentation_sdk::http::{ http_flavor, http_host, http_method, url_scheme, user_agent, }; -use tracing_opentelemetry_instrumentation_sdk::TRACING_TARGET; pub fn make_span_from_request(req: &http::Request) -> tracing::Span { // [opentelemetry-specification/.../http.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md) diff --git a/src/formatter.rs b/src/formatter.rs index d1ac1b9..7edcf15 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -10,8 +10,8 @@ use std::io; use chrono::Utc; use opentelemetry::trace::{SpanId, TraceContextExt, TraceId}; -use serde::ser::{SerializeMap, Serializer as _}; use serde::Serialize; +use serde::ser::{SerializeMap, Serializer as _}; use tracing::{Event, Subscriber}; use tracing_opentelemetry::OtelData; diff --git a/src/init.rs b/src/init.rs index c21e3fe..01ae9c6 100644 --- a/src/init.rs +++ b/src/init.rs @@ -1,7 +1,8 @@ use crate::formatter::DatadogFormatter; use crate::shutdown::TracerShutdown; -use crate::tracer::build_tracer; -use opentelemetry::trace::TraceError; +use crate::tracer::build_tracer_provider; +use opentelemetry::trace::TracerProvider; +use opentelemetry_sdk::trace::TraceError; use std::env; use tracing::Subscriber; use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; @@ -25,10 +26,12 @@ fn loglevel_filter_layer(dd_enabled: bool) -> EnvFilter { // `otel::setup` set to debug to log detected resources, configuration read and infered let otel_log_level = env::var("OTEL_LOG_LEVEL").unwrap_or_else(|_| "debug".to_string()); - env::set_var( - "RUST_LOG", - format!("{log_level},otel::tracing={axum_tracing_log_level},otel={otel_log_level}"), - ); + unsafe { + env::set_var( + "RUST_LOG", + format!("{log_level},otel::tracing={axum_tracing_log_level},otel={otel_log_level}"), + ); + } EnvFilter::from_default_env() } @@ -57,18 +60,35 @@ pub fn init() -> Result<(WorkerGuard, TracerShutdown), TraceError> { let dd_enabled = env::var("DD_ENABLED").map(|s| s == "true").unwrap_or(false); - let tracer = if dd_enabled { - Some(build_tracer()?) - } else { - None - }; - let telemetry_layer = tracer.map(|tracer| tracing_opentelemetry::layer().with_tracer(tracer)); + if dd_enabled { + let provider = build_tracer_provider()?; + + let telemetry_layer = + tracing_opentelemetry::layer().with_tracer(provider.tracer("DataDogTelemetry")); - Registry::default() - .with(loglevel_filter_layer(dd_enabled)) - .with(log_layer(dd_enabled, non_blocking)) - .with(telemetry_layer) - .init(); + Registry::default() + .with(loglevel_filter_layer(dd_enabled)) + .with(log_layer(dd_enabled, non_blocking)) + .with(telemetry_layer) + .init(); - Ok((guard, TracerShutdown {})) + Ok(( + guard, + TracerShutdown { + tracer_provider: Some(provider), + }, + )) + } else { + Registry::default() + .with(loglevel_filter_layer(dd_enabled)) + .with(log_layer(dd_enabled, non_blocking)) + .init(); + + Ok(( + guard, + TracerShutdown { + tracer_provider: None, + }, + )) + } } diff --git a/src/lib.rs b/src/lib.rs index cff2cc6..db7f807 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,4 +17,3 @@ pub mod shutdown; pub mod tracer; pub use init::init; -pub use opentelemetry::global::shutdown_tracer_provider; diff --git a/src/shutdown.rs b/src/shutdown.rs index 2016a33..a258bc4 100644 --- a/src/shutdown.rs +++ b/src/shutdown.rs @@ -4,10 +4,14 @@ //! //! [`opentelemetry::global::shutdown_trace_provider`]: https://github.com/open-telemetry/opentelemetry-rust/blob/cf46a55420458bfd74a177cd713681369f01f6eb/opentelemetry/src/global/trace.rs#L407 -pub struct TracerShutdown {} +use opentelemetry_sdk::trace::SdkTracerProvider; + +pub struct TracerShutdown { + pub tracer_provider: Option, +} impl TracerShutdown { - pub fn shutdown(&self) { - opentelemetry::global::shutdown_tracer_provider(); + pub fn shutdown(self) { + self.tracer_provider.map(|p| p.shutdown()); } } diff --git a/src/tracer.rs b/src/tracer.rs index 9529905..693ad46 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -5,17 +5,18 @@ //! //! It also contains a convenience function to build a layer with the tracer. use opentelemetry::global; -pub use opentelemetry::trace::{TraceError, TraceId, TraceResult}; +pub use opentelemetry::trace::TraceId; +use opentelemetry::trace::TracerProvider; use opentelemetry_datadog::{ApiVersion, DatadogPropagator}; -use opentelemetry_sdk::trace; +use opentelemetry_sdk::trace::{Config, SdkTracerProvider, TraceError, TraceResult}; use opentelemetry_sdk::trace::{RandomIdGenerator, Sampler, Tracer}; use std::env; use std::time::Duration; use tracing::Subscriber; -use tracing_opentelemetry::{OpenTelemetryLayer, PreSampledTracer}; +use tracing_opentelemetry::OpenTelemetryLayer; use tracing_subscriber::registry::LookupSpan; -pub fn build_tracer() -> TraceResult { +pub fn build_tracer_provider() -> TraceResult { let service_name = env::var("DD_SERVICE") .map_err(|_| <&str as Into>::into("missing DD_SERVICE"))?; @@ -31,28 +32,28 @@ pub fn build_tracer() -> TraceResult { .build() .expect("Could not init datadog http_client"); - let tracer = opentelemetry_datadog::new_pipeline() + let mut trace_config = Config::default(); + trace_config.sampler = Box::new(Sampler::AlwaysOn); + trace_config.id_generator = Box::new(RandomIdGenerator::default()); + + let tracer_provider = opentelemetry_datadog::new_pipeline() .with_http_client(dd_http_client) .with_service_name(service_name) .with_api_version(ApiVersion::Version05) .with_agent_endpoint(format!("http://{dd_host}:{dd_port}")) - .with_trace_config( - trace::Config::default() - .with_sampler(Sampler::AlwaysOn) - .with_id_generator(RandomIdGenerator::default()), - ) - .install_batch(opentelemetry_sdk::runtime::Tokio); + .with_trace_config(trace_config) + .install_batch(); global::set_text_map_propagator(DatadogPropagator::default()); - tracer + tracer_provider } -pub fn build_layer() -> TraceResult> -where - Tracer: opentelemetry::trace::Tracer + PreSampledTracer + 'static, - S: Subscriber + for<'span> LookupSpan<'span>, -{ - let tracer = build_tracer()?; - Ok(tracing_opentelemetry::layer().with_tracer(tracer)) -} +// pub fn build_layer() -> TraceResult> +// where +// Tracer: opentelemetry::trace::Tracer + 'static, +// S: Subscriber + for<'span> LookupSpan<'span>, +// { +// let tracer = build_tracer()?; +// Ok(tracing_opentelemetry::layer().with_tracer(tracer.tracer("DataDogTelemetry"))) +// } From bec0aa9f76f8754286e35a926d12357d8eb960a2 Mon Sep 17 00:00:00 2001 From: filip Date: Mon, 22 Dec 2025 13:56:13 +0100 Subject: [PATCH 2/9] lints --- src/formatter.rs | 31 +++++++++++-------------------- src/tracer.rs | 6 +----- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/formatter.rs b/src/formatter.rs index 7edcf15..ee14bce 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -9,7 +9,7 @@ use std::io; use chrono::Utc; -use opentelemetry::trace::{SpanId, TraceContextExt, TraceId}; +use opentelemetry::trace::{SpanId, TraceId}; use serde::Serialize; use serde::ser::{SerializeMap, Serializer as _}; use tracing::{Event, Subscriber}; @@ -45,16 +45,9 @@ fn lookup_trace_info(span_ref: &SpanRef) -> Option where S: Subscriber + for<'a> LookupSpan<'a>, { - span_ref.extensions().get::().map(|o| { - let trace_id = if o.parent_cx.has_active_span() { - o.parent_cx.span().span_context().trace_id() - } else { - o.builder.trace_id.unwrap_or(TraceId::INVALID) - }; - TraceInfo { - trace_id: trace_id.into(), - span_id: o.builder.span_id.unwrap_or(SpanId::INVALID).into(), - } + span_ref.extensions().get::().map(|o| TraceInfo { + trace_id: o.trace_id().unwrap_or(TraceId::INVALID).into(), + span_id: o.span_id().unwrap_or(SpanId::INVALID).into(), }) } @@ -89,11 +82,11 @@ where event.record(&mut visitor); serializer = visitor.take_serializer()?; - if let Some(ref span_ref) = ctx.lookup_current() { - if let Some(trace_info) = lookup_trace_info(span_ref) { - serializer.serialize_entry("dd.span_id", &trace_info.span_id)?; - serializer.serialize_entry("dd.trace_id", &trace_info.trace_id)?; - } + if let Some(ref span_ref) = ctx.lookup_current() + && let Some(trace_info) = lookup_trace_info(span_ref) + { + serializer.serialize_entry("dd.span_id", &trace_info.span_id)?; + serializer.serialize_entry("dd.trace_id", &trace_info.trace_id)?; } serializer.end() @@ -119,11 +112,9 @@ impl<'a> io::Write for WriteAdaptor<'a> { let s = std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - self.fmt_write - .write_str(s) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + self.fmt_write.write_str(s).map_err(io::Error::other)?; - Ok(s.as_bytes().len()) + Ok(s.len()) } fn flush(&mut self) -> io::Result<()> { diff --git a/src/tracer.rs b/src/tracer.rs index 693ad46..c40414f 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -6,15 +6,11 @@ //! It also contains a convenience function to build a layer with the tracer. use opentelemetry::global; pub use opentelemetry::trace::TraceId; -use opentelemetry::trace::TracerProvider; use opentelemetry_datadog::{ApiVersion, DatadogPropagator}; use opentelemetry_sdk::trace::{Config, SdkTracerProvider, TraceError, TraceResult}; -use opentelemetry_sdk::trace::{RandomIdGenerator, Sampler, Tracer}; +use opentelemetry_sdk::trace::{RandomIdGenerator, Sampler}; use std::env; use std::time::Duration; -use tracing::Subscriber; -use tracing_opentelemetry::OpenTelemetryLayer; -use tracing_subscriber::registry::LookupSpan; pub fn build_tracer_provider() -> TraceResult { let service_name = env::var("DD_SERVICE") From ba4b876748ab3ba7774834870f7c344a3c59de9c Mon Sep 17 00:00:00 2001 From: filip Date: Mon, 22 Dec 2025 14:04:21 +0100 Subject: [PATCH 3/9] cleanup --- Cargo.toml | 1 - src/tracer.rs | 9 --------- 2 files changed, 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c1380d2..4d50a3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,6 @@ chrono = "^0.4.33" opentelemetry = { version = "^0.31.0" } opentelemetry_sdk = { version = "^0.31.0", features = ["rt-tokio"] } opentelemetry-http = { version = "^0.31.0" } -# opentelemetry-otlp = { version = "0.31.0", features = ["http", "metrics", "trace"] } opentelemetry-datadog = { version = "0.19.0", features = ["reqwest-client"] } reqwest = { version = "0.12", default-features = false } serde = { version = "1", features = ["derive"] } diff --git a/src/tracer.rs b/src/tracer.rs index c40414f..a2bcf91 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -44,12 +44,3 @@ pub fn build_tracer_provider() -> TraceResult { tracer_provider } - -// pub fn build_layer() -> TraceResult> -// where -// Tracer: opentelemetry::trace::Tracer + 'static, -// S: Subscriber + for<'span> LookupSpan<'span>, -// { -// let tracer = build_tracer()?; -// Ok(tracing_opentelemetry::layer().with_tracer(tracer.tracer("DataDogTelemetry"))) -// } From c44d1653767d984777b38533b15f47cc09384141 Mon Sep 17 00:00:00 2001 From: filip Date: Mon, 22 Dec 2025 14:22:11 +0100 Subject: [PATCH 4/9] wip --- src/axum/http_server.rs | 5 +++-- src/axum/middleware.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/axum/http_server.rs b/src/axum/http_server.rs index 022f43d..01e521e 100644 --- a/src/axum/http_server.rs +++ b/src/axum/http_server.rs @@ -6,14 +6,15 @@ use std::error::Error; use tracing::field::Empty; use tracing_opentelemetry_instrumentation_sdk::TRACING_TARGET; use tracing_opentelemetry_instrumentation_sdk::http::{ - http_flavor, http_host, http_method, url_scheme, user_agent, + http_flavor, http_host, url_scheme, user_agent, }; pub fn make_span_from_request(req: &http::Request) -> tracing::Span { // [opentelemetry-specification/.../http.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md) // [opentelemetry-specification/.../span-general.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/span-general.md) // Can not use const or opentelemetry_semantic_conventions::trace::* for name of records - let http_method = http_method(req.method()); + let http_method = req.method(); + tracing::trace_span!( target: TRACING_TARGET, "HTTP request", diff --git a/src/axum/middleware.rs b/src/axum/middleware.rs index 78893a9..952ba80 100644 --- a/src/axum/middleware.rs +++ b/src/axum/middleware.rs @@ -120,7 +120,7 @@ where let span = http_server::make_span_from_request(&req); let route = http_route(&req); - let method = otel_http::http_method(req.method()); + let method = req.method(); span.record("http.route", route); span.record("otel.name", format!("{method} {route}").trim()); From d79352a7d34c6012bf4b29bfd5f5e391d2ae8ff5 Mon Sep 17 00:00:00 2001 From: filip Date: Mon, 22 Dec 2025 14:28:05 +0100 Subject: [PATCH 5/9] fix crate version conflict --- Cargo.toml | 2 +- src/axum/middleware.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4d50a3f..1156822 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ http = { version = "1", optional = true } pin-project-lite = { version = "0.2", optional = true } futures-util = { version = "0.3", default-features = false, features = [], optional = true } axum-tracing-opentelemetry = { version = "0.30", optional = true } -tracing-opentelemetry-instrumentation-sdk = { version = "0.31.0", features = ["http"], optional = true } +tracing-opentelemetry-instrumentation-sdk = { version = "0.32.0", features = ["http"], optional = true } tower = { version = "0.4", optional = true } chrono = "^0.4.33" opentelemetry = { version = "^0.31.0" } diff --git a/src/axum/middleware.rs b/src/axum/middleware.rs index 952ba80..b47848a 100644 --- a/src/axum/middleware.rs +++ b/src/axum/middleware.rs @@ -116,7 +116,7 @@ where fn call(&mut self, req: Request) -> Self::Future { use tracing_opentelemetry::OpenTelemetrySpanExt; let req = req; - let span = if self.filter.map_or(true, |f| f(req.uri().path())) { + let span = if self.filter.is_none_or(|f| f(req.uri().path())) { let span = http_server::make_span_from_request(&req); let route = http_route(&req); @@ -125,7 +125,7 @@ where span.record("http.route", route); span.record("otel.name", format!("{method} {route}").trim()); - span.set_parent(otel_http::extract_context(req.headers())); + _ = span.set_parent(otel_http::extract_context(req.headers())); span } else { tracing::Span::none() From 135795cba75d5905ad8395c32024c8ecdadf9994 Mon Sep 17 00:00:00 2001 From: filip Date: Mon, 22 Dec 2025 14:36:00 +0100 Subject: [PATCH 6/9] version++ --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1156822..a3136f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "datadog-tracing" -version = "0.3.0" +version = "0.3.1" authors = [ "Fernando Goncalves ", "Alefh Sousa ", From 678554d39e7132b10bbca038eba6d2be0e3a641a Mon Sep 17 00:00:00 2001 From: filip Date: Mon, 9 Mar 2026 15:13:19 +0100 Subject: [PATCH 7/9] feat: gate OTel deps behind datadog feature flag Make OpenTelemetry-related dependencies optional behind a `datadog` feature (enabled by default) so lightweight consumers can depend on just tracing-subscriber without compiling the OTel stack. --- Cargo.toml | 28 +++++++++++++----- src/init.rs | 84 ++++++++++++++++++++++++++++++++++++----------------- src/lib.rs | 3 ++ 3 files changed, 81 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a3136f2..d4ac859 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,19 @@ categories = ["web-programming"] exclude = [".pre-commit-config.yaml"] [features] +default = ["datadog"] +datadog = [ + "dep:chrono", + "dep:opentelemetry", + "dep:opentelemetry_sdk", + "dep:opentelemetry-http", + "dep:opentelemetry-datadog", + "dep:reqwest", + "dep:tracing-opentelemetry", + "dep:tracing-serde", +] axum = [ + "datadog", "dep:axum", "dep:tokio", "dep:axum-tracing-opentelemetry", @@ -35,12 +47,12 @@ futures-util = { version = "0.3", default-features = false, features = [], optio axum-tracing-opentelemetry = { version = "0.30", optional = true } tracing-opentelemetry-instrumentation-sdk = { version = "0.32.0", features = ["http"], optional = true } tower = { version = "0.4", optional = true } -chrono = "^0.4.33" -opentelemetry = { version = "^0.31.0" } -opentelemetry_sdk = { version = "^0.31.0", features = ["rt-tokio"] } -opentelemetry-http = { version = "^0.31.0" } -opentelemetry-datadog = { version = "0.19.0", features = ["reqwest-client"] } -reqwest = { version = "0.12", default-features = false } +chrono = { version = "^0.4.33", optional = true } +opentelemetry = { version = "^0.31.0", optional = true } +opentelemetry_sdk = { version = "^0.31.0", features = ["rt-tokio"], optional = true } +opentelemetry-http = { version = "^0.31.0", optional = true } +opentelemetry-datadog = { version = "0.19.0", features = ["reqwest-client"], optional = true } +reqwest = { version = "0.12", default-features = false, optional = true } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = [ @@ -49,6 +61,6 @@ tokio = { version = "1", features = [ ], optional = true } tracing = "^0.1.40" tracing-appender = "0.2.3" -tracing-opentelemetry = "^0.32.0" -tracing-serde = "^0.1.3" +tracing-opentelemetry = { version = "^0.32.0", optional = true } +tracing-serde = { version = "^0.1.3", optional = true } tracing-subscriber = { version = "^0.3.18", features = ["env-filter", "json"] } diff --git a/src/init.rs b/src/init.rs index 01ae9c6..0557d42 100644 --- a/src/init.rs +++ b/src/init.rs @@ -1,41 +1,60 @@ +use std::env; +#[cfg(feature = "datadog")] +use tracing_appender::non_blocking::NonBlocking; +use tracing_appender::non_blocking::WorkerGuard; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{EnvFilter, Registry}; + +#[cfg(feature = "datadog")] use crate::formatter::DatadogFormatter; +#[cfg(feature = "datadog")] use crate::shutdown::TracerShutdown; +#[cfg(feature = "datadog")] use crate::tracer::build_tracer_provider; +#[cfg(feature = "datadog")] use opentelemetry::trace::TracerProvider; +#[cfg(feature = "datadog")] use opentelemetry_sdk::trace::TraceError; -use std::env; +#[cfg(feature = "datadog")] use tracing::Subscriber; -use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; -use tracing_subscriber::layer::SubscriberExt; +#[cfg(feature = "datadog")] use tracing_subscriber::registry::LookupSpan; -use tracing_subscriber::util::SubscriberInitExt; -use tracing_subscriber::{EnvFilter, Layer, Registry}; +#[cfg(feature = "datadog")] +use tracing_subscriber::Layer; -fn loglevel_filter_layer(dd_enabled: bool) -> EnvFilter { +fn loglevel_filter_layer(#[allow(unused)] dd_enabled: bool) -> EnvFilter { let log_level = env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()); - // `axum_tracing_opentelemetry` should be a level info to emit opentelemetry trace & span - let axum_tracing_log_level = env::var("AXUM_TRACING_LOG_LEVEL").unwrap_or_else(|_| { - if dd_enabled { - "trace".to_string() - } else { - "off".to_string() + #[cfg(feature = "datadog")] + { + let axum_tracing_log_level = env::var("AXUM_TRACING_LOG_LEVEL").unwrap_or_else(|_| { + if dd_enabled { + "trace".to_string() + } else { + "off".to_string() + } + }); + let otel_log_level = env::var("OTEL_LOG_LEVEL").unwrap_or_else(|_| "debug".to_string()); + unsafe { + env::set_var( + "RUST_LOG", + format!("{log_level},otel::tracing={axum_tracing_log_level},otel={otel_log_level}"), + ); } - }); - - // `otel::setup` set to debug to log detected resources, configuration read and infered - let otel_log_level = env::var("OTEL_LOG_LEVEL").unwrap_or_else(|_| "debug".to_string()); + } - unsafe { - env::set_var( - "RUST_LOG", - format!("{log_level},otel::tracing={axum_tracing_log_level},otel={otel_log_level}"), - ); + #[cfg(not(feature = "datadog"))] + { + unsafe { + env::set_var("RUST_LOG", &log_level); + } } EnvFilter::from_default_env() } +#[cfg(feature = "datadog")] fn log_layer( dd_enabled: bool, non_blocking: NonBlocking, @@ -55,23 +74,27 @@ where } } +/// Initialize tracing subscriber. +/// +/// With the `datadog` feature enabled, checks `DD_ENABLED` env var and optionally +/// sets up OpenTelemetry export to Datadog agent. +/// +/// Without the `datadog` feature, sets up a plain human-readable fmt subscriber +/// with `RUST_LOG` env filter. +#[cfg(feature = "datadog")] pub fn init() -> Result<(WorkerGuard, TracerShutdown), TraceError> { let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout()); - let dd_enabled = env::var("DD_ENABLED").map(|s| s == "true").unwrap_or(false); if dd_enabled { let provider = build_tracer_provider()?; - let telemetry_layer = tracing_opentelemetry::layer().with_tracer(provider.tracer("DataDogTelemetry")); - Registry::default() .with(loglevel_filter_layer(dd_enabled)) .with(log_layer(dd_enabled, non_blocking)) .with(telemetry_layer) .init(); - Ok(( guard, TracerShutdown { @@ -83,7 +106,6 @@ pub fn init() -> Result<(WorkerGuard, TracerShutdown), TraceError> { .with(loglevel_filter_layer(dd_enabled)) .with(log_layer(dd_enabled, non_blocking)) .init(); - Ok(( guard, TracerShutdown { @@ -92,3 +114,13 @@ pub fn init() -> Result<(WorkerGuard, TracerShutdown), TraceError> { )) } } + +#[cfg(not(feature = "datadog"))] +pub fn init() -> Result> { + let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout()); + Registry::default() + .with(loglevel_filter_layer(false)) + .with(tracing_subscriber::fmt::layer().with_writer(non_blocking)) + .init(); + Ok(guard) +} diff --git a/src/lib.rs b/src/lib.rs index db7f807..6d52ddb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,9 +11,12 @@ #[cfg(feature = "axum")] pub mod axum; +#[cfg(feature = "datadog")] pub mod formatter; pub mod init; +#[cfg(feature = "datadog")] pub mod shutdown; +#[cfg(feature = "datadog")] pub mod tracer; pub use init::init; From 875f704e0828637811e0a07622a1cfffb679ccff Mon Sep 17 00:00:00 2001 From: filip Date: Mon, 9 Mar 2026 15:27:40 +0100 Subject: [PATCH 8/9] fix: write logs to stderr in non-datadog mode CLI tools need stdout reserved for program output (e.g. --output-json). Logs should go to stderr to avoid mixing with structured output. --- src/init.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init.rs b/src/init.rs index 0557d42..5f40f5c 100644 --- a/src/init.rs +++ b/src/init.rs @@ -117,7 +117,7 @@ pub fn init() -> Result<(WorkerGuard, TracerShutdown), TraceError> { #[cfg(not(feature = "datadog"))] pub fn init() -> Result> { - let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout()); + let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stderr()); Registry::default() .with(loglevel_filter_layer(false)) .with(tracing_subscriber::fmt::layer().with_writer(non_blocking)) From a859881645156f7f2dbb9097e213dcc7c83888e2 Mon Sep 17 00:00:00 2001 From: filip Date: Mon, 9 Mar 2026 15:31:18 +0100 Subject: [PATCH 9/9] feat: add OutputTarget option to init Add `init_with_target(OutputTarget)` to allow callers to choose stdout or stderr for log output. `init()` defaults to stdout for backwards compatibility. --- src/init.rs | 33 +++++++++++++++++++++++++++++++-- src/lib.rs | 2 +- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/init.rs b/src/init.rs index 5f40f5c..e5e21b4 100644 --- a/src/init.rs +++ b/src/init.rs @@ -1,4 +1,5 @@ use std::env; +use std::io; #[cfg(feature = "datadog")] use tracing_appender::non_blocking::NonBlocking; use tracing_appender::non_blocking::WorkerGuard; @@ -6,6 +7,12 @@ use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{EnvFilter, Registry}; +/// Where to write log output. +pub enum OutputTarget { + Stdout, + Stderr, +} + #[cfg(feature = "datadog")] use crate::formatter::DatadogFormatter; #[cfg(feature = "datadog")] @@ -81,9 +88,21 @@ where /// /// Without the `datadog` feature, sets up a plain human-readable fmt subscriber /// with `RUST_LOG` env filter. +/// Initialize tracing with logs written to stdout (default, backwards compatible). #[cfg(feature = "datadog")] pub fn init() -> Result<(WorkerGuard, TracerShutdown), TraceError> { - let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout()); + init_with_target(OutputTarget::Stdout) +} + +/// Initialize tracing with logs written to the specified output target. +#[cfg(feature = "datadog")] +pub fn init_with_target( + target: OutputTarget, +) -> Result<(WorkerGuard, TracerShutdown), TraceError> { + let (non_blocking, guard) = match target { + OutputTarget::Stdout => tracing_appender::non_blocking(io::stdout()), + OutputTarget::Stderr => tracing_appender::non_blocking(io::stderr()), + }; let dd_enabled = env::var("DD_ENABLED").map(|s| s == "true").unwrap_or(false); if dd_enabled { @@ -115,9 +134,19 @@ pub fn init() -> Result<(WorkerGuard, TracerShutdown), TraceError> { } } +/// Initialize tracing with logs written to stdout (default, backwards compatible). #[cfg(not(feature = "datadog"))] pub fn init() -> Result> { - let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stderr()); + init_with_target(OutputTarget::Stdout) +} + +/// Initialize tracing with logs written to the specified output target. +#[cfg(not(feature = "datadog"))] +pub fn init_with_target(target: OutputTarget) -> Result> { + let (non_blocking, guard) = match target { + OutputTarget::Stdout => tracing_appender::non_blocking(io::stdout()), + OutputTarget::Stderr => tracing_appender::non_blocking(io::stderr()), + }; Registry::default() .with(loglevel_filter_layer(false)) .with(tracing_subscriber::fmt::layer().with_writer(non_blocking)) diff --git a/src/lib.rs b/src/lib.rs index 6d52ddb..9dfb306 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,4 +19,4 @@ pub mod shutdown; #[cfg(feature = "datadog")] pub mod tracer; -pub use init::init; +pub use init::{init, init_with_target, OutputTarget};