Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[package]
name = "datadog-tracing"
version = "0.3.0"
version = "0.3.1"
authors = [
"Fernando Goncalves <fernando.hsgoncalves@gmail.com>",
"Alefh Sousa <alefh.sousa@gmail.com>",
]
edition = "2021"
edition = "2024"
license = "MIT"
description = "utilities for integrating Datadog with tracing"
readme = "README.md"
Expand All @@ -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",
Expand All @@ -31,17 +43,16 @@ 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.32.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 }
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 = [
Expand All @@ -50,6 +61,6 @@ tokio = { version = "1", features = [
], optional = true }
tracing = "^0.1.40"
tracing-appender = "0.2.3"
tracing-opentelemetry = "^0.22.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"] }
4 changes: 4 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
targets = [ "wasm32-unknown-unknown" ]
components = [ "rustfmt", "clippy", "rust-src" ]
1 change: 1 addition & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
edition = "2024"
7 changes: 4 additions & 3 deletions src/axum/http_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
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,
};
use tracing_opentelemetry_instrumentation_sdk::TRACING_TARGET;

pub fn make_span_from_request<B>(req: &http::Request<B>) -> 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",
Expand Down
6 changes: 3 additions & 3 deletions src/axum/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,16 @@ where
fn call(&mut self, req: Request<B>) -> 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);
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());

span.set_parent(otel_http::extract_context(req.headers()));
_ = span.set_parent(otel_http::extract_context(req.headers()));
span
} else {
tracing::Span::none()
Expand Down
33 changes: 12 additions & 21 deletions src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
use std::io;

use chrono::Utc;
use opentelemetry::trace::{SpanId, TraceContextExt, TraceId};
use serde::ser::{SerializeMap, Serializer as _};
use opentelemetry::trace::{SpanId, TraceId};
use serde::Serialize;
use serde::ser::{SerializeMap, Serializer as _};
use tracing::{Event, Subscriber};
use tracing_opentelemetry::OtelData;

Expand Down Expand Up @@ -45,16 +45,9 @@ fn lookup_trace_info<S>(span_ref: &SpanRef<S>) -> Option<TraceInfo>
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
span_ref.extensions().get::<OtelData>().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::<OtelData>().map(|o| TraceInfo {
trace_id: o.trace_id().unwrap_or(TraceId::INVALID).into(),
span_id: o.span_id().unwrap_or(SpanId::INVALID).into(),
})
}

Expand Down Expand Up @@ -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()
Expand All @@ -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<()> {
Expand Down
147 changes: 114 additions & 33 deletions src/init.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,67 @@
use std::env;
use std::io;
#[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};

/// Where to write log output.
pub enum OutputTarget {
Stdout,
Stderr,
}

#[cfg(feature = "datadog")]
use crate::formatter::DatadogFormatter;
#[cfg(feature = "datadog")]
use crate::shutdown::TracerShutdown;
use crate::tracer::build_tracer;
use opentelemetry::trace::TraceError;
use std::env;
#[cfg(feature = "datadog")]
use crate::tracer::build_tracer_provider;
#[cfg(feature = "datadog")]
use opentelemetry::trace::TracerProvider;
#[cfg(feature = "datadog")]
use opentelemetry_sdk::trace::TraceError;
#[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());
}

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<S>(
dd_enabled: bool,
non_blocking: NonBlocking,
Expand All @@ -52,23 +81,75 @@ 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.
/// 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);

let tracer = if dd_enabled {
Some(build_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();
Ok((
guard,
TracerShutdown {
tracer_provider: Some(provider),
},
))
} else {
None
};
let telemetry_layer = tracer.map(|tracer| tracing_opentelemetry::layer().with_tracer(tracer));
Registry::default()
.with(loglevel_filter_layer(dd_enabled))
.with(log_layer(dd_enabled, non_blocking))
.init();
Ok((
guard,
TracerShutdown {
tracer_provider: None,
},
))
}
}

/// Initialize tracing with logs written to stdout (default, backwards compatible).
#[cfg(not(feature = "datadog"))]
pub fn init() -> Result<WorkerGuard, Box<dyn std::error::Error>> {
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<WorkerGuard, Box<dyn std::error::Error>> {
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(dd_enabled))
.with(log_layer(dd_enabled, non_blocking))
.with(telemetry_layer)
.with(loglevel_filter_layer(false))
.with(tracing_subscriber::fmt::layer().with_writer(non_blocking))
.init();

Ok((guard, TracerShutdown {}))
Ok(guard)
}
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +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;
pub use opentelemetry::global::shutdown_tracer_provider;
pub use init::{init, init_with_target, OutputTarget};
10 changes: 7 additions & 3 deletions src/shutdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SdkTracerProvider>,
}

impl TracerShutdown {
pub fn shutdown(&self) {
opentelemetry::global::shutdown_tracer_provider();
pub fn shutdown(self) {
self.tracer_provider.map(|p| p.shutdown());
}
}
Loading