From f4839926effe900277323a77b637066b1622f413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Andr=C3=A9=20dos=20Santos=20Lopes?= Date: Thu, 7 May 2026 17:27:50 +0100 Subject: [PATCH] fix(telemetry): include dependencies and integrations in app-extended-heartbeat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the instrumentation-telemetry-api-docs schema, app-extended-heartbeat must carry the full state — configuration, dependencies, and integrations — so the agent can reconstruct application records on data loss. dd-trace-go and dd-trace-dotnet both ship the full triple. The Rust worker, however, defines AppStarted as a configuration-only struct and reuses it for app-extended-heartbeat, so dependencies and integrations are not included in the heartbeat payload. The ExtendedHeartbeat handler does call unflush_stored() on all three collectors, evidently with the intent of re-emitting the full state. Because the heartbeat payload omits the re-queued dependencies and integrations and app_started_sent_success only pops configurations from unflushed, the re-queued items remain in unflushed and are sent on the next FlushData as a duplicate app-integrations-change / app-dependencies-loaded. Updating the shared AppStarted struct, build_app_started, and app_started_sent_success addresses both Lifecycle(Start) and Lifecycle(ExtendedHeartbeat) call sites. Co-Authored-By: Claude Opus 4.7 (1M context) --- Cargo.lock | 2 +- libdd-crashtracker/Cargo.toml | 2 +- libdd-data-pipeline/Cargo.toml | 2 +- libdd-telemetry/Cargo.toml | 2 +- libdd-telemetry/examples/tm-ping.rs | 2 ++ libdd-telemetry/src/data/payload.rs | 12 ++++++++++-- libdd-telemetry/src/data/payloads.rs | 2 ++ libdd-telemetry/src/worker/mod.rs | 4 ++++ 8 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4771409ca..e35d8e0e7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3233,7 +3233,7 @@ dependencies = [ [[package]] name = "libdd-telemetry" -version = "4.0.0" +version = "5.0.0" dependencies = [ "anyhow", "async-trait", diff --git a/libdd-crashtracker/Cargo.toml b/libdd-crashtracker/Cargo.toml index 07395fd44a..f54e6a950e 100644 --- a/libdd-crashtracker/Cargo.toml +++ b/libdd-crashtracker/Cargo.toml @@ -50,7 +50,7 @@ chrono = {version = "0.4", default-features = false, features = ["std", "clock", cxx = { version = "1.0", optional = true } errno = "0.3" libdd-common = { version = "4.0.0", path = "../libdd-common" } -libdd-telemetry = { version = "4.0.0", path = "../libdd-telemetry" } +libdd-telemetry = { version = "5.0.0", path = "../libdd-telemetry" } http = "1.1" libc = "0.2" nix = { version = "0.29", features = ["poll", "signal", "socket"] } diff --git a/libdd-data-pipeline/Cargo.toml b/libdd-data-pipeline/Cargo.toml index 593dede475..05f1705e16 100644 --- a/libdd-data-pipeline/Cargo.toml +++ b/libdd-data-pipeline/Cargo.toml @@ -34,7 +34,7 @@ tokio-util = "0.7.11" libdd-capabilities = { path = "../libdd-capabilities", version = "1.0.0" } libdd-common = { version = "4.0.0", path = "../libdd-common", default-features = false } libdd-shared-runtime = { version = "0.1.0", path = "../libdd-shared-runtime" } -libdd-telemetry = { version = "4.0.0", path = "../libdd-telemetry", default-features = false, optional = true} +libdd-telemetry = { version = "5.0.0", path = "../libdd-telemetry", default-features = false, optional = true} libdd-trace-protobuf = { version = "3.0.1", path = "../libdd-trace-protobuf" } libdd-trace-stats = { version = "2.0.0", path = "../libdd-trace-stats", default-features = false } libdd-trace-utils = { version = "3.0.1", path = "../libdd-trace-utils", default-features = false } diff --git a/libdd-telemetry/Cargo.toml b/libdd-telemetry/Cargo.toml index d2dc58d8b9..f7f58afeb0 100644 --- a/libdd-telemetry/Cargo.toml +++ b/libdd-telemetry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libdd-telemetry" -version= "4.0.0" +version= "5.0.0" description = "Telemetry client allowing to send data as described in https://docs.datadoghq.com/tracing/configure_data_security/?tab=net#telemetry-collection" homepage = "https://github.com/DataDog/libdatadog/tree/main/libdd-telemetry" repository = "https://github.com/DataDog/libdatadog/tree/main/libdd-telemetry" diff --git a/libdd-telemetry/examples/tm-ping.rs b/libdd-telemetry/examples/tm-ping.rs index 0a0897490b..de0d7d6d26 100644 --- a/libdd-telemetry/examples/tm-ping.rs +++ b/libdd-telemetry/examples/tm-ping.rs @@ -17,6 +17,8 @@ use libdd_telemetry::{ fn build_app_started_payload() -> AppStarted { AppStarted { configuration: Vec::new(), + dependencies: Vec::new(), + integrations: Vec::new(), } } diff --git a/libdd-telemetry/src/data/payload.rs b/libdd-telemetry/src/data/payload.rs index e8503e5efb..609969ce38 100644 --- a/libdd-telemetry/src/data/payload.rs +++ b/libdd-telemetry/src/data/payload.rs @@ -66,6 +66,8 @@ mod tests { seq_id: None, }, ], + dependencies: Vec::new(), + integrations: Vec::new(), }); let serialized = serde_json::to_value(&payload).unwrap(); @@ -88,7 +90,9 @@ mod tests { "config_id": null, "seq_id": null } - ] + ], + "dependencies": [], + "integrations": [] } }); @@ -490,6 +494,8 @@ mod tests { config_id: None, seq_id: None, }], + dependencies: Vec::new(), + integrations: Vec::new(), }); let serialized = serde_json::to_value(&payload).unwrap(); @@ -505,7 +511,9 @@ mod tests { "config_id": null, "seq_id": null } - ] + ], + "dependencies": [], + "integrations": [] } }); diff --git a/libdd-telemetry/src/data/payloads.rs b/libdd-telemetry/src/data/payloads.rs index 4d67650255..a4e3d3aae5 100644 --- a/libdd-telemetry/src/data/payloads.rs +++ b/libdd-telemetry/src/data/payloads.rs @@ -48,6 +48,8 @@ pub enum ConfigurationOrigin { #[derive(Serialize, Debug)] pub struct AppStarted { pub configuration: Vec, + pub dependencies: Vec, + pub integrations: Vec, } #[derive(Serialize, Debug)] diff --git a/libdd-telemetry/src/worker/mod.rs b/libdd-telemetry/src/worker/mod.rs index dc696ced0f..aa916c0c00 100644 --- a/libdd-telemetry/src/worker/mod.rs +++ b/libdd-telemetry/src/worker/mod.rs @@ -673,6 +673,8 @@ impl TelemetryWorker { fn build_app_started(&mut self) -> data::AppStarted { data::AppStarted { configuration: self.data.configurations.unflushed().cloned().collect(), + dependencies: self.data.dependencies.unflushed().cloned().collect(), + integrations: self.data.integrations.unflushed().cloned().collect(), } } @@ -680,6 +682,8 @@ impl TelemetryWorker { self.data .configurations .removed_flushed(p.configuration.len()); + self.data.dependencies.removed_flushed(p.dependencies.len()); + self.data.integrations.removed_flushed(p.integrations.len()); } fn payload_sent_success(&mut self, payload: &data::Payload) {