Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c23938d
wip: integrate obfuscation to trace exporter
Eldolfin Mar 30, 2026
f2621f3
fix: cargo fmt
Eldolfin Mar 31, 2026
6659152
feat(data-pipeline): feature-gate stats obfuscation behind stats-obfu…
Eldolfin Mar 31, 2026
0602070
refactor(data-pipeline): use Arc<AtomicBool> for shared obfuscation s…
Eldolfin Mar 31, 2026
d41ad73
fix: cargo fmt
Eldolfin Apr 1, 2026
36cca8f
fix: update LICENSE-3rdparty.yml
Eldolfin Apr 1, 2026
1ffcde1
feat(data-pipeline): read obfuscation config from /info
Eldolfin Apr 3, 2026
02ab474
fix: obfuscation config optional in /info
Eldolfin Apr 8, 2026
0994585
fix: clippy
Eldolfin Apr 13, 2026
5a40e9a
Merge remote-tracking branch 'origin/main' into oscarld/integrate-obf…
Eldolfin Apr 13, 2026
4eb0aa3
fix(lint): remove all cancellation_token references
Eldolfin Apr 13, 2026
50c0469
fix: last clippy error
Eldolfin Apr 13, 2026
e7b04a6
WIP: moving stats obfuscation logic to SpanConcentrator (untested)
Eldolfin Apr 16, 2026
1567e34
fix: clippy
Eldolfin Apr 16, 2026
edd4a19
fix: tests
Eldolfin Apr 16, 2026
a7dc804
fix: clippy again...
Eldolfin Apr 16, 2026
1723dc2
fix: correct wasm32 client_side_stats construction to use StatsComput…
Eldolfin Apr 16, 2026
f758d03
fix: cargo fmt and clippy
Eldolfin Apr 16, 2026
0d3da7b
fix: remove temp default-feature
Eldolfin Apr 16, 2026
3808360
fix ci hopefully...
Eldolfin Apr 16, 2026
3e8388f
fix more ci and maybe the arcswap missuse ?
Eldolfin Apr 17, 2026
3feddef
fix: sql_obfuscation_mode = "" is the deprecated unspecified mode
Eldolfin Apr 17, 2026
fd1a97e
fix: random pascal case was wrong
Eldolfin Apr 20, 2026
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ license = "Apache-2.0"
authors = ["Datadog Inc. <info@datadoghq.com>"]

[workspace.dependencies]
arc-swap = "1.7.1"
hyper = { version = "1.6", features = [
"http1",
"client",
Expand Down
2 changes: 1 addition & 1 deletion libdd-common-ffi/src/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ mod tests {
let vec = vec![0, 2, 4, 6];
let ffi_vec: Vec<u8> = Vec::from(vec.clone());

for (a, b) in vec.iter().zip(ffi_vec.into_iter()) {
for (a, b) in vec.iter().zip(&ffi_vec) {
assert_eq!(a, b)
}
}
Expand Down
7 changes: 6 additions & 1 deletion libdd-data-pipeline/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ autobenches = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
arc-swap.workspace = true
anyhow = { version = "1.0" }
arc-swap = "1.7.1"
async-trait = "0.1"
http = "1"
http-body-util = "0.1"
Expand All @@ -36,6 +36,7 @@ libdd-telemetry = { version = "4.0.0", path = "../libdd-telemetry", default-feat
libdd-trace-protobuf = { version = "3.0.1", path = "../libdd-trace-protobuf" }
libdd-trace-stats = { version = "2.0.0", path = "../libdd-trace-stats" }
libdd-trace-utils = { version = "3.0.1", path = "../libdd-trace-utils", default-features = false }
libdd-trace-obfuscation = { version = "2.0.0", path = "../libdd-trace-obfuscation", optional = true }
libdd-ddsketch = { version = "1.0.1", path = "../libdd-ddsketch" }
libdd-dogstatsd-client = { version = "2.0.0", path = "../libdd-dogstatsd-client", default-features = false }
libdd-tinybytes = { version = "1.1.0", path = "../libdd-tinybytes", features = [
Expand Down Expand Up @@ -81,4 +82,8 @@ https = [
"libdd-trace-utils/https",
"libdd-dogstatsd-client/https",
]
stats-obfuscation = [
"libdd-trace-obfuscation",
"libdd-trace-stats/stats-obfuscation"
]
test-utils = []
10 changes: 5 additions & 5 deletions libdd-data-pipeline/src/agent_info/fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,12 +392,12 @@ mod single_threaded_tests {
},
"remove_stack_traces": false,
"redis": {
"Enabled": true,
"RemoveAllArgs": false
"enabled": true,
"remove_all_args": false
},
"memcached": {
"Enabled": true,
"KeepCommand": false
"enabled": true,
"keep_command": false
}
}
},
Expand All @@ -408,7 +408,7 @@ mod single_threaded_tests {
format!("{:x}", Sha256::digest(json.as_bytes()))
}

const TEST_INFO_HASH: &str = "b7709671827946c15603847bca76c90438579c038ec134eae19c51f1f3e3dfea";
const TEST_INFO_HASH: &str = "cce54bf6e7d1bf38088a3ec809bfeec160bc52d37f70bd6b581ce3c2f7be5a65";

#[cfg_attr(miri, ignore)]
#[tokio::test]
Expand Down
14 changes: 10 additions & 4 deletions libdd-data-pipeline/src/agent_info/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub struct AgentInfoStruct {
pub peer_tags: Option<Vec<String>>,
/// List of span kinds eligible for stats computation
pub span_kinds_stats_computed: Option<Vec<String>>,
/// Obfuscation version supported by the agent for client-side stats
pub obfuscation_version: Option<u32>,
/// Container tags hash from HTTP response header
pub container_tags_hash: Option<String>,
}
Expand All @@ -54,37 +56,41 @@ pub struct Config {
pub max_memory: Option<f64>,
pub max_cpu: Option<f64>,
pub analyzed_spans_by_service: Option<HashMap<String, HashMap<String, f64>>>,
pub obfuscation: Option<ObfuscationConfig>,
}

#[allow(missing_docs)]
#[derive(Clone, Deserialize, Default, Debug, PartialEq)]
#[derive(Clone, Serialize, Deserialize, Default, Debug, PartialEq)]
pub struct ObfuscationConfig {
pub elastic_search: bool,
pub mongo: bool,
pub sql_exec_plan: bool,
pub sql_exec_plan_normalize: bool,
#[cfg(feature = "stats-obfuscation")]
#[serde(default)]
pub sql_obfuscation_mode: libdd_trace_obfuscation::sql::SqlObfuscationMode,
pub http: HttpObfuscationConfig,
pub remove_stack_traces: bool,
pub redis: RedisObfuscationConfig,
pub memcached: MemcachedObfuscationConfig,
}

#[allow(missing_docs)]
#[derive(Clone, Deserialize, Default, Debug, PartialEq)]
#[derive(Clone, Serialize, Deserialize, Default, Debug, PartialEq)]
pub struct HttpObfuscationConfig {
pub remove_query_string: bool,
pub remove_path_digits: bool,
}

#[allow(missing_docs)]
#[derive(Clone, Deserialize, Default, Debug, PartialEq)]
#[derive(Clone, Serialize, Deserialize, Default, Debug, PartialEq)]
pub struct RedisObfuscationConfig {
pub enabled: bool,
pub remove_all_args: bool,
}

#[allow(missing_docs)]
#[derive(Clone, Deserialize, Default, Debug, PartialEq)]
#[derive(Clone, Serialize, Deserialize, Default, Debug, PartialEq)]
pub struct MemcachedObfuscationConfig {
pub enabled: bool,
pub keep_command: bool,
Expand Down
1 change: 1 addition & 0 deletions libdd-data-pipeline/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#![cfg_attr(not(test), deny(clippy::panic))]
#![cfg_attr(not(test), deny(clippy::unwrap_used))]
#![cfg_attr(not(test), deny(clippy::expect_used))]
#![cfg_attr(not(test), deny(clippy::unreachable))]
#![cfg_attr(not(test), deny(clippy::todo))]
#![cfg_attr(not(test), deny(clippy::unimplemented))]

Expand Down
68 changes: 66 additions & 2 deletions libdd-data-pipeline/src/stats_exporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use libdd_capabilities::{HttpClientTrait, MaybeSend};
use libdd_common::Endpoint;
use libdd_shared_runtime::Worker;
use libdd_trace_protobuf::pb;
#[cfg(feature = "stats-obfuscation")]
use libdd_trace_stats::span_concentrator::SharedStatsComputationObfuscationConfig;
use libdd_trace_stats::span_concentrator::SpanConcentrator;
use libdd_trace_utils::send_with_retry::{send_with_retry, RetryStrategy};
use tracing::error;
Expand All @@ -34,6 +36,8 @@ pub struct StatsExporter<H: HttpClientTrait> {
meta: TracerMetadata,
sequence_id: AtomicU64,
client: H,
#[cfg(feature = "stats-obfuscation")]
obfuscation_config: SharedStatsComputationObfuscationConfig,
}

impl<H: HttpClientTrait> StatsExporter<H> {
Expand All @@ -43,14 +47,14 @@ impl<H: HttpClientTrait> StatsExporter<H> {
/// - `concentrator` SpanConcentrator storing the stats to be sent to the agent
/// - `meta` metadata used in ClientStatsPayload and as headers to send stats to the agent
/// - `endpoint` the Endpoint used to send stats to the agent
/// - `cancellation_token` Token used to safely shutdown the exporter by force flushing the
/// concentrator
pub fn new(
flush_interval: time::Duration,
concentrator: Arc<Mutex<SpanConcentrator>>,
meta: TracerMetadata,
endpoint: Endpoint,
client: H,
#[cfg(feature = "stats-obfuscation")]
obfuscation_config: SharedStatsComputationObfuscationConfig,
) -> Self {
Self {
flush_interval,
Expand All @@ -59,6 +63,8 @@ impl<H: HttpClientTrait> StatsExporter<H> {
meta,
sequence_id: AtomicU64::new(0),
client,
Comment thread
Eldolfin marked this conversation as resolved.
#[cfg(feature = "stats-obfuscation")]
obfuscation_config,
}
}

Expand Down Expand Up @@ -91,6 +97,16 @@ impl<H: HttpClientTrait> StatsExporter<H> {
libdd_common::header::APPLICATION_MSGPACK,
);

#[cfg(feature = "stats-obfuscation")]
if self.obfuscation_config.load().enabled {
headers.insert(
http::HeaderName::from_static("datadog-obfuscation-version"),
http::HeaderValue::from_static(
crate::trace_exporter::stats::SUPPORTED_OBFUSCATION_VERSION_STR,
),
);
}

let result = send_with_retry(
&self.client,
&self.endpoint,
Expand Down Expand Up @@ -194,6 +210,8 @@ mod tests {
use httpmock::MockServer;
use libdd_capabilities_impl::NativeCapabilities;
use libdd_shared_runtime::SharedRuntime;
#[cfg(feature = "stats-obfuscation")]
use libdd_trace_stats::span_concentrator::StatsComputationObfuscationConfig;
use libdd_trace_utils::span::{trace_utils, v04::SpanSlice};
Comment thread
Eldolfin marked this conversation as resolved.
use libdd_trace_utils::test_utils::poll_for_mock_hit;
use time::Duration;
Expand Down Expand Up @@ -231,6 +249,8 @@ mod tests {
SystemTime::now() - BUCKETS_DURATION * 3,
vec![],
vec![],
#[cfg(feature = "stats-obfuscation")]
None,
);
let mut trace = vec![];

Expand Down Expand Up @@ -272,6 +292,8 @@ mod tests {
get_test_metadata(),
Endpoint::from_url(stats_url_from_agent_url(&server.url("/")).unwrap()),
NativeCapabilities::new_client(),
#[cfg(feature = "stats-obfuscation")]
StatsComputationObfuscationConfig::disabled(),
);

let send_status = stats_exporter.send(true).await;
Expand Down Expand Up @@ -299,6 +321,8 @@ mod tests {
get_test_metadata(),
Endpoint::from_url(stats_url_from_agent_url(&server.url("/")).unwrap()),
NativeCapabilities::new_client(),
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This StatsExporter::new call passes StatsComputationObfuscationConfig::disabled() without a #[cfg(feature = "stats-obfuscation")] guard. When the feature is off, StatsExporter::new won’t take this extra argument and the test won’t compile. Gate this argument (or cfg-gate the entire test) the same way as in test_send_stats.

Suggested change
NativeCapabilities::new_client(),
NativeCapabilities::new_client(),
#[cfg(feature = "stats-obfuscation")]

Copilot uses AI. Check for mistakes.
#[cfg(feature = "stats-obfuscation")]
StatsComputationObfuscationConfig::disabled(),
);

let send_status = stats_exporter.send(true).await;
Expand Down Expand Up @@ -333,6 +357,8 @@ mod tests {
get_test_metadata(),
Endpoint::from_url(stats_url_from_agent_url(&server.url("/")).unwrap()),
NativeCapabilities::new_client(),
#[cfg(feature = "stats-obfuscation")]
StatsComputationObfuscationConfig::disabled(),
);
Comment on lines 357 to 362
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test passes StatsComputationObfuscationConfig::disabled() without a #[cfg(feature = "stats-obfuscation")] guard. When stats-obfuscation is off, StatsExporter::new won’t accept this extra argument and the test won’t compile; gate the argument (or the entire test) on the feature.

Copilot uses AI. Check for mistakes.

let _handle = shared_runtime
Expand Down Expand Up @@ -374,6 +400,8 @@ mod tests {
get_test_metadata(),
Endpoint::from_url(stats_url_from_agent_url(&server.url("/")).unwrap()),
NativeCapabilities::new_client(),
#[cfg(feature = "stats-obfuscation")]
StatsComputationObfuscationConfig::disabled(),
);
Comment on lines 400 to 405
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test passes StatsComputationObfuscationConfig::disabled() without a #[cfg(feature = "stats-obfuscation")] guard. With the feature disabled, the call signature changes and this test won’t compile; wrap the argument in #[cfg(feature = "stats-obfuscation")] or cfg-gate the test.

Copilot uses AI. Check for mistakes.

let _handle = shared_runtime
Expand Down Expand Up @@ -413,4 +441,40 @@ mod tests {
"Non-empty env should be preserved"
);
}
#[cfg(feature = "stats-obfuscation")]
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn test_send_stats_with_obfuscation_header() {
use arc_swap::ArcSwap;

let server = MockServer::start_async().await;

let mock = server
.mock_async(|when, then| {
when.method(POST)
.header("Content-type", "application/msgpack")
.header("datadog-obfuscation-version", "1")
.path("/v0.6/stats")
.body_includes("libdatadog-test");
then.status(200).body("");
})
.await;

let stats_exporter = StatsExporter::new(
BUCKETS_DURATION,
Arc::new(Mutex::new(get_test_concentrator())),
get_test_metadata(),
Endpoint::from_url(stats_url_from_agent_url(&server.url("/")).unwrap()),
NativeCapabilities::new_client(),
Arc::new(ArcSwap::from_pointee(StatsComputationObfuscationConfig {
enabled: true,
..Default::default()
})),
);

let send_status = stats_exporter.send(true).await;
send_status.unwrap();

mock.assert_async().await;
}
}
26 changes: 24 additions & 2 deletions libdd-data-pipeline/src/trace_exporter/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ impl TraceExporterBuilder {

#[cfg(not(target_arch = "wasm32"))]
{
Comment thread
Eldolfin marked this conversation as resolved.
#[cfg(feature = "stats-obfuscation")]
use libdd_trace_stats::span_concentrator::StatsComputationObfuscationConfig;

use crate::trace_exporter::stats::StatsComputationConfig;

let info_endpoint = Endpoint::from_url(add_path(&agent_url, INFO_ENDPOINT));
let (info_fetcher, info_response_observer) =
AgentInfoFetcher::<H>::new(info_endpoint.clone(), Duration::from_secs(5 * 60));
Expand Down Expand Up @@ -378,7 +383,13 @@ impl TraceExporterBuilder {
shared_runtime,
dogstatsd,
common_stats_tags: vec![libdatadog_version],
client_side_stats: ArcSwap::new(stats.into()),
client_side_stats: StatsComputationConfig {
status: ArcSwap::new(stats.into()),
Comment thread
Eldolfin marked this conversation as resolved.
#[cfg(feature = "stats-obfuscation")]
obfuscation_config: Arc::new(ArcSwap::from_pointee(
StatsComputationObfuscationConfig::default(),
)),
Comment thread
Eldolfin marked this conversation as resolved.
},
previous_info_state: arc_swap::ArcSwapOption::new(None),
info_response_observer,
#[cfg(feature = "telemetry")]
Expand Down Expand Up @@ -427,6 +438,11 @@ impl TraceExporterBuilder {

#[cfg(target_arch = "wasm32")]
{
#[cfg(feature = "stats-obfuscation")]
use libdd_trace_stats::span_concentrator::StatsComputationObfuscationConfig;

use crate::trace_exporter::stats::StatsComputationConfig;

let info_endpoint = Endpoint::from_url(add_path(&agent_url, INFO_ENDPOINT));
let (_info_fetcher, info_response_observer) =
AgentInfoFetcher::<H>::new(info_endpoint, Duration::from_secs(5 * 60));
Expand Down Expand Up @@ -462,7 +478,13 @@ impl TraceExporterBuilder {
shared_runtime,
dogstatsd,
common_stats_tags: vec![libdatadog_version],
client_side_stats: ArcSwap::new(stats.into()),
client_side_stats: StatsComputationConfig {
status: ArcSwap::new(stats.into()),
#[cfg(feature = "stats-obfuscation")]
obfuscation_config: Arc::new(ArcSwap::from_pointee(
StatsComputationObfuscationConfig::default(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this only imported with the stats-obfuscation feature?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, StatsComputationObfuscationConfig is only needed with the stats-obfuscation feature otherwise it's not relevant. Now that I'm reading it again maybe StatsComputationObfuscationConfig is too similar to StatsComputationConfig which can make it confusing to read

)),
},
previous_info_state: arc_swap::ArcSwapOption::new(None),
info_response_observer,
health_metrics_enabled: self.health_metrics_enabled,
Expand Down
Loading
Loading