Skip to content
Closed
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
11 changes: 11 additions & 0 deletions bottlecap/src/bin/bottlecap/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,17 @@ fn start_metrics_flushers(
) -> Vec<MetricsFlusher> {
let mut flushers = Vec::new();

// APM-only ("traces only") mode: do not create any metrics flushers, so that
// no metrics (custom DogStatsD, enhanced, or process) ever reach intake. The
// DogStatsD server still runs and the aggregator is still drained on flush, but
// the drained data is discarded because there is no flusher to send it. This is
// the authoritative guarantee that no infrastructure-monitoring charges are
// incurred (see DD_SERVERLESS_APM_ONLY).
if config.serverless_apm_only {
debug!("DD_SERVERLESS_APM_ONLY is enabled: not starting any metrics flushers");
return flushers;
}

let metrics_intake_url = if !config.dd_url.is_empty() {
let dd_dd_url = DdDdUrl::new(config.dd_url.clone()).expect("can't parse DD_DD_URL");

Expand Down
10 changes: 10 additions & 0 deletions bottlecap/src/config/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,14 @@ pub struct EnvConfig {
/// Enable logs for AWS Lambda. Alias for `DD_SERVERLESS_LOGS_ENABLED`. Default is `true`.
#[serde(deserialize_with = "deserialize_optional_bool_from_anything")]
pub logs_enabled: Option<bool>,
/// @env `DD_SERVERLESS_APM_ONLY`
///
/// Run the extension in APM-only ("traces only") mode. When `true`, logs and
/// all metrics (enhanced, process, custom `DogStatsD`, and OTLP) are suppressed
/// at intake so no infrastructure-monitoring or log-ingestion charges are
/// incurred. Traces and APM trace stats are unaffected. Default is `false`.
#[serde(deserialize_with = "deserialize_optional_bool_from_anything")]
pub serverless_apm_only: Option<bool>,
/// @env `DD_SERVERLESS_FLUSH_STRATEGY`
///
/// The flush strategy to use for AWS Lambda.
Expand Down Expand Up @@ -677,6 +685,7 @@ fn merge_config(config: &mut Config, env_config: &EnvConfig) {
|| env_config.logs_enabled.unwrap_or(false);
}

merge_option_to_value!(config, env_config, serverless_apm_only);
merge_option_to_value!(config, env_config, serverless_flush_strategy);
merge_option_to_value!(config, env_config, enhanced_metrics);
merge_option_to_value!(config, env_config, lambda_proc_enhanced_metrics);
Expand Down Expand Up @@ -1037,6 +1046,7 @@ mod tests {
kms_api_key: "test-kms-key".to_string(),
api_key_ssm_arn: String::default(),
serverless_logs_enabled: false,
serverless_apm_only: false,
serverless_flush_strategy: FlushStrategy::Periodically(PeriodicStrategy {
interval: 60000,
}),
Expand Down
70 changes: 70 additions & 0 deletions bottlecap/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,31 @@ impl ConfigBuilder {
self.config.site = "datadoghq.com".to_string();
}

// APM-only ("traces only") mode: suppress every billable metrics and logs
// egress path so the customer incurs no infrastructure-monitoring or
// log-ingestion charges. This intentionally overrides any individually
// configured metrics/logs toggles, since the guarantee must hold even if a
// user also set, e.g., DD_ENHANCED_METRICS=true. Traces and APM trace stats
// are unaffected. The custom DogStatsD egress is additionally disabled in
// the metrics flusher wiring (see start_metrics_flushers in main.rs).
if self.config.serverless_apm_only {
if self.config.serverless_logs_enabled
|| self.config.enhanced_metrics
|| self.config.lambda_proc_enhanced_metrics
|| self.config.otlp_config_metrics_enabled
|| self.config.otlp_config_logs_enabled
{
debug!(
"DD_SERVERLESS_APM_ONLY is enabled: forcing logs and all metrics off (traces-only mode)"
);
}
self.config.serverless_logs_enabled = false;
self.config.enhanced_metrics = false;
self.config.lambda_proc_enhanced_metrics = false;
self.config.otlp_config_metrics_enabled = false;
self.config.otlp_config_logs_enabled = false;
}

// If `proxy_https` is not set, set it from `HTTPS_PROXY` environment variable
// if it exists
if let Ok(https_proxy) = std::env::var("HTTPS_PROXY")
Expand Down Expand Up @@ -355,6 +380,11 @@ pub struct Config {
pub kms_api_key: String,
pub api_key_ssm_arn: String,
pub serverless_logs_enabled: bool,
/// When true, the extension operates in APM-only ("traces only") mode:
/// logs and all metrics (enhanced, process, custom `DogStatsD`, and OTLP) are
/// suppressed at intake so that no infrastructure-monitoring or log-ingestion
/// charges are incurred. Traces and APM trace stats are unaffected.
pub serverless_apm_only: bool,
pub serverless_flush_strategy: FlushStrategy,
pub enhanced_metrics: bool,
pub lambda_proc_enhanced_metrics: bool,
Expand Down Expand Up @@ -472,6 +502,7 @@ impl Default for Config {
kms_api_key: String::default(),
api_key_ssm_arn: String::default(),
serverless_logs_enabled: true,
serverless_apm_only: false,
serverless_flush_strategy: FlushStrategy::Default,
enhanced_metrics: true,
lambda_proc_enhanced_metrics: true,
Expand Down Expand Up @@ -1454,6 +1485,45 @@ pub mod tests {
});
}

#[test]
fn test_serverless_apm_only_defaults_off() {
figment::Jail::expect_with(|jail| {
jail.clear_env();
let config = get_config(Path::new(""));
assert!(!config.serverless_apm_only);
// Defaults remain unchanged when APM-only is not set.
assert!(config.serverless_logs_enabled);
assert!(config.enhanced_metrics);
assert!(config.lambda_proc_enhanced_metrics);
Ok(())
});
}

#[test]
fn test_serverless_apm_only_forces_metrics_and_logs_off() {
figment::Jail::expect_with(|jail| {
jail.clear_env();
jail.set_env("DD_SERVERLESS_APM_ONLY", "true");
// Even when a user explicitly enables these, APM-only must override them
// so that no metrics or logs reach intake (billing guarantee).
jail.set_env("DD_SERVERLESS_LOGS_ENABLED", "true");
jail.set_env("DD_ENHANCED_METRICS", "true");
jail.set_env("DD_LAMBDA_PROC_ENHANCED_METRICS", "true");
jail.set_env("DD_OTLP_CONFIG_METRICS_ENABLED", "true");
jail.set_env("DD_OTLP_CONFIG_LOGS_ENABLED", "true");

let config = get_config(Path::new(""));

assert!(config.serverless_apm_only);
assert!(!config.serverless_logs_enabled);
assert!(!config.enhanced_metrics);
assert!(!config.lambda_proc_enhanced_metrics);
assert!(!config.otlp_config_metrics_enabled);
assert!(!config.otlp_config_logs_enabled);
Ok(())
});
}

#[test]
fn test_overrides_config_based_on_priority() {
figment::Jail::expect_with(|jail| {
Expand Down
4 changes: 4 additions & 0 deletions bottlecap/src/config/yaml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ pub struct YamlConfig {
pub serverless_logs_enabled: Option<bool>,
#[serde(deserialize_with = "deserialize_optional_bool_from_anything")]
pub logs_enabled: Option<bool>,
#[serde(deserialize_with = "deserialize_optional_bool_from_anything")]
pub serverless_apm_only: Option<bool>,
pub serverless_flush_strategy: Option<FlushStrategy>,
#[serde(deserialize_with = "deserialize_optional_bool_from_anything")]
pub enhanced_metrics: Option<bool>,
Expand Down Expand Up @@ -701,6 +703,7 @@ fn merge_config(config: &mut Config, yaml_config: &YamlConfig) {
|| yaml_config.logs_enabled.unwrap_or(false);
}

merge_option_to_value!(config, yaml_config, serverless_apm_only);
merge_option_to_value!(config, yaml_config, serverless_flush_strategy);
merge_option_to_value!(config, yaml_config, enhanced_metrics);
merge_option_to_value!(config, yaml_config, lambda_proc_enhanced_metrics);
Expand Down Expand Up @@ -1011,6 +1014,7 @@ api_security_sample_delay: 60 # Seconds
kms_api_key: "test-kms-key".to_string(),
api_key_ssm_arn: String::default(),
serverless_logs_enabled: false,
serverless_apm_only: false,
serverless_flush_strategy: FlushStrategy::Periodically(PeriodicStrategy {
interval: 60000,
}),
Expand Down
8 changes: 8 additions & 0 deletions bottlecap/src/logs/flusher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,14 @@ impl LogsFlusher {
&self,
retry_request: Option<reqwest::RequestBuilder>,
) -> Vec<reqwest::RequestBuilder> {
// APM-only ("traces only") mode: never send logs to intake. Logs are also
// dropped upstream (serverless_logs_enabled is forced off, so the processor
// never queues them), but this guard guarantees no log egress regardless of
// aggregator or redrive state. See DD_SERVERLESS_APM_ONLY.
if self.config.serverless_apm_only {
return Vec::new();
}

let mut failed_requests = Vec::new();

// If retry_request is provided, only process that request
Expand Down