From 8745975faf7de5f6d87bfee436c1778a6edbb422 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Thu, 12 Mar 2026 17:51:19 +0100 Subject: [PATCH 1/7] refactor(trace-utils): change header name type to accept dynamic values --- libdd-common/src/lib.rs | 6 +-- libdd-data-pipeline/src/stats_exporter.rs | 4 +- .../src/trace_exporter/agent_response.rs | 4 +- libdd-data-pipeline/src/trace_exporter/mod.rs | 12 ++--- .../src/trace_exporter/trace_serializer.rs | 47 +++++++++-------- libdd-trace-utils/src/send_data/mod.rs | 50 ++++++++++--------- libdd-trace-utils/src/send_with_retry/mod.rs | 13 +++-- libdd-trace-utils/src/tracer_header_tags.rs | 40 +++++++++------ 8 files changed, 98 insertions(+), 78 deletions(-) diff --git a/libdd-common/src/lib.rs b/libdd-common/src/lib.rs index 0d841543fe..f1346c9a5e 100644 --- a/libdd-common/src/lib.rs +++ b/libdd-common/src/lib.rs @@ -86,10 +86,6 @@ pub mod header { #![allow(clippy::declare_interior_mutable_const)] use hyper::{header::HeaderName, http::HeaderValue}; - // These strings are defined separately to be used in context where &str are used to represent - // headers (e.g. SendData) while keeping a single source of truth. - pub const DATADOG_SEND_REAL_HTTP_STATUS_STR: &str = "datadog-send-real-http-status"; - pub const DATADOG_TRACE_COUNT_STR: &str = "x-datadog-trace-count"; pub const APPLICATION_MSGPACK_STR: &str = "application/msgpack"; pub const APPLICATION_PROTOBUF_STR: &str = "application/x-protobuf"; @@ -101,7 +97,7 @@ pub mod header { /// If this is not set then the agent will always return a 200 regardless if the payload is /// dropped. pub const DATADOG_SEND_REAL_HTTP_STATUS: HeaderName = - HeaderName::from_static(DATADOG_SEND_REAL_HTTP_STATUS_STR); + HeaderName::from_static("datadog-send-real-http-status"); pub const DATADOG_API_KEY: HeaderName = HeaderName::from_static("dd-api-key"); pub const APPLICATION_JSON: HeaderValue = HeaderValue::from_static("application/json"); pub const APPLICATION_MSGPACK: HeaderValue = HeaderValue::from_static(APPLICATION_MSGPACK_STR); diff --git a/libdd-data-pipeline/src/stats_exporter.rs b/libdd-data-pipeline/src/stats_exporter.rs index 8e2768dbec..3a4dac7793 100644 --- a/libdd-data-pipeline/src/stats_exporter.rs +++ b/libdd-data-pipeline/src/stats_exporter.rs @@ -84,10 +84,10 @@ impl StatsExporter { } let body = rmp_serde::encode::to_vec_named(&payload)?; - let mut headers: HashMap<&'static str, String> = self.meta.borrow().into(); + let mut headers: HashMap = self.meta.borrow().into(); headers.insert( - http::header::CONTENT_TYPE.as_str(), + http::header::CONTENT_TYPE, libdd_common::header::APPLICATION_MSGPACK_STR.to_string(), ); diff --git a/libdd-data-pipeline/src/trace_exporter/agent_response.rs b/libdd-data-pipeline/src/trace_exporter/agent_response.rs index df3a3ae929..02b4ae3fa6 100644 --- a/libdd-data-pipeline/src/trace_exporter/agent_response.rs +++ b/libdd-data-pipeline/src/trace_exporter/agent_response.rs @@ -1,11 +1,13 @@ // Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 +use http::HeaderName; use std::sync::Arc; use arc_swap::ArcSwap; -pub const DATADOG_RATES_PAYLOAD_VERSION_HEADER: &str = "datadog-rates-payload-version"; +pub const DATADOG_RATES_PAYLOAD_VERSION: HeaderName = + HeaderName::from_static("datadog-rates-payload-version"); /// `AgentResponse` structure holds agent response information upon successful request. #[derive(Debug, PartialEq)] diff --git a/libdd-data-pipeline/src/trace_exporter/mod.rs b/libdd-data-pipeline/src/trace_exporter/mod.rs index 4169a89323..4d94b043dd 100644 --- a/libdd-data-pipeline/src/trace_exporter/mod.rs +++ b/libdd-data-pipeline/src/trace_exporter/mod.rs @@ -19,7 +19,7 @@ use crate::pausable_worker::PausableWorker; use crate::stats_exporter::StatsExporter; use crate::telemetry::{SendPayloadTelemetry, TelemetryClient}; use crate::trace_exporter::agent_response::{ - AgentResponsePayloadVersion, DATADOG_RATES_PAYLOAD_VERSION_HEADER, + AgentResponsePayloadVersion, DATADOG_RATES_PAYLOAD_VERSION, }; use crate::trace_exporter::error::{InternalErrorKind, RequestError, TraceExporterError}; use crate::{ @@ -148,8 +148,8 @@ impl<'a> From<&'a TracerMetadata> for TracerHeaderTags<'a> { } } -impl<'a> From<&'a TracerMetadata> for HashMap<&'static str, String> { - fn from(tags: &'a TracerMetadata) -> HashMap<&'static str, String> { +impl<'a> From<&'a TracerMetadata> for HashMap { + fn from(tags: &'a TracerMetadata) -> HashMap { TracerHeaderTags::from(tags).into() } } @@ -563,7 +563,7 @@ impl TraceExporter { &self, endpoint: &Endpoint, mp_payload: Vec, - headers: HashMap<&'static str, String>, + headers: HashMap, chunks: usize, chunks_dropped_p0: usize, ) -> Result { @@ -738,7 +738,7 @@ impl TraceExporter { match ( status.is_success(), self.agent_payload_response_version.as_ref(), - response.headers().get(DATADOG_RATES_PAYLOAD_VERSION_HEADER), + response.headers().get(DATADOG_RATES_PAYLOAD_VERSION), ) { (false, _, _) => { // If the status is not success, the rates are considered unchanged @@ -920,7 +920,7 @@ mod tests { ..Default::default() }; - let hashmap: HashMap<&'static str, String> = (&tracer_tags).into(); + let hashmap: HashMap = (&tracer_tags).into(); assert_eq!(hashmap.get("datadog-meta-tracer-version").unwrap(), "v0.1"); assert_eq!(hashmap.get("datadog-meta-lang").unwrap(), "rust"); diff --git a/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs b/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs index c8083fe6c2..16e5fd69f2 100644 --- a/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs +++ b/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use crate::trace_exporter::agent_response::{ - AgentResponsePayloadVersion, DATADOG_RATES_PAYLOAD_VERSION_HEADER, + AgentResponsePayloadVersion, DATADOG_RATES_PAYLOAD_VERSION, }; use crate::trace_exporter::error::TraceExporterError; use crate::trace_exporter::TraceExporterOutputFormat; -use http::header::CONTENT_TYPE; +use http::{header::CONTENT_TYPE, HeaderName}; use libdd_common::header::{ - APPLICATION_MSGPACK_STR, DATADOG_SEND_REAL_HTTP_STATUS_STR, DATADOG_TRACE_COUNT_STR, + APPLICATION_MSGPACK_STR, DATADOG_SEND_REAL_HTTP_STATUS, DATADOG_TRACE_COUNT, }; use libdd_trace_utils::msgpack_decoder::decode::error::DecodeError; use libdd_trace_utils::msgpack_encoder; @@ -22,7 +22,7 @@ pub(super) struct PreparedTracesPayload { /// Serialized msgpack payload pub data: Vec, /// HTTP headers for the request - pub headers: HashMap<&'static str, String>, + pub headers: HashMap, /// Number of trace chunks pub chunk_count: usize, } @@ -82,14 +82,14 @@ impl<'a> TraceSerializer<'a> { &self, header_tags: TracerHeaderTags, chunk_count: usize, - ) -> HashMap<&'static str, String> { - let mut headers: HashMap<&'static str, String> = header_tags.into(); - headers.insert(DATADOG_SEND_REAL_HTTP_STATUS_STR, "1".to_string()); - headers.insert(DATADOG_TRACE_COUNT_STR, chunk_count.to_string()); - headers.insert(CONTENT_TYPE.as_str(), APPLICATION_MSGPACK_STR.to_string()); + ) -> HashMap { + let mut headers: HashMap = header_tags.into(); + headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, "1".to_string()); + headers.insert(DATADOG_TRACE_COUNT, chunk_count.to_string()); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK_STR.to_string()); if let Some(agent_payload_response_version) = &self.agent_payload_response_version { headers.insert( - DATADOG_RATES_PAYLOAD_VERSION_HEADER, + DATADOG_RATES_PAYLOAD_VERSION, agent_payload_response_version.header_value(), ); } @@ -115,9 +115,7 @@ mod tests { use super::*; use crate::trace_exporter::agent_response::AgentResponsePayloadVersion; use http::header::CONTENT_TYPE; - use libdd_common::header::{ - APPLICATION_MSGPACK_STR, DATADOG_SEND_REAL_HTTP_STATUS_STR, DATADOG_TRACE_COUNT_STR, - }; + use libdd_common::header::APPLICATION_MSGPACK_STR; use libdd_tinybytes::BytesString; use libdd_trace_utils::span::v04::SpanBytes; use libdd_trace_utils::trace_utils::TracerHeaderTags; @@ -179,8 +177,11 @@ mod tests { let headers = serializer.build_traces_headers(header_tags, 3); // Check basic headers are present - assert_eq!(headers.get(DATADOG_SEND_REAL_HTTP_STATUS_STR).unwrap(), "1"); - assert_eq!(headers.get(DATADOG_TRACE_COUNT_STR).unwrap(), "3"); + assert_eq!( + headers.get(DATADOG_SEND_REAL_HTTP_STATUS.as_str()).unwrap(), + "1" + ); + assert_eq!(headers.get(DATADOG_TRACE_COUNT.as_str()).unwrap(), "3"); assert_eq!( headers.get(CONTENT_TYPE.as_str()).unwrap(), APPLICATION_MSGPACK_STR @@ -212,8 +213,8 @@ mod tests { let headers = serializer.build_traces_headers(header_tags, 2); // Check that agent payload version header is included - assert!(headers.contains_key(DATADOG_RATES_PAYLOAD_VERSION_HEADER)); - assert_eq!(headers.get(DATADOG_TRACE_COUNT_STR).unwrap(), "2"); + assert!(headers.contains_key(DATADOG_RATES_PAYLOAD_VERSION.as_str())); + assert_eq!(headers.get(DATADOG_TRACE_COUNT.as_str()).unwrap(), "2"); } #[test] @@ -346,7 +347,10 @@ mod tests { assert!(!prepared.headers.is_empty()); // Check headers - assert_eq!(prepared.headers.get(DATADOG_TRACE_COUNT_STR).unwrap(), "2"); + assert_eq!( + prepared.headers.get(DATADOG_TRACE_COUNT.as_str()).unwrap(), + "2" + ); assert_eq!(prepared.headers.get("datadog-meta-lang").unwrap(), "rust"); } @@ -379,7 +383,7 @@ mod tests { assert_eq!(prepared.chunk_count, 1); assert!(prepared .headers - .contains_key(DATADOG_RATES_PAYLOAD_VERSION_HEADER)); + .contains_key(DATADOG_RATES_PAYLOAD_VERSION.as_str())); } #[test] @@ -394,7 +398,10 @@ mod tests { let prepared = result.unwrap(); assert_eq!(prepared.chunk_count, 0); assert!(!prepared.data.is_empty()); // Even empty traces result in some serialized data - assert_eq!(prepared.headers.get(DATADOG_TRACE_COUNT_STR).unwrap(), "0"); + assert_eq!( + prepared.headers.get(DATADOG_TRACE_COUNT.as_str()).unwrap(), + "0" + ); } #[test] diff --git a/libdd-trace-utils/src/send_data/mod.rs b/libdd-trace-utils/src/send_data/mod.rs index 3115c67d8d..63604558ea 100644 --- a/libdd-trace-utils/src/send_data/mod.rs +++ b/libdd-trace-utils/src/send_data/mod.rs @@ -10,11 +10,11 @@ use crate::tracer_payload::TracerPayloadCollection; use anyhow::{anyhow, Context}; use futures::stream::FuturesUnordered; use futures::StreamExt; -use http::header::CONTENT_TYPE; +use http::{header::CONTENT_TYPE, HeaderName}; use libdd_common::{ header::{ - APPLICATION_MSGPACK_STR, APPLICATION_PROTOBUF_STR, DATADOG_SEND_REAL_HTTP_STATUS_STR, - DATADOG_TRACE_COUNT_STR, + APPLICATION_MSGPACK_STR, APPLICATION_PROTOBUF_STR, DATADOG_SEND_REAL_HTTP_STATUS, + DATADOG_TRACE_COUNT, }, Connect, Endpoint, GenericHttpClient, }; @@ -68,7 +68,7 @@ pub struct SendData { pub(crate) tracer_payloads: TracerPayloadCollection, pub(crate) size: usize, // have a rough size estimate to force flushing if it's large target: Endpoint, - headers: HashMap<&'static str, String>, + headers: HashMap, retry_strategy: RetryStrategy, #[cfg(feature = "compression")] compression: Compression, @@ -85,7 +85,7 @@ pub struct SendDataBuilder { pub(crate) tracer_payloads: TracerPayloadCollection, pub(crate) size: usize, target: Endpoint, - headers: HashMap<&'static str, String>, + headers: HashMap, retry_strategy: RetryStrategy, #[cfg(feature = "compression")] compression: Compression, @@ -98,8 +98,8 @@ impl SendDataBuilder { tracer_header_tags: TracerHeaderTags, target: &Endpoint, ) -> SendDataBuilder { - let mut headers: HashMap<&'static str, String> = tracer_header_tags.into(); - headers.insert(DATADOG_SEND_REAL_HTTP_STATUS_STR, "1".to_string()); + let mut headers: HashMap = tracer_header_tags.into(); + headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, "1".to_string()); SendDataBuilder { tracer_payloads: tracer_payload, size, @@ -160,8 +160,8 @@ impl SendData { tracer_header_tags: TracerHeaderTags, target: &Endpoint, ) -> SendData { - let mut headers: HashMap<&'static str, String> = tracer_header_tags.into(); - headers.insert(DATADOG_SEND_REAL_HTTP_STATUS_STR, "1".to_string()); + let mut headers: HashMap = tracer_header_tags.into(); + headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, "1".to_string()); SendData { tracer_payloads: tracer_payload, size, @@ -243,7 +243,7 @@ impl SendData { &self, chunks: u64, payload: Vec, - headers: HashMap<&'static str, String>, + headers: HashMap, http_client: &GenericHttpClient, endpoint: Option<&Endpoint>, ) -> (SendWithRetryResult, u64, u64) { @@ -268,7 +268,11 @@ impl SendData { } #[cfg(feature = "compression")] - fn compress_payload(&self, payload: Vec, headers: &mut HashMap<&str, String>) -> Vec { + fn compress_payload( + &self, + payload: Vec, + headers: &mut HashMap, + ) -> Vec { match self.compression { Compression::Zstd(level) => { let result = (|| -> std::io::Result> { @@ -279,7 +283,7 @@ impl SendData { match result { Ok(compressed_payload) => { - headers.insert("Content-Encoding", "zstd".to_string()); + headers.insert(http::header::CONTENT_ENCODING, "zstd".to_string()); compressed_payload } Err(_) => payload, @@ -317,7 +321,7 @@ impl SendData { #[cfg(not(feature = "compression"))] let final_payload = serialized_trace_payload; - request_headers.insert(CONTENT_TYPE.as_str(), APPLICATION_PROTOBUF_STR.to_string()); + request_headers.insert(CONTENT_TYPE, APPLICATION_PROTOBUF_STR.to_string()); let (response, bytes_sent, chunks) = self .send_payload( @@ -351,8 +355,8 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(tracer_payload.chunks.len()).unwrap(); let mut headers = self.headers.clone(); - headers.insert(DATADOG_TRACE_COUNT_STR, chunks.to_string()); - headers.insert(CONTENT_TYPE.as_str(), APPLICATION_MSGPACK_STR.to_string()); + headers.insert(DATADOG_TRACE_COUNT, chunks.to_string()); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK_STR.to_string()); let payload = match rmp_serde::to_vec_named(tracer_payload) { Ok(p) => p, @@ -372,8 +376,8 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(self.tracer_payloads.size()).unwrap(); let mut headers = self.headers.clone(); - headers.insert(DATADOG_TRACE_COUNT_STR, chunks.to_string()); - headers.insert(CONTENT_TYPE.as_str(), APPLICATION_MSGPACK_STR.to_string()); + headers.insert(DATADOG_TRACE_COUNT, chunks.to_string()); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK_STR.to_string()); let payload = msgpack_encoder::v04::to_vec(payload); @@ -389,8 +393,8 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(self.tracer_payloads.size()).unwrap(); let mut headers = self.headers.clone(); - headers.insert(DATADOG_TRACE_COUNT_STR, chunks.to_string()); - headers.insert(CONTENT_TYPE.as_str(), APPLICATION_MSGPACK_STR.to_string()); + headers.insert(DATADOG_TRACE_COUNT, chunks.to_string()); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK_STR.to_string()); let payload = match rmp_serde::to_vec(payload) { Ok(p) => p, @@ -574,7 +578,7 @@ mod tests { assert_eq!(data.target.url.path(), "/foo/bar"); for (key, value) in HashMap::from(header_tags) { - assert_eq!(data.headers.get(key).unwrap(), &value); + assert_eq!(data.headers.get(&key).unwrap(), &value); } } @@ -679,7 +683,7 @@ mod tests { let mock = server .mock_async(|when, then| { when.method(POST) - .header(DATADOG_TRACE_COUNT_STR, "1") + .header(DATADOG_TRACE_COUNT.as_str(), "1") .header("Content-type", "application/msgpack") .header("datadog-meta-lang", header_tags.lang) .header( @@ -739,7 +743,7 @@ mod tests { let mock = server .mock_async(|when, then| { when.method(POST) - .header(DATADOG_TRACE_COUNT_STR, "1") + .header(DATADOG_TRACE_COUNT.as_str(), "1") .header("Content-type", "application/msgpack") .header("datadog-meta-lang", header_tags.lang) .header( @@ -928,7 +932,7 @@ mod tests { let mock = server .mock_async(|when, then| { when.method(POST) - .header(DATADOG_TRACE_COUNT_STR, "2") + .header(DATADOG_TRACE_COUNT.as_str(), "2") .header("Content-type", "application/msgpack") .header("datadog-meta-lang", header_tags.lang) .header( diff --git a/libdd-trace-utils/src/send_with_retry/mod.rs b/libdd-trace-utils/src/send_with_retry/mod.rs index bb9c3ae662..dea20d528a 100644 --- a/libdd-trace-utils/src/send_with_retry/mod.rs +++ b/libdd-trace-utils/src/send_with_retry/mod.rs @@ -8,9 +8,9 @@ mod retry_strategy; pub use retry_strategy::{RetryBackoffType, RetryStrategy}; use bytes::Bytes; -use http::Method; +use http::{HeaderName, Method}; use libdd_common::{http_common, Connect, Endpoint, GenericHttpClient, HttpRequestBuilder}; -use std::{collections::HashMap, time::Duration}; +use std::{collections::HashMap, ops::Deref, time::Duration}; use tracing::{debug, error}; pub type Attempts = u32; @@ -104,7 +104,10 @@ impl std::error::Error for RequestError {} /// url: "localhost:8126/v04/traces".parse::().unwrap(), /// ..Endpoint::default() /// }; -/// let headers = HashMap::from([("Content-type", "application/msgpack".to_string())]); +/// let headers = HashMap::from([( +/// http::HeaderName::from_static("content-type"), +/// "application/msgpack".to_string(), +/// )]); /// let retry_strategy = RetryStrategy::new(3, 10, RetryBackoffType::Exponential, Some(5)); /// let client = new_default_client(); /// send_with_retry(&client, &target, payload, &headers, &retry_strategy).await @@ -114,7 +117,7 @@ pub async fn send_with_retry( client: &GenericHttpClient, target: &Endpoint, payload: Vec, - headers: &HashMap<&'static str, String>, + headers: &HashMap, retry_strategy: &RetryStrategy, ) -> SendWithRetryResult { let mut request_attempt = 0; @@ -142,7 +145,7 @@ pub async fn send_with_retry( .or(Err(SendWithRetryError::Build(request_attempt)))? .method(Method::POST); for (key, value) in headers { - req = req.header(*key, value.clone()); + req = req.header(key.clone(), value.deref()); } match send_request( diff --git a/libdd-trace-utils/src/tracer_header_tags.rs b/libdd-trace-utils/src/tracer_header_tags.rs index cae7fc53d1..a124eb9011 100644 --- a/libdd-trace-utils/src/tracer_header_tags.rs +++ b/libdd-trace-utils/src/tracer_header_tags.rs @@ -1,8 +1,7 @@ // Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 -use http::HeaderMap; -use http::HeaderValue; +use http::{HeaderMap, HeaderName, HeaderValue}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -41,26 +40,35 @@ pub struct TracerHeaderTags<'a> { pub dropped_p0_spans: usize, } -impl<'a> From> for HashMap<&'static str, String> { - fn from(tags: TracerHeaderTags<'a>) -> HashMap<&'static str, String> { +impl<'a> From> for HashMap { + fn from(tags: TracerHeaderTags<'a>) -> HashMap { let mut headers = HashMap::from([ - ("datadog-meta-lang", tags.lang.to_string()), - ("datadog-meta-lang-version", tags.lang_version.to_string()), ( - "datadog-meta-lang-interpreter", + HeaderName::from_static("datadog-meta-lang"), + tags.lang.to_string(), + ), + ( + HeaderName::from_static("datadog-meta-lang-version"), + tags.lang_version.to_string(), + ), + ( + HeaderName::from_static("datadog-meta-lang-interpreter"), tags.lang_interpreter.to_string(), ), ( - "datadog-meta-lang-interpreter-vendor", + HeaderName::from_static("datadog-meta-lang-interpreter-vendor"), tags.lang_vendor.to_string(), ), ( - "datadog-meta-tracer-version", + HeaderName::from_static("datadog-meta-tracer-version"), tags.tracer_version.to_string(), ), - ("datadog-container-id", tags.container_id.to_string()), ( - "datadog-client-computed-stats", + HeaderName::from_static("datadog-container-id"), + tags.container_id.to_string(), + ), + ( + HeaderName::from_static("datadog-client-computed-stats"), if tags.client_computed_stats { "true".to_string() } else { @@ -68,7 +76,7 @@ impl<'a> From> for HashMap<&'static str, String> { }, ), ( - "datadog-client-computed-top-level", + HeaderName::from_static("datadog-client-computed-top-level"), if tags.client_computed_top_level { "true".to_string() } else { @@ -76,7 +84,7 @@ impl<'a> From> for HashMap<&'static str, String> { }, ), ( - "datadog-client-dropped-p0-traces", + HeaderName::from_static("datadog-client-dropped-p0-traces"), if tags.dropped_p0_traces > 0 { tags.dropped_p0_traces.to_string() } else { @@ -84,7 +92,7 @@ impl<'a> From> for HashMap<&'static str, String> { }, ), ( - "datadog-client-dropped-p0-spans", + HeaderName::from_static("datadog-client-dropped-p0-spans"), if tags.dropped_p0_spans > 0 { tags.dropped_p0_spans.to_string() } else { @@ -151,7 +159,7 @@ mod tests { dropped_p0_spans: 120, }; - let map: HashMap<&'static str, String> = header_tags.into(); + let map: HashMap = header_tags.into(); assert_eq!(map.len(), 10); assert_eq!(map.get("datadog-meta-lang").unwrap(), "test-lang"); @@ -189,7 +197,7 @@ mod tests { dropped_p0_traces: 0, }; - let map: HashMap<&'static str, String> = header_tags.into(); + let map: HashMap = header_tags.into(); assert_eq!(map.len(), 5); assert_eq!(map.get("datadog-meta-lang").unwrap(), "test-lang"); From 62d38625cb358f65e025a74e2c0cb5f960f28d01 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Thu, 12 Mar 2026 21:08:25 +0100 Subject: [PATCH 2/7] fix: tests --- libdd-trace-utils/src/tracer_header_tags.rs | 64 ++++++++++----------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/libdd-trace-utils/src/tracer_header_tags.rs b/libdd-trace-utils/src/tracer_header_tags.rs index a124eb9011..4f89e1e612 100644 --- a/libdd-trace-utils/src/tracer_header_tags.rs +++ b/libdd-trace-utils/src/tracer_header_tags.rs @@ -141,9 +141,17 @@ impl<'a> From<&'a HeaderMap> for TracerHeaderTags<'a> { #[cfg(test)] mod tests { + use std::str::FromStr; + use super::*; use hyper::HeaderMap; + fn get<'a>(m: &'a HashMap, key: & str) -> Option<&'a str> { + m.get(&HeaderName::from_str(key).unwrap()).map(|v| v.as_str()) + } + + + #[test] fn tags_to_hashmap() { let header_tags = TracerHeaderTags { @@ -162,26 +170,18 @@ mod tests { let map: HashMap = header_tags.into(); assert_eq!(map.len(), 10); - assert_eq!(map.get("datadog-meta-lang").unwrap(), "test-lang"); - assert_eq!(map.get("datadog-meta-lang-version").unwrap(), "2.0"); - assert_eq!( - map.get("datadog-meta-lang-interpreter").unwrap(), - "interpreter" - ); - assert_eq!( - map.get("datadog-meta-lang-interpreter-vendor").unwrap(), - "vendor" - ); - assert_eq!(map.get("datadog-meta-tracer-version").unwrap(), "1.0"); - assert_eq!(map.get("datadog-container-id").unwrap(), "id"); - assert_eq!( - map.get("datadog-client-computed-top-level").unwrap(), - "true" - ); - assert_eq!(map.get("datadog-client-computed-stats").unwrap(), "true"); - assert_eq!(map.get("datadog-client-dropped-p0-traces").unwrap(), "12"); - assert_eq!(map.get("datadog-client-dropped-p0-spans").unwrap(), "120"); + assert_eq!(get(&map, "datadog-meta-lang"), Some("test-lang")); + assert_eq!(get(&map, "datadog-meta-lang-version"), Some("2.0")); + assert_eq!(get(&map, "datadog-meta-lang-interpreter"), Some("interpreter")); + assert_eq!(get(&map, "datadog-meta-lang-interpreter-vendor"), Some("vendor")); + assert_eq!(get(&map, "datadog-meta-tracer-version"), Some("1.0")); + assert_eq!(get(&map, "datadog-container-id"), Some("id")); + assert_eq!(get(&map, "datadog-client-computed-top-level"), Some("true")); + assert_eq!(get(&map, "datadog-client-computed-stats"), Some("true")); + assert_eq!(get(&map, "datadog-client-dropped-p0-traces"), Some("12")); + assert_eq!(get(&map, "datadog-client-dropped-p0-spans"), Some("120")); } + #[test] fn tags_to_hashmap_empty_value() { let header_tags = TracerHeaderTags { @@ -200,22 +200,16 @@ mod tests { let map: HashMap = header_tags.into(); assert_eq!(map.len(), 5); - assert_eq!(map.get("datadog-meta-lang").unwrap(), "test-lang"); - assert_eq!(map.get("datadog-meta-lang-version").unwrap(), "2.0"); - assert_eq!( - map.get("datadog-meta-lang-interpreter").unwrap(), - "interpreter" - ); - assert_eq!( - map.get("datadog-meta-lang-interpreter-vendor").unwrap(), - "vendor" - ); - assert_eq!(map.get("datadog-meta-tracer-version").unwrap(), "1.0"); - assert_eq!(map.get("datadog-container-id"), None); - assert_eq!(map.get("datadog-client-computed-top-level"), None); - assert_eq!(map.get("datadog-client-computed-stats"), None); - assert_eq!(map.get("datadog-client-dropped-p0-traces"), None); - assert_eq!(map.get("datadog-client-dropped-p0-spans"), None); + assert_eq!(get(&map, "datadog-meta-lang"), Some("test-lang")); + assert_eq!(get(&map, "datadog-meta-lang-version"), Some("2.0")); + assert_eq!(get(&map, "datadog-meta-lang-interpreter"), Some("interpreter")); + assert_eq!(get(&map, "datadog-meta-lang-interpreter-vendor"), Some("vendor")); + assert_eq!(get(&map, "datadog-meta-tracer-version"), Some("1.0")); + assert_eq!(get(&map, "datadog-container-id"), None); + assert_eq!(get(&map, "datadog-client-computed-top-level"), None); + assert_eq!(get(&map, "datadog-client-computed-stats"), None); + assert_eq!(get(&map, "datadog-client-dropped-p0-traces"), None); + assert_eq!(get(&map, "datadog-client-dropped-p0-spans"), None); } #[test] From ba5b268dd13e760b3a3bea6db07113c1626fe5b7 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Fri, 13 Mar 2026 13:32:49 +0100 Subject: [PATCH 3/7] fix: use test get as headername don't hash like strings --- libdd-trace-utils/src/tracer_header_tags.rs | 27 +++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/libdd-trace-utils/src/tracer_header_tags.rs b/libdd-trace-utils/src/tracer_header_tags.rs index 4f89e1e612..2e6595a306 100644 --- a/libdd-trace-utils/src/tracer_header_tags.rs +++ b/libdd-trace-utils/src/tracer_header_tags.rs @@ -146,12 +146,11 @@ mod tests { use super::*; use hyper::HeaderMap; - fn get<'a>(m: &'a HashMap, key: & str) -> Option<&'a str> { - m.get(&HeaderName::from_str(key).unwrap()).map(|v| v.as_str()) + fn get<'a>(m: &'a HashMap, key: &str) -> Option<&'a str> { + m.get(&HeaderName::from_str(key).unwrap()) + .map(|v| v.as_str()) } - - #[test] fn tags_to_hashmap() { let header_tags = TracerHeaderTags { @@ -172,8 +171,14 @@ mod tests { assert_eq!(map.len(), 10); assert_eq!(get(&map, "datadog-meta-lang"), Some("test-lang")); assert_eq!(get(&map, "datadog-meta-lang-version"), Some("2.0")); - assert_eq!(get(&map, "datadog-meta-lang-interpreter"), Some("interpreter")); - assert_eq!(get(&map, "datadog-meta-lang-interpreter-vendor"), Some("vendor")); + assert_eq!( + get(&map, "datadog-meta-lang-interpreter"), + Some("interpreter") + ); + assert_eq!( + get(&map, "datadog-meta-lang-interpreter-vendor"), + Some("vendor") + ); assert_eq!(get(&map, "datadog-meta-tracer-version"), Some("1.0")); assert_eq!(get(&map, "datadog-container-id"), Some("id")); assert_eq!(get(&map, "datadog-client-computed-top-level"), Some("true")); @@ -202,8 +207,14 @@ mod tests { assert_eq!(map.len(), 5); assert_eq!(get(&map, "datadog-meta-lang"), Some("test-lang")); assert_eq!(get(&map, "datadog-meta-lang-version"), Some("2.0")); - assert_eq!(get(&map, "datadog-meta-lang-interpreter"), Some("interpreter")); - assert_eq!(get(&map, "datadog-meta-lang-interpreter-vendor"), Some("vendor")); + assert_eq!( + get(&map, "datadog-meta-lang-interpreter"), + Some("interpreter") + ); + assert_eq!( + get(&map, "datadog-meta-lang-interpreter-vendor"), + Some("vendor") + ); assert_eq!(get(&map, "datadog-meta-tracer-version"), Some("1.0")); assert_eq!(get(&map, "datadog-container-id"), None); assert_eq!(get(&map, "datadog-client-computed-top-level"), None); From 506f273cd69bbd304d2a7a1877febf6bdaf8dc36 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Tue, 17 Mar 2026 16:39:10 +0100 Subject: [PATCH 4/7] fix: use http::HeaderMap --- libdd-data-pipeline/src/stats_exporter.rs | 5 +- libdd-data-pipeline/src/trace_exporter/mod.rs | 11 +- .../src/trace_exporter/trace_serializer.rs | 30 ++-- libdd-trace-utils/src/send_data/mod.rs | 59 +++++--- libdd-trace-utils/src/send_with_retry/mod.rs | 24 +-- libdd-trace-utils/src/tracer_header_tags.rs | 139 +++++++++--------- 6 files changed, 140 insertions(+), 128 deletions(-) diff --git a/libdd-data-pipeline/src/stats_exporter.rs b/libdd-data-pipeline/src/stats_exporter.rs index 3a4dac7793..b1b235c995 100644 --- a/libdd-data-pipeline/src/stats_exporter.rs +++ b/libdd-data-pipeline/src/stats_exporter.rs @@ -3,7 +3,6 @@ use std::{ borrow::Borrow, - collections::HashMap, sync::{ atomic::{AtomicU64, Ordering}, Arc, Mutex, @@ -84,11 +83,11 @@ impl StatsExporter { } let body = rmp_serde::encode::to_vec_named(&payload)?; - let mut headers: HashMap = self.meta.borrow().into(); + let mut headers: http::HeaderMap = self.meta.borrow().into(); headers.insert( http::header::CONTENT_TYPE, - libdd_common::header::APPLICATION_MSGPACK_STR.to_string(), + libdd_common::header::APPLICATION_MSGPACK, ); let result = send_with_retry( diff --git a/libdd-data-pipeline/src/trace_exporter/mod.rs b/libdd-data-pipeline/src/trace_exporter/mod.rs index 4d94b043dd..42bd17dcb8 100644 --- a/libdd-data-pipeline/src/trace_exporter/mod.rs +++ b/libdd-data-pipeline/src/trace_exporter/mod.rs @@ -45,7 +45,7 @@ use libdd_trace_utils::trace_utils::TracerHeaderTags; use std::io; use std::sync::{Arc, Mutex}; use std::time::Duration; -use std::{borrow::Borrow, collections::HashMap, str::FromStr}; +use std::{borrow::Borrow, str::FromStr}; use tokio::runtime::Runtime; use tracing::{debug, error, warn}; @@ -148,8 +148,8 @@ impl<'a> From<&'a TracerMetadata> for TracerHeaderTags<'a> { } } -impl<'a> From<&'a TracerMetadata> for HashMap { - fn from(tags: &'a TracerMetadata) -> HashMap { +impl<'a> From<&'a TracerMetadata> for http::HeaderMap { + fn from(tags: &'a TracerMetadata) -> http::HeaderMap { TracerHeaderTags::from(tags).into() } } @@ -563,7 +563,7 @@ impl TraceExporter { &self, endpoint: &Endpoint, mp_payload: Vec, - headers: HashMap, + headers: http::HeaderMap, chunks: usize, chunks_dropped_p0: usize, ) -> Result { @@ -876,7 +876,6 @@ mod tests { use libdd_trace_utils::msgpack_encoder; use libdd_trace_utils::span::v04::SpanBytes; use libdd_trace_utils::span::v05; - use std::collections::HashMap; use std::net; use std::time::Duration; use tokio::time::sleep; @@ -920,7 +919,7 @@ mod tests { ..Default::default() }; - let hashmap: HashMap = (&tracer_tags).into(); + let hashmap: http::HeaderMap = (&tracer_tags).into(); assert_eq!(hashmap.get("datadog-meta-tracer-version").unwrap(), "v0.1"); assert_eq!(hashmap.get("datadog-meta-lang").unwrap(), "rust"); diff --git a/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs b/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs index 16e5fd69f2..e8ab34befa 100644 --- a/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs +++ b/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs @@ -6,23 +6,22 @@ use crate::trace_exporter::agent_response::{ }; use crate::trace_exporter::error::TraceExporterError; use crate::trace_exporter::TraceExporterOutputFormat; -use http::{header::CONTENT_TYPE, HeaderName}; +use http::{header::CONTENT_TYPE, HeaderMap, HeaderValue}; use libdd_common::header::{ - APPLICATION_MSGPACK_STR, DATADOG_SEND_REAL_HTTP_STATUS, DATADOG_TRACE_COUNT, + APPLICATION_MSGPACK, DATADOG_SEND_REAL_HTTP_STATUS, DATADOG_TRACE_COUNT, }; use libdd_trace_utils::msgpack_decoder::decode::error::DecodeError; use libdd_trace_utils::msgpack_encoder; use libdd_trace_utils::span::{v04::Span, TraceData}; use libdd_trace_utils::trace_utils::{self, TracerHeaderTags}; use libdd_trace_utils::tracer_payload; -use std::collections::HashMap; /// Prepared traces payload ready for sending to the agent pub(super) struct PreparedTracesPayload { /// Serialized msgpack payload pub data: Vec, /// HTTP headers for the request - pub headers: HashMap, + pub headers: HeaderMap, /// Number of trace chunks pub chunk_count: usize, } @@ -78,20 +77,17 @@ impl<'a> TraceSerializer<'a> { } /// Build HTTP headers for traces request - fn build_traces_headers( - &self, - header_tags: TracerHeaderTags, - chunk_count: usize, - ) -> HashMap { - let mut headers: HashMap = header_tags.into(); - headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, "1".to_string()); - headers.insert(DATADOG_TRACE_COUNT, chunk_count.to_string()); - headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK_STR.to_string()); + fn build_traces_headers(&self, header_tags: TracerHeaderTags, chunk_count: usize) -> HeaderMap { + let mut headers: HeaderMap = header_tags.into(); + headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, HeaderValue::from_static("1")); + // should always be true, as the name should only contain visible ascii chars + let _ = HeaderValue::try_from(chunk_count.to_string()) + .map(|v| headers.insert(DATADOG_TRACE_COUNT, v)); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); if let Some(agent_payload_response_version) = &self.agent_payload_response_version { - headers.insert( - DATADOG_RATES_PAYLOAD_VERSION, - agent_payload_response_version.header_value(), - ); + // should always be true, as the version should only contain visible ascii chars + let _ = HeaderValue::try_from(agent_payload_response_version.header_value()) + .map(|v| headers.insert(DATADOG_RATES_PAYLOAD_VERSION, v)); } headers } diff --git a/libdd-trace-utils/src/send_data/mod.rs b/libdd-trace-utils/src/send_data/mod.rs index 63604558ea..1559611008 100644 --- a/libdd-trace-utils/src/send_data/mod.rs +++ b/libdd-trace-utils/src/send_data/mod.rs @@ -10,10 +10,10 @@ use crate::tracer_payload::TracerPayloadCollection; use anyhow::{anyhow, Context}; use futures::stream::FuturesUnordered; use futures::StreamExt; -use http::{header::CONTENT_TYPE, HeaderName}; +use http::{header::CONTENT_TYPE, HeaderMap, HeaderValue}; use libdd_common::{ header::{ - APPLICATION_MSGPACK_STR, APPLICATION_PROTOBUF_STR, DATADOG_SEND_REAL_HTTP_STATUS, + APPLICATION_MSGPACK, APPLICATION_PROTOBUF, DATADOG_SEND_REAL_HTTP_STATUS, DATADOG_TRACE_COUNT, }, Connect, Endpoint, GenericHttpClient, @@ -68,7 +68,7 @@ pub struct SendData { pub(crate) tracer_payloads: TracerPayloadCollection, pub(crate) size: usize, // have a rough size estimate to force flushing if it's large target: Endpoint, - headers: HashMap, + headers: HeaderMap, retry_strategy: RetryStrategy, #[cfg(feature = "compression")] compression: Compression, @@ -85,7 +85,7 @@ pub struct SendDataBuilder { pub(crate) tracer_payloads: TracerPayloadCollection, pub(crate) size: usize, target: Endpoint, - headers: HashMap, + headers: HeaderMap, retry_strategy: RetryStrategy, #[cfg(feature = "compression")] compression: Compression, @@ -98,8 +98,8 @@ impl SendDataBuilder { tracer_header_tags: TracerHeaderTags, target: &Endpoint, ) -> SendDataBuilder { - let mut headers: HashMap = tracer_header_tags.into(); - headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, "1".to_string()); + let mut headers: HeaderMap = tracer_header_tags.into(); + headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, HeaderValue::from_static("1")); SendDataBuilder { tracer_payloads: tracer_payload, size, @@ -160,8 +160,8 @@ impl SendData { tracer_header_tags: TracerHeaderTags, target: &Endpoint, ) -> SendData { - let mut headers: HashMap = tracer_header_tags.into(); - headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, "1".to_string()); + let mut headers: HeaderMap = tracer_header_tags.into(); + headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, HeaderValue::from_static("1")); SendData { tracer_payloads: tracer_payload, size, @@ -243,7 +243,7 @@ impl SendData { &self, chunks: u64, payload: Vec, - headers: HashMap, + headers: HeaderMap, http_client: &GenericHttpClient, endpoint: Option<&Endpoint>, ) -> (SendWithRetryResult, u64, u64) { @@ -268,11 +268,7 @@ impl SendData { } #[cfg(feature = "compression")] - fn compress_payload( - &self, - payload: Vec, - headers: &mut HashMap, - ) -> Vec { + fn compress_payload(&self, payload: Vec, headers: &mut HeaderMap) -> Vec { match self.compression { Compression::Zstd(level) => { let result = (|| -> std::io::Result> { @@ -283,7 +279,10 @@ impl SendData { match result { Ok(compressed_payload) => { - headers.insert(http::header::CONTENT_ENCODING, "zstd".to_string()); + headers.insert( + http::header::CONTENT_ENCODING, + HeaderValue::from_static("zstd"), + ); compressed_payload } Err(_) => payload, @@ -321,7 +320,7 @@ impl SendData { #[cfg(not(feature = "compression"))] let final_payload = serialized_trace_payload; - request_headers.insert(CONTENT_TYPE, APPLICATION_PROTOBUF_STR.to_string()); + request_headers.insert(CONTENT_TYPE, APPLICATION_PROTOBUF); let (response, bytes_sent, chunks) = self .send_payload( @@ -355,8 +354,12 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(tracer_payload.chunks.len()).unwrap(); let mut headers = self.headers.clone(); - headers.insert(DATADOG_TRACE_COUNT, chunks.to_string()); - headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK_STR.to_string()); + #[allow(clippy::unwrap_used)] + headers.insert( + DATADOG_TRACE_COUNT, + HeaderValue::try_from(chunks.to_string()).unwrap(), + ); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); let payload = match rmp_serde::to_vec_named(tracer_payload) { Ok(p) => p, @@ -376,8 +379,12 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(self.tracer_payloads.size()).unwrap(); let mut headers = self.headers.clone(); - headers.insert(DATADOG_TRACE_COUNT, chunks.to_string()); - headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK_STR.to_string()); + #[allow(clippy::unwrap_used)] + headers.insert( + DATADOG_TRACE_COUNT, + HeaderValue::try_from(chunks.to_string()).unwrap(), + ); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); let payload = msgpack_encoder::v04::to_vec(payload); @@ -393,8 +400,12 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(self.tracer_payloads.size()).unwrap(); let mut headers = self.headers.clone(); - headers.insert(DATADOG_TRACE_COUNT, chunks.to_string()); - headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK_STR.to_string()); + #[allow(clippy::unwrap_used)] + headers.insert( + DATADOG_TRACE_COUNT, + HeaderValue::try_from(chunks.to_string()).unwrap(), + ); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); let payload = match rmp_serde::to_vec(payload) { Ok(p) => p, @@ -577,8 +588,8 @@ mod tests { assert_eq!(data.target.api_key, None); assert_eq!(data.target.url.path(), "/foo/bar"); - for (key, value) in HashMap::from(header_tags) { - assert_eq!(data.headers.get(&key).unwrap(), &value); + for (key, value) in &HeaderMap::from(header_tags) { + assert_eq!(data.headers.get(key).unwrap(), value); } } diff --git a/libdd-trace-utils/src/send_with_retry/mod.rs b/libdd-trace-utils/src/send_with_retry/mod.rs index dea20d528a..28b24cf11d 100644 --- a/libdd-trace-utils/src/send_with_retry/mod.rs +++ b/libdd-trace-utils/src/send_with_retry/mod.rs @@ -8,9 +8,9 @@ mod retry_strategy; pub use retry_strategy::{RetryBackoffType, RetryStrategy}; use bytes::Bytes; -use http::{HeaderName, Method}; +use http::{HeaderMap, Method}; use libdd_common::{http_common, Connect, Endpoint, GenericHttpClient, HttpRequestBuilder}; -use std::{collections::HashMap, ops::Deref, time::Duration}; +use std::time::Duration; use tracing::{debug, error}; pub type Attempts = u32; @@ -97,17 +97,17 @@ impl std::error::Error for RequestError {} /// # use libdd_common::Endpoint; /// # use libdd_common::http_common::new_default_client; /// # use libdd_trace_utils::send_with_retry::*; -/// # use std::collections::HashMap; /// # async fn run() -> SendWithRetryResult { /// let payload: Vec = vec![0, 1, 2, 3]; /// let target = Endpoint { /// url: "localhost:8126/v04/traces".parse::().unwrap(), /// ..Endpoint::default() /// }; -/// let headers = HashMap::from([( +/// let mut headers = http::HeaderMap::new(); +/// headers.insert( /// http::HeaderName::from_static("content-type"), -/// "application/msgpack".to_string(), -/// )]); +/// http::HeaderValue::from_static("application/msgpack"), +/// ); /// let retry_strategy = RetryStrategy::new(3, 10, RetryBackoffType::Exponential, Some(5)); /// let client = new_default_client(); /// send_with_retry(&client, &target, payload, &headers, &retry_strategy).await @@ -117,7 +117,7 @@ pub async fn send_with_retry( client: &GenericHttpClient, target: &Endpoint, payload: Vec, - headers: &HashMap, + headers: &HeaderMap, retry_strategy: &RetryStrategy, ) -> SendWithRetryResult { let mut request_attempt = 0; @@ -145,7 +145,7 @@ pub async fn send_with_retry( .or(Err(SendWithRetryError::Build(request_attempt)))? .method(Method::POST); for (key, value) in headers { - req = req.header(key.clone(), value.deref()); + req = req.header(key, value); } match send_request( @@ -288,7 +288,7 @@ mod tests { &client, &target_endpoint, vec![0, 1, 2, 3], - &HashMap::new(), + &HeaderMap::new(), &strategy, ) .await; @@ -337,7 +337,7 @@ mod tests { &client, &target_endpoint, vec![0, 1, 2, 3], - &HashMap::new(), + &HeaderMap::new(), &strategy, ) .await; @@ -386,7 +386,7 @@ mod tests { &client, &target_endpoint, vec![0, 1, 2, 3], - &HashMap::new(), + &HeaderMap::new(), &strategy, ) .await; @@ -435,7 +435,7 @@ mod tests { &client, &target_endpoint, vec![0, 1, 2, 3], - &HashMap::new(), + &HeaderMap::new(), &strategy, ) .await; diff --git a/libdd-trace-utils/src/tracer_header_tags.rs b/libdd-trace-utils/src/tracer_header_tags.rs index 2e6595a306..04cf349798 100644 --- a/libdd-trace-utils/src/tracer_header_tags.rs +++ b/libdd-trace-utils/src/tracer_header_tags.rs @@ -3,7 +3,6 @@ use http::{HeaderMap, HeaderName, HeaderValue}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; macro_rules! parse_string_header { ( @@ -40,67 +39,79 @@ pub struct TracerHeaderTags<'a> { pub dropped_p0_spans: usize, } -impl<'a> From> for HashMap { - fn from(tags: TracerHeaderTags<'a>) -> HashMap { - let mut headers = HashMap::from([ - ( - HeaderName::from_static("datadog-meta-lang"), - tags.lang.to_string(), - ), - ( - HeaderName::from_static("datadog-meta-lang-version"), - tags.lang_version.to_string(), - ), - ( - HeaderName::from_static("datadog-meta-lang-interpreter"), - tags.lang_interpreter.to_string(), - ), - ( - HeaderName::from_static("datadog-meta-lang-interpreter-vendor"), - tags.lang_vendor.to_string(), - ), - ( - HeaderName::from_static("datadog-meta-tracer-version"), - tags.tracer_version.to_string(), - ), - ( - HeaderName::from_static("datadog-container-id"), - tags.container_id.to_string(), - ), - ( +impl<'a> From> for HeaderMap { + fn from(tags: TracerHeaderTags<'a>) -> HeaderMap { + let mut headers = HeaderMap::new(); + fn try_insert( + h: &mut HeaderMap, + key: HeaderName, + v: impl TryInto + AsRef<[u8]>, + ) { + if v.as_ref().is_empty() { + return; + } + if let Ok(v) = v.try_into() { + h.insert(key, v); + } + } + try_insert( + &mut headers, + HeaderName::from_static("datadog-meta-lang"), + tags.lang, + ); + try_insert( + &mut headers, + HeaderName::from_static("datadog-meta-lang-version"), + tags.lang_version, + ); + try_insert( + &mut headers, + HeaderName::from_static("datadog-meta-lang-interpreter"), + tags.lang_interpreter, + ); + try_insert( + &mut headers, + HeaderName::from_static("datadog-meta-lang-interpreter-vendor"), + tags.lang_vendor, + ); + try_insert( + &mut headers, + HeaderName::from_static("datadog-meta-tracer-version"), + tags.tracer_version, + ); + try_insert( + &mut headers, + HeaderName::from_static("datadog-container-id"), + tags.container_id, + ); + if tags.client_computed_stats { + try_insert( + &mut headers, HeaderName::from_static("datadog-client-computed-stats"), - if tags.client_computed_stats { - "true".to_string() - } else { - String::new() - }, - ), - ( + HeaderValue::from_static("true"), + ); + } + if tags.client_computed_top_level { + try_insert( + &mut headers, HeaderName::from_static("datadog-client-computed-top-level"), - if tags.client_computed_top_level { - "true".to_string() - } else { - String::new() - }, - ), - ( + HeaderValue::from_static("true"), + ); + } + if tags.dropped_p0_traces > 0 { + try_insert( + &mut headers, HeaderName::from_static("datadog-client-dropped-p0-traces"), - if tags.dropped_p0_traces > 0 { - tags.dropped_p0_traces.to_string() - } else { - String::new() - }, - ), - ( + tags.dropped_p0_traces.to_string(), + ); + } + if tags.dropped_p0_spans > 0 { + try_insert( + &mut headers, HeaderName::from_static("datadog-client-dropped-p0-spans"), - if tags.dropped_p0_spans > 0 { - tags.dropped_p0_spans.to_string() - } else { - String::new() - }, - ), - ]); - headers.retain(|_, v| !v.is_empty()); + tags.dropped_p0_spans.to_string(), + ); + } headers } } @@ -141,14 +152,10 @@ impl<'a> From<&'a HeaderMap> for TracerHeaderTags<'a> { #[cfg(test)] mod tests { - use std::str::FromStr; - use super::*; - use hyper::HeaderMap; - fn get<'a>(m: &'a HashMap, key: &str) -> Option<&'a str> { - m.get(&HeaderName::from_str(key).unwrap()) - .map(|v| v.as_str()) + fn get<'a>(m: &'a HeaderMap, key: &str) -> Option<&'a str> { + m.get(key).and_then(|v| v.to_str().ok()) } #[test] @@ -166,7 +173,7 @@ mod tests { dropped_p0_spans: 120, }; - let map: HashMap = header_tags.into(); + let map: HeaderMap = header_tags.into(); assert_eq!(map.len(), 10); assert_eq!(get(&map, "datadog-meta-lang"), Some("test-lang")); @@ -202,7 +209,7 @@ mod tests { dropped_p0_traces: 0, }; - let map: HashMap = header_tags.into(); + let map: HeaderMap = header_tags.into(); assert_eq!(map.len(), 5); assert_eq!(get(&map, "datadog-meta-lang"), Some("test-lang")); From 6b60d8bddbd3c1e71bdbca31f362a052c077a338 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Tue, 17 Mar 2026 17:17:55 +0100 Subject: [PATCH 5/7] perf: reserve header names capacity --- libdd-trace-utils/src/tracer_header_tags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdd-trace-utils/src/tracer_header_tags.rs b/libdd-trace-utils/src/tracer_header_tags.rs index 04cf349798..45d30fa0e4 100644 --- a/libdd-trace-utils/src/tracer_header_tags.rs +++ b/libdd-trace-utils/src/tracer_header_tags.rs @@ -41,7 +41,7 @@ pub struct TracerHeaderTags<'a> { impl<'a> From> for HeaderMap { fn from(tags: TracerHeaderTags<'a>) -> HeaderMap { - let mut headers = HeaderMap::new(); + let mut headers = HeaderMap::with_capacity(10); fn try_insert( h: &mut HeaderMap, key: HeaderName, From 59e405830a047e5eb2412266042c33d056aea0de Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Tue, 17 Mar 2026 17:33:55 +0100 Subject: [PATCH 6/7] fix: cleanup header value conversion --- .../src/trace_exporter/trace_serializer.rs | 37 ++++++------------- libdd-trace-utils/src/send_data/mod.rs | 13 ++----- 2 files changed, 14 insertions(+), 36 deletions(-) diff --git a/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs b/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs index e8ab34befa..2a257c7e82 100644 --- a/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs +++ b/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs @@ -79,13 +79,12 @@ impl<'a> TraceSerializer<'a> { /// Build HTTP headers for traces request fn build_traces_headers(&self, header_tags: TracerHeaderTags, chunk_count: usize) -> HeaderMap { let mut headers: HeaderMap = header_tags.into(); + headers.reserve(4); headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, HeaderValue::from_static("1")); - // should always be true, as the name should only contain visible ascii chars - let _ = HeaderValue::try_from(chunk_count.to_string()) - .map(|v| headers.insert(DATADOG_TRACE_COUNT, v)); + headers.insert(DATADOG_TRACE_COUNT, chunk_count.into()); headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); if let Some(agent_payload_response_version) = &self.agent_payload_response_version { - // should always be true, as the version should only contain visible ascii chars + // should never fail, as the version should only contain visible ascii chars let _ = HeaderValue::try_from(agent_payload_response_version.header_value()) .map(|v| headers.insert(DATADOG_RATES_PAYLOAD_VERSION, v)); } @@ -173,15 +172,9 @@ mod tests { let headers = serializer.build_traces_headers(header_tags, 3); // Check basic headers are present - assert_eq!( - headers.get(DATADOG_SEND_REAL_HTTP_STATUS.as_str()).unwrap(), - "1" - ); - assert_eq!(headers.get(DATADOG_TRACE_COUNT.as_str()).unwrap(), "3"); - assert_eq!( - headers.get(CONTENT_TYPE.as_str()).unwrap(), - APPLICATION_MSGPACK_STR - ); + assert_eq!(headers.get(DATADOG_SEND_REAL_HTTP_STATUS).unwrap(), "1"); + assert_eq!(headers.get(DATADOG_TRACE_COUNT).unwrap(), "3"); + assert_eq!(headers.get(CONTENT_TYPE).unwrap(), APPLICATION_MSGPACK_STR); // Check tracer metadata headers are present assert_eq!(headers.get("datadog-meta-lang").unwrap(), "rust"); @@ -209,8 +202,8 @@ mod tests { let headers = serializer.build_traces_headers(header_tags, 2); // Check that agent payload version header is included - assert!(headers.contains_key(DATADOG_RATES_PAYLOAD_VERSION.as_str())); - assert_eq!(headers.get(DATADOG_TRACE_COUNT.as_str()).unwrap(), "2"); + assert!(headers.contains_key(DATADOG_RATES_PAYLOAD_VERSION)); + assert_eq!(headers.get(DATADOG_TRACE_COUNT).unwrap(), "2"); } #[test] @@ -343,10 +336,7 @@ mod tests { assert!(!prepared.headers.is_empty()); // Check headers - assert_eq!( - prepared.headers.get(DATADOG_TRACE_COUNT.as_str()).unwrap(), - "2" - ); + assert_eq!(prepared.headers.get(DATADOG_TRACE_COUNT).unwrap(), "2"); assert_eq!(prepared.headers.get("datadog-meta-lang").unwrap(), "rust"); } @@ -377,9 +367,7 @@ mod tests { let prepared = result.unwrap(); assert_eq!(prepared.chunk_count, 1); - assert!(prepared - .headers - .contains_key(DATADOG_RATES_PAYLOAD_VERSION.as_str())); + assert!(prepared.headers.contains_key(DATADOG_RATES_PAYLOAD_VERSION)); } #[test] @@ -394,10 +382,7 @@ mod tests { let prepared = result.unwrap(); assert_eq!(prepared.chunk_count, 0); assert!(!prepared.data.is_empty()); // Even empty traces result in some serialized data - assert_eq!( - prepared.headers.get(DATADOG_TRACE_COUNT.as_str()).unwrap(), - "0" - ); + assert_eq!(prepared.headers.get(DATADOG_TRACE_COUNT).unwrap(), "0"); } #[test] diff --git a/libdd-trace-utils/src/send_data/mod.rs b/libdd-trace-utils/src/send_data/mod.rs index 1559611008..60708bfc5c 100644 --- a/libdd-trace-utils/src/send_data/mod.rs +++ b/libdd-trace-utils/src/send_data/mod.rs @@ -354,11 +354,8 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(tracer_payload.chunks.len()).unwrap(); let mut headers = self.headers.clone(); - #[allow(clippy::unwrap_used)] - headers.insert( - DATADOG_TRACE_COUNT, - HeaderValue::try_from(chunks.to_string()).unwrap(), - ); + headers.reserve(2); + headers.insert(DATADOG_TRACE_COUNT, chunks.into()); headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); let payload = match rmp_serde::to_vec_named(tracer_payload) { @@ -379,11 +376,7 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(self.tracer_payloads.size()).unwrap(); let mut headers = self.headers.clone(); - #[allow(clippy::unwrap_used)] - headers.insert( - DATADOG_TRACE_COUNT, - HeaderValue::try_from(chunks.to_string()).unwrap(), - ); + headers.insert(DATADOG_TRACE_COUNT, chunks.into()); headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); let payload = msgpack_encoder::v04::to_vec(payload); From 13ba4a8b5ee2abfed44eeda7b788a03683437ab1 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Tue, 17 Mar 2026 19:06:00 +0100 Subject: [PATCH 7/7] fix: remove unnecessary unwrap --- libdd-trace-utils/src/send_data/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libdd-trace-utils/src/send_data/mod.rs b/libdd-trace-utils/src/send_data/mod.rs index 60708bfc5c..0220b6591a 100644 --- a/libdd-trace-utils/src/send_data/mod.rs +++ b/libdd-trace-utils/src/send_data/mod.rs @@ -376,6 +376,7 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(self.tracer_payloads.size()).unwrap(); let mut headers = self.headers.clone(); + headers.reserve(2); headers.insert(DATADOG_TRACE_COUNT, chunks.into()); headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); @@ -393,11 +394,8 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(self.tracer_payloads.size()).unwrap(); let mut headers = self.headers.clone(); - #[allow(clippy::unwrap_used)] - headers.insert( - DATADOG_TRACE_COUNT, - HeaderValue::try_from(chunks.to_string()).unwrap(), - ); + headers.reserve(2); + headers.insert(DATADOG_TRACE_COUNT, chunks.into()); headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); let payload = match rmp_serde::to_vec(payload) {