Skip to content

Commit 8a94026

Browse files
committed
fix: adapt bottlecap to libdatadog HttpClientTrait architecture
libdatadog #1555 replaced raw hyper-client APIs with a HttpClientTrait abstraction. SendData::send and send_with_retry now require H: HttpClientTrait, and stats_utils::send_stats_payload_with_client was removed in favor of a generic that constructs its own client internally. - Wrap the proxy/TLS-aware hyper client in a struct that implements libdd_capabilities::HttpClientTrait so it can be passed to libdd trace senders without losing custom proxy / CA / skip-SSL config. - Inline the stats POST in stats_flusher to keep using the configured HttpClient (preserves Lambda's pool_max_idle_per_host(0) tuning that avoids stale connections after freeze/resume). - Update obfuscation_config::ObfuscationConfig construction for the new nested HttpConfig/MemcachedConfig/RedisConfig layout.
1 parent d4162ee commit 8a94026

3 files changed

Lines changed: 107 additions & 17 deletions

File tree

bottlecap/src/bin/bottlecap/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,11 +1148,11 @@ fn start_trace_agent(
11481148

11491149
let obfuscation_config = obfuscation_config::ObfuscationConfig {
11501150
tag_replace_rules: config.apm_replace_tags.clone(),
1151-
http_remove_path_digits: config.apm_config_obfuscation_http_remove_paths_with_digits,
1152-
http_remove_query_string: config.apm_config_obfuscation_http_remove_query_string,
1153-
obfuscate_memcached: false,
1154-
obfuscation_redis_enabled: false,
1155-
obfuscation_redis_remove_all_args: false,
1151+
http: obfuscation_config::HttpConfig {
1152+
remove_paths_with_digits: config.apm_config_obfuscation_http_remove_paths_with_digits,
1153+
remove_query_string: config.apm_config_obfuscation_http_remove_query_string,
1154+
},
1155+
..Default::default()
11561156
};
11571157

11581158
let trace_processor = Arc::new(trace_processor::ServerlessTraceProcessor {

bottlecap/src/traces/http_client.rs

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,79 @@
66
//! This module provides the HTTP client type required by `libdd_trace_utils`
77
//! for sending traces and stats to Datadog intake endpoints.
88
9+
use http_body_util::BodyExt;
910
use hyper_http_proxy;
1011
use hyper_rustls::HttpsConnectorBuilder;
11-
use libdd_common::{GenericHttpClient, http_common};
12+
use libdd_capabilities::{
13+
MaybeSend,
14+
http::{HttpClientTrait, HttpError},
15+
};
16+
use libdd_common::http_common::{self, Body, GenericHttpClient};
1217
use rustls::RootCertStore;
1318
use rustls_pki_types::CertificateDer;
1419
use std::error::Error;
1520
use std::fs::File;
21+
use std::future::Future;
1622
use std::io::BufReader;
1723
use std::sync::{Arc, LazyLock};
1824
use tracing::debug;
1925

20-
/// Type alias for the HTTP client used by trace and stats flushers.
21-
///
22-
/// This is the client type expected by `libdd_trace_utils::SendData::send()`.
23-
pub type HttpClient =
26+
type InnerClient =
2427
GenericHttpClient<hyper_http_proxy::ProxyConnector<libdd_common::connector::Connector>>;
2528

29+
/// HTTP client used by trace and stats flushers.
30+
///
31+
/// Wraps a hyper client preconfigured with optional proxy and TLS settings, and
32+
/// implements [`HttpClientTrait`] so it can be passed to `libdd_trace_utils`
33+
/// senders such as `SendData::send`.
34+
#[derive(Clone, Debug)]
35+
pub struct HttpClient {
36+
inner: InnerClient,
37+
}
38+
39+
impl HttpClient {
40+
fn new(inner: InnerClient) -> Self {
41+
Self { inner }
42+
}
43+
}
44+
45+
impl HttpClientTrait for HttpClient {
46+
fn new_client() -> Self {
47+
// Used by libdd APIs that construct a default client when no
48+
// pre-configured one is supplied. Bottlecap's production code paths
49+
// build the client via `create_client` so this fallback only matches
50+
// libdatadog's `new_default_client` shape.
51+
let connector = libdd_common::connector::Connector::default();
52+
let proxy_connector = hyper_http_proxy::ProxyConnector::new(connector)
53+
.expect("failed to build default proxy connector");
54+
let inner = http_common::client_builder()
55+
.pool_max_idle_per_host(0)
56+
.build(proxy_connector);
57+
HttpClient { inner }
58+
}
59+
60+
fn request(
61+
&self,
62+
req: http::Request<bytes::Bytes>,
63+
) -> impl Future<Output = Result<http::Response<bytes::Bytes>, HttpError>> + MaybeSend {
64+
let client = self.inner.clone();
65+
async move {
66+
let hyper_req = req.map(Body::from_bytes);
67+
let response = client
68+
.request(hyper_req)
69+
.await
70+
.map_err(|e| HttpError::Network(e.into()))?;
71+
let (parts, body) = response.into_parts();
72+
let collected = body
73+
.collect()
74+
.await
75+
.map_err(|e| HttpError::ResponseBody(e.into()))?
76+
.to_bytes();
77+
Ok(http::Response::from_parts(parts, collected))
78+
}
79+
}
80+
}
81+
2682
/// Initialize the crypto provider needed for setting custom root certificates.
2783
fn ensure_crypto_provider_initialized() {
2884
static INIT_CRYPTO_PROVIDER: LazyLock<()> = LazyLock::new(|| {
@@ -185,13 +241,15 @@ pub fn create_client(
185241
"HTTP_CLIENT | Proxy connector created with proxy: {:?}",
186242
proxy_https
187243
);
188-
Ok(client)
244+
Ok(HttpClient::new(client))
189245
} else {
190246
let proxy_connector = hyper_http_proxy::ProxyConnector::new(connector)?;
191247
// Disable connection pooling to avoid stale connections after Lambda freeze/resume.
192248
// See comment above for detailed explanation.
193-
Ok(http_common::client_builder()
194-
.pool_max_idle_per_host(0)
195-
.build(proxy_connector))
249+
Ok(HttpClient::new(
250+
http_common::client_builder()
251+
.pool_max_idle_per_host(0)
252+
.build(proxy_connector),
253+
))
196254
}
197255
}

bottlecap/src/traces/stats_flusher.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use crate::config;
1111
use crate::lifecycle::invocation::processor::S_TO_MS;
1212
use crate::traces::http_client::HttpClient;
1313
use crate::traces::stats_aggregator::StatsAggregator;
14+
use bytes::Bytes;
1415
use dogstatsd::api_key::ApiKeyFactory;
16+
use libdd_capabilities::http::HttpClientTrait;
1517
use libdd_common::Endpoint;
1618
use libdd_trace_protobuf::pb;
1719
use libdd_trace_utils::{config_utils::trace_stats_url, stats_utils};
@@ -96,11 +98,11 @@ impl StatsFlusher {
9698

9799
for attempt in 1..=FLUSH_RETRY_COUNT {
98100
let start = std::time::Instant::now();
99-
let resp = stats_utils::send_stats_payload_with_client(
100-
serialized_stats_payload.clone(),
101+
let resp = send_stats_payload(
102+
&self.http_client,
101103
endpoint,
102104
api_key.as_str(),
103-
Some(&self.http_client),
105+
serialized_stats_payload.clone(),
104106
)
105107
.await;
106108
let elapsed = start.elapsed();
@@ -167,3 +169,33 @@ impl StatsFlusher {
167169
}
168170
}
169171
}
172+
173+
/// Posts a serialized stats payload using the supplied client.
174+
///
175+
/// Equivalent to libdatadog's `stats_utils::send_stats_payload`, but uses the
176+
/// caller-provided client so bottlecap's proxy/TLS configuration is preserved.
177+
async fn send_stats_payload(
178+
client: &HttpClient,
179+
target: &Endpoint,
180+
api_key: &str,
181+
data: Vec<u8>,
182+
) -> anyhow::Result<()> {
183+
let req = http::Request::builder()
184+
.method(http::Method::POST)
185+
.uri(target.url.clone())
186+
.header("Content-Type", "application/msgpack")
187+
.header("Content-Encoding", "gzip")
188+
.header("DD-API-KEY", api_key)
189+
.body(Bytes::from(data))?;
190+
191+
let response = client
192+
.request(req)
193+
.await
194+
.map_err(|e| anyhow::anyhow!("Failed to send trace stats: {e}"))?;
195+
196+
if response.status() != http::StatusCode::ACCEPTED {
197+
let response_body = String::from_utf8(response.into_body().to_vec()).unwrap_or_default();
198+
anyhow::bail!("Server did not accept trace stats: {response_body}");
199+
}
200+
Ok(())
201+
}

0 commit comments

Comments
 (0)