Skip to content
Draft
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 @@ -1300,6 +1300,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.ext.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
88 changes: 86 additions & 2 deletions bottlecap/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,42 @@ use serde::Deserialize;
pub type Config = datadog_agent_config::Config<LambdaConfig>;

#[allow(clippy::module_name_repetitions)]
#[inline]
#[must_use]
pub fn get_config(config_directory: &Path) -> Config {
get_config_with_extension::<LambdaConfig>(config_directory)
let mut config = get_config_with_extension::<LambdaConfig>(config_directory);
apply_serverless_apm_only(&mut config);
config
}

/// 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`), and
/// log egress is guarded in the logs flusher.
fn apply_serverless_apm_only(config: &mut Config) {
if !config.ext.serverless_apm_only {
return;
}

if config.ext.serverless_logs_enabled
|| config.ext.enhanced_metrics
|| config.ext.lambda_proc_enhanced_metrics
|| config.otlp_config_metrics_enabled
|| config.otlp_config_logs_enabled
{
tracing::debug!(
"DD_SERVERLESS_APM_ONLY is enabled: forcing logs and all metrics off (traces-only mode)"
);
}

config.ext.serverless_logs_enabled = false;
config.ext.enhanced_metrics = false;
config.ext.lambda_proc_enhanced_metrics = false;
config.otlp_config_metrics_enabled = false;
config.otlp_config_logs_enabled = false;
}
// ---------------------------------------------------------------------------
// LambdaConfig — bottlecap's `ConfigExtension` for the shared
Expand Down Expand Up @@ -60,6 +92,12 @@ pub struct LambdaConfig {
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. Defaults to `false`.
pub serverless_apm_only: bool,
pub serverless_flush_strategy: UpstreamFlushStrategy,
pub enhanced_metrics: bool,
pub lambda_proc_enhanced_metrics: bool,
Expand Down Expand Up @@ -89,6 +127,7 @@ impl Default for LambdaConfig {
kms_api_key: String::new(),
api_key_ssm_arn: String::new(),
serverless_logs_enabled: true,
serverless_apm_only: false,
serverless_flush_strategy: UpstreamFlushStrategy::Default,
enhanced_metrics: true,
lambda_proc_enhanced_metrics: true,
Expand Down Expand Up @@ -138,6 +177,13 @@ pub struct LambdaConfigSource {
#[serde(deserialize_with = "deser_opt_bool")]
pub logs_enabled: Option<bool>,

/// `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. Traces and APM trace
/// stats are unaffected. Defaults to `false`.
#[serde(deserialize_with = "deser_opt_bool")]
pub serverless_apm_only: Option<bool>,

pub serverless_flush_strategy: Option<UpstreamFlushStrategy>,

#[serde(deserialize_with = "deser_opt_bool")]
Expand Down Expand Up @@ -193,6 +239,7 @@ impl DatadogConfigExtension for LambdaConfig {
datadog_agent_config::merge_fields!(self, source,
string: [api_key_secret_arn, kms_api_key, api_key_ssm_arn],
value: [
serverless_apm_only,
serverless_flush_strategy,
enhanced_metrics,
lambda_proc_enhanced_metrics,
Expand Down Expand Up @@ -260,6 +307,43 @@ mod lambda_config_tests {
assert_eq!(config.ext, LambdaConfig::default());
}

#[test]
fn serverless_apm_only_defaults_off() {
let config = load(|_| Ok(()));
assert!(!config.ext.serverless_apm_only);
// Defaults remain unchanged when APM-only is not set.
assert!(config.ext.serverless_logs_enabled);
assert!(config.ext.enhanced_metrics);
assert!(config.ext.lambda_proc_enhanced_metrics);
}

#[test]
fn serverless_apm_only_forces_metrics_and_logs_off() {
// Exercised through `get_config` (not `load`) because the override is
// applied there, after the shared env/yaml merge.
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.ext.serverless_apm_only);
assert!(!config.ext.serverless_logs_enabled);
assert!(!config.ext.enhanced_metrics);
assert!(!config.ext.lambda_proc_enhanced_metrics);
assert!(!config.otlp_config_metrics_enabled);
assert!(!config.otlp_config_logs_enabled);
Ok(())
});
}

// ---- string fields from env / yaml ----

#[test]
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.ext.serverless_apm_only {
return Vec::new();
}

let mut failed_requests = Vec::new();

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