diff --git a/bottlecap/src/bin/bottlecap/main.rs b/bottlecap/src/bin/bottlecap/main.rs index 09b331f89..a60618701 100644 --- a/bottlecap/src/bin/bottlecap/main.rs +++ b/bottlecap/src/bin/bottlecap/main.rs @@ -37,6 +37,7 @@ use bottlecap::{ }, logger, logs::{agent::LogsAgent, flusher::LogsFlusher}, + metrics::enhanced::lambda::Lambda as enhanced_metrics, otlp::{agent::Agent as OtlpAgent, should_enable_otlp_agent}, proxy::{interceptor, should_start_proxy}, secrets::decrypt, @@ -84,9 +85,7 @@ use std::{ collections::{HashMap, hash_map}, env, io::{Error, Result}, - os::unix::process::CommandExt, path::Path, - process::Command, sync::{Arc, Mutex}, time::{Duration, Instant}, }; @@ -402,14 +401,7 @@ fn load_configs(start_time: Instant) -> (AwsConfig, AwsCredentials, Arc) let aws_credentials = AwsCredentials::from_env(); let lambda_directory: String = env::var("LAMBDA_TASK_ROOT").unwrap_or_else(|_| "/var/task".to_string()); - let config = match config::get_config(Path::new(&lambda_directory)) { - Ok(config) => Arc::new(config), - Err(_e) => { - let err = Command::new("/opt/datadog-agent-go").exec(); - panic!("Error starting the extension: {err:?}"); - } - }; - + let config = Arc::new(config::get_config(Path::new(&lambda_directory))); (aws_config, aws_credentials, config) } @@ -508,12 +500,22 @@ async fn extension_loop_active( .as_micros() .to_string() ); - + let metrics_intake_url = create_metrics_intake_url_prefix(config); let metrics_flushers = Arc::new(TokioMutex::new(start_metrics_flushers( Arc::clone(&api_key_factory), &metrics_aggr, + &metrics_intake_url, config, ))); + + // Create lambda enhanced metrics instance once + let lambda_enhanced_metrics = + enhanced_metrics::new(Arc::clone(&metrics_aggr), Arc::clone(config)); + + // Send config issue metrics + let config_issues = config::fallback(config); + send_config_issue_metric(&config_issues, &lambda_enhanced_metrics); + // Lifecycle Invocation Processor let invocation_processor = Arc::new(TokioMutex::new(InvocationProcessor::new( Arc::clone(&tags_provider), @@ -1006,33 +1008,33 @@ fn start_logs_agent( (logs_agent_channel, logs_flusher) } -fn start_metrics_flushers( - api_key_factory: Arc, - metrics_aggr: &Arc>, - config: &Arc, -) -> Vec { - let mut flushers = Vec::new(); - - let metrics_intake_url = if !config.dd_url.is_empty() { +fn create_metrics_intake_url_prefix(config: &Config) -> MetricsIntakeUrlPrefix { + if !config.dd_url.is_empty() { let dd_dd_url = DdDdUrl::new(config.dd_url.clone()).expect("can't parse DD_DD_URL"); - let prefix_override = MetricsIntakeUrlPrefixOverride::maybe_new(None, Some(dd_dd_url)); - MetricsIntakeUrlPrefix::new(None, prefix_override) + MetricsIntakeUrlPrefix::new(None, prefix_override).expect("can't parse DD_DD_URL prefix") } else if !config.url.is_empty() { let dd_url = DdUrl::new(config.url.clone()).expect("can't parse DD_URL"); - let prefix_override = MetricsIntakeUrlPrefixOverride::maybe_new(Some(dd_url), None); - MetricsIntakeUrlPrefix::new(None, prefix_override) + MetricsIntakeUrlPrefix::new(None, prefix_override).expect("can't parse DD_URL prefix") } else { - // use site let metrics_site = MetricsSite::new(config.site.clone()).expect("can't parse site"); - MetricsIntakeUrlPrefix::new(Some(metrics_site), None) - }; + MetricsIntakeUrlPrefix::new(Some(metrics_site), None).expect("can't parse site prefix") + } +} + +fn start_metrics_flushers( + api_key_factory: Arc, + metrics_aggr: &Arc>, + metrics_intake_url: &MetricsIntakeUrlPrefix, + config: &Arc, +) -> Vec { + let mut flushers = Vec::new(); let flusher_config = MetricsFlusherConfig { api_key_factory, aggregator: Arc::clone(metrics_aggr), - metrics_intake_url_prefix: metrics_intake_url.expect("can't parse site or override"), + metrics_intake_url_prefix: metrics_intake_url.clone(), https_proxy: config.proxy_https.clone(), timeout: Duration::from_secs(config.flush_timeout), retry_strategy: DsdRetryStrategy::Immediate(3), @@ -1157,6 +1159,28 @@ fn start_trace_agent( ) } +/// Sends metrics indicating issue with configuration. +/// +/// # Arguments +/// * `issue_reasons` - Vector of messages describing the issue with the configurations +/// * `lambda_enhanced_metrics` - The lambda enhanced metrics instance +fn send_config_issue_metric(issue_reasons: &[String], lambda_enhanced_metrics: &enhanced_metrics) { + if issue_reasons.is_empty() { + return; + } + let now = std::time::UNIX_EPOCH + .elapsed() + .expect("can't poll clock") + .as_secs() + .try_into() + .unwrap_or_default(); + + // Setup a separate metric for each config issue reason + for issue_reason in issue_reasons { + lambda_enhanced_metrics.set_config_load_issue_metric(now, issue_reason); + } +} + async fn start_dogstatsd(metrics_aggr: &Arc>) -> CancellationToken { let dogstatsd_config = DogStatsDConfig { host: EXTENSION_HOST.to_string(), diff --git a/bottlecap/src/config/mod.rs b/bottlecap/src/config/mod.rs index faeeca2ad..4f5073262 100644 --- a/bottlecap/src/config/mod.rs +++ b/bottlecap/src/config/mod.rs @@ -434,10 +434,13 @@ impl Default for Config { } fn log_fallback_reason(reason: &str) { - println!("{{\"DD_EXTENSION_FALLBACK_REASON\":\"{reason}\"}}"); + error!("Fallback support for {reason} is no longer available."); } -fn fallback(config: &Config) -> Result<(), ConfigError> { +#[must_use = "fallback reasons should be processed to emit appropriate metrics"] +pub fn fallback(config: &Config) -> Vec { + let mut fallback_reasons = Vec::new(); + // Customer explicitly opted out of the Next Gen extension let opted_out = match config.extension_version.as_deref() { Some("compatibility") => true, @@ -446,21 +449,18 @@ fn fallback(config: &Config) -> Result<(), ConfigError> { }; if opted_out { - log_fallback_reason("extension_version"); - return Err(ConfigError::UnsupportedField( - "extension_version".to_string(), - )); + let reason = "extension_version"; + log_fallback_reason(reason); + fallback_reasons.push(reason.to_string()); } // ASM / .NET // todo(duncanista): Remove once the .NET runtime is fixed if config.serverless_appsec_enabled && has_dotnet_binary() { - log_fallback_reason("serverless_appsec_enabled_dotnet"); - return Err(ConfigError::UnsupportedField( - "serverless_appsec_enabled_dotnet".to_string(), - )); + let reason = "serverless_appsec_enabled_dotnet"; + log_fallback_reason(reason); + fallback_reasons.push(reason.to_string()); } - // OTLP let has_otlp_config = config .otlp_config_receiver_protocols_grpc_endpoint @@ -492,25 +492,22 @@ fn fallback(config: &Config) -> Result<(), ConfigError> { || config.otlp_config_logs_enabled; if has_otlp_config { - log_fallback_reason("otel"); - return Err(ConfigError::UnsupportedField("otel".to_string())); + let reason = "otel"; + log_fallback_reason(reason); + fallback_reasons.push(reason.to_string()); } - Ok(()) + fallback_reasons } #[allow(clippy::module_name_repetitions)] -pub fn get_config(config_directory: &Path) -> Result { +#[must_use = "configuration must be used to initialize the application"] +pub fn get_config(config_directory: &Path) -> Config { let path: std::path::PathBuf = config_directory.join("datadog.yaml"); let mut config_builder = ConfigBuilder::default() .add_source(Box::new(YamlConfigSource { path })) .add_source(Box::new(EnvConfigSource)); - - let config = config_builder.build(); - - fallback(&config)?; - - Ok(config) + config_builder.build() } #[inline] @@ -676,11 +673,7 @@ pub mod tests { figment::Jail::expect_with(|jail| { jail.clear_env(); jail.set_env("DD_EXTENSION_VERSION", "compatibility"); - let config = get_config(Path::new("")).expect_err("should reject unknown fields"); - assert_eq!( - config, - ConfigError::UnsupportedField("extension_version".to_string()) - ); + let _config = get_config(Path::new("")); Ok(()) }); } @@ -694,8 +687,7 @@ pub mod tests { "localhost:4138", ); - let config = get_config(Path::new("")).expect_err("should reject unknown fields"); - assert_eq!(config, ConfigError::UnsupportedField("otel".to_string())); + let _config = get_config(Path::new("")); Ok(()) }); } @@ -715,8 +707,7 @@ pub mod tests { ", )?; - let config = get_config(Path::new("")).expect_err("should reject unknown fields"); - assert_eq!(config, ConfigError::UnsupportedField("otel".to_string())); + let _config = get_config(Path::new("")); Ok(()) }); } @@ -726,7 +717,7 @@ pub mod tests { figment::Jail::expect_with(|jail| { jail.clear_env(); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!( config.logs_config_logs_dd_url, "https://http-intake.logs.datadoghq.com".to_string() @@ -744,7 +735,7 @@ pub mod tests { "agent-http-intake-pci.logs.datadoghq.com:443", ); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!( config.logs_config_logs_dd_url, "agent-http-intake-pci.logs.datadoghq.com:443".to_string() @@ -759,7 +750,7 @@ pub mod tests { jail.clear_env(); jail.set_env("DD_APM_DD_URL", "https://trace-pci.agent.datadoghq.com"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!( config.apm_dd_url, "https://trace-pci.agent.datadoghq.com/api/v0.2/traces".to_string() @@ -774,7 +765,7 @@ pub mod tests { jail.clear_env(); jail.set_env("DD_DD_URL", "custom_proxy:3128"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.dd_url, "custom_proxy:3128".to_string()); Ok(()) }); @@ -786,7 +777,7 @@ pub mod tests { jail.clear_env(); jail.set_env("DD_URL", "custom_proxy:3128"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.url, "custom_proxy:3128".to_string()); Ok(()) }); @@ -797,7 +788,7 @@ pub mod tests { figment::Jail::expect_with(|jail| { jail.clear_env(); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.dd_url, String::new()); Ok(()) }); @@ -807,13 +798,9 @@ pub mod tests { fn test_allowed_but_disabled() { figment::Jail::expect_with(|jail| { jail.clear_env(); - jail.set_env( - "DD_OTLP_CONFIG_RECEIVER_PROTOCOLS_GRPC_ENDPOINT", - "localhost:4138", - ); + jail.set_env("DD_SERVERLESS_APPSEC_ENABLED", "true"); - let config = get_config(Path::new("")).expect_err("should reject unknown fields"); - assert_eq!(config, ConfigError::UnsupportedField("otel".to_string())); + let _config = get_config(Path::new("")); Ok(()) }); } @@ -829,7 +816,7 @@ pub mod tests { ", )?; jail.set_env("DD_SITE", "datad0g.com"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.site, "datad0g.com"); Ok(()) }); @@ -845,7 +832,7 @@ pub mod tests { r" ", )?; - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.site, "datadoghq.com"); Ok(()) }); @@ -856,7 +843,7 @@ pub mod tests { figment::Jail::expect_with(|jail| { jail.clear_env(); jail.set_env("DD_SITE", "datadoghq.eu"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.site, "datadoghq.eu"); Ok(()) }); @@ -867,7 +854,7 @@ pub mod tests { figment::Jail::expect_with(|jail| { jail.clear_env(); jail.set_env("DD_LOG_LEVEL", "TRACE"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.log_level, LogLevel::Trace); Ok(()) }); @@ -877,7 +864,7 @@ pub mod tests { fn test_parse_default() { figment::Jail::expect_with(|jail| { jail.clear_env(); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!( config, Config { @@ -901,7 +888,7 @@ pub mod tests { figment::Jail::expect_with(|jail| { jail.clear_env(); jail.set_env("DD_PROXY_HTTPS", "my-proxy:3128"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.proxy_https, Some("my-proxy:3128".to_string())); Ok(()) }); @@ -917,7 +904,7 @@ pub mod tests { "NO_PROXY", "127.0.0.1,localhost,172.16.0.0/12,us-east-1.amazonaws.com,datadoghq.eu", ); - let config = get_config(Path::new("")).expect("should parse noproxy"); + let config = get_config(Path::new("")); assert_eq!(config.proxy_https, None); Ok(()) }); @@ -935,7 +922,7 @@ pub mod tests { ", )?; - let config = get_config(Path::new("")).expect("should parse weird proxy config"); + let config = get_config(Path::new("")); assert_eq!(config.proxy_https, Some("my-proxy:3128".to_string())); Ok(()) }); @@ -955,7 +942,7 @@ pub mod tests { ", )?; - let config = get_config(Path::new("")).expect("should parse weird proxy config"); + let config = get_config(Path::new("")); assert_eq!(config.proxy_https, None); // Assertion to ensure config.site runs before proxy // because we chenck that noproxy contains the site @@ -969,7 +956,7 @@ pub mod tests { figment::Jail::expect_with(|jail| { jail.clear_env(); jail.set_env("DD_SERVERLESS_FLUSH_STRATEGY", "end"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.serverless_flush_strategy, FlushStrategy::End); Ok(()) }); @@ -980,7 +967,7 @@ pub mod tests { figment::Jail::expect_with(|jail| { jail.clear_env(); jail.set_env("DD_SERVERLESS_FLUSH_STRATEGY", "periodically,100000"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!( config.serverless_flush_strategy, FlushStrategy::Periodically(PeriodicStrategy { interval: 100_000 }) @@ -994,7 +981,7 @@ pub mod tests { figment::Jail::expect_with(|jail| { jail.clear_env(); jail.set_env("DD_SERVERLESS_FLUSH_STRATEGY", "invalid_strategy"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.serverless_flush_strategy, FlushStrategy::Default); Ok(()) }); @@ -1008,7 +995,7 @@ pub mod tests { "DD_SERVERLESS_FLUSH_STRATEGY", "periodically,invalid_interval", ); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.serverless_flush_strategy, FlushStrategy::Default); Ok(()) }); @@ -1021,7 +1008,7 @@ pub mod tests { jail.set_env("DD_VERSION", "123"); jail.set_env("DD_ENV", "123456890"); jail.set_env("DD_SERVICE", "123456"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.version.expect("failed to parse DD_VERSION"), "123"); assert_eq!(config.env.expect("failed to parse DD_ENV"), "123456890"); assert_eq!( @@ -1051,7 +1038,7 @@ pub mod tests { pattern: exclude-me-yaml ", )?; - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!( config.logs_config_processing_rules, Some(vec![ProcessingRule { @@ -1080,7 +1067,7 @@ pub mod tests { pattern: exclude ", )?; - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!( config.logs_config_processing_rules, Some(vec![ProcessingRule { @@ -1109,7 +1096,7 @@ pub mod tests { repl: 'REDACTED' ", )?; - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); let rule = parse_rules_from_string( r#"[ {"name": "*", "pattern": "foo", "repl": "REDACTED"} @@ -1140,7 +1127,7 @@ pub mod tests { repl: 'REDACTED-YAML' ", )?; - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); let rule = parse_rules_from_string( r#"[ {"name": "*", "pattern": "foo", "repl": "REDACTED-ENV"} @@ -1167,7 +1154,7 @@ pub mod tests { remove_paths_with_digits: true ", )?; - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert!(config.apm_config_obfuscation_http_remove_query_string,); assert!(config.apm_config_obfuscation_http_remove_paths_with_digits,); Ok(()) @@ -1182,7 +1169,7 @@ pub mod tests { "datadog,tracecontext,b3,b3multi", ); jail.set_env("DD_EXTENSION_VERSION", "next"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); let expected_styles = vec![ TracePropagationStyle::Datadog, @@ -1201,7 +1188,7 @@ pub mod tests { figment::Jail::expect_with(|jail| { jail.clear_env(); jail.set_env("DD_TRACE_PROPAGATION_STYLE_EXTRACT", "datadog"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!( config.trace_propagation_style, @@ -1226,8 +1213,7 @@ pub mod tests { "DD_APM_REPLACE_TAGS", r#"[{"name":"resource.name","pattern":"(.*)/(foo[:%].+)","repl":"$1/{foo}"}]"#, ); - let config = get_config(Path::new("")); - assert!(config.is_ok()); + let _config = get_config(Path::new("")); Ok(()) }); } @@ -1240,7 +1226,7 @@ pub mod tests { jail.set_env("DD_ENHANCED_METRICS", "1"); jail.set_env("DD_LOGS_CONFIG_USE_COMPRESSION", "TRUE"); jail.set_env("DD_CAPTURE_LAMBDA_PAYLOAD", "0"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert!(config.serverless_logs_enabled); assert!(config.enhanced_metrics); assert!(config.logs_config_use_compression); @@ -1264,7 +1250,7 @@ pub mod tests { jail.set_env("DD_SITE", "us5.datadoghq.com"); jail.set_env("DD_API_KEY", "env-api-key"); jail.set_env("DD_FLUSH_TIMEOUT", "10"); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert_eq!(config.site, "us5.datadoghq.com"); assert_eq!(config.api_key, "env-api-key"); diff --git a/bottlecap/src/metrics/enhanced/constants.rs b/bottlecap/src/metrics/enhanced/constants.rs index 44666910f..110c0b618 100644 --- a/bottlecap/src/metrics/enhanced/constants.rs +++ b/bottlecap/src/metrics/enhanced/constants.rs @@ -45,6 +45,8 @@ pub const THREADS_USE_METRIC: &str = "aws.lambda.enhanced.threads_use"; pub const SHUTDOWNS_METRIC: &str = "aws.lambda.enhanced.shutdowns"; //pub const ASM_INVOCATIONS_METRIC: &str = "aws.lambda.enhanced.asm.invocations"; pub const UNUSED_INIT: &str = "aws.lambda.enhanced.unused_init"; +pub const DATADOG_SERVERLESS_EXTENSION_FAILOVER_CONFIG_ISSUE_METRIC: &str = + "datadog.serverless.extension.failover"; pub const ENHANCED_METRICS_ENV_VAR: &str = "DD_ENHANCED_METRICS"; // Monitoring interval for tmp, fd, and threads metrics diff --git a/bottlecap/src/metrics/enhanced/lambda.rs b/bottlecap/src/metrics/enhanced/lambda.rs index 34879ce87..639fd65e0 100644 --- a/bottlecap/src/metrics/enhanced/lambda.rs +++ b/bottlecap/src/metrics/enhanced/lambda.rs @@ -60,18 +60,29 @@ impl Lambda { .insert(String::from("runtime"), runtime.to_string()); } - fn get_dynamic_value_tags(&self) -> Option { - let vec_tags: Vec = self - .dynamic_value_tags - .iter() - .map(|(k, v)| format!("{k}:{v}")) - .collect(); + fn tags_to_sorted_tags(tags: &HashMap) -> Option { + let vec_tags: Vec = tags.iter().map(|(k, v)| format!("{k}:{v}")).collect(); let string_tags = vec_tags.join(","); SortedTags::parse(&string_tags).ok() } + fn get_dynamic_value_tags(&self) -> Option { + Self::tags_to_sorted_tags(&self.dynamic_value_tags) + } + + fn get_combined_tags(&self, additional_tags: &HashMap) -> Option { + if additional_tags.is_empty() { + return self.get_dynamic_value_tags(); + } + + let mut combined_tags = self.dynamic_value_tags.clone(); + combined_tags.extend(additional_tags.clone()); + + Self::tags_to_sorted_tags(&combined_tags) + } + pub fn increment_invocation_metric(&self, timestamp: i64) { self.increment_metric(constants::INVOCATIONS_METRIC, timestamp); } @@ -94,6 +105,19 @@ impl Lambda { self.increment_metric(constants::OUT_OF_MEMORY_METRIC, timestamp); } + /// Set up a metric tracking configuration load issue with details + pub fn set_config_load_issue_metric(&self, timestamp: i64, reason_msg: &str) { + let dynamic_tags = self.get_combined_tags(&HashMap::from([( + "reason".to_string(), + reason_msg.to_string(), + )])); + self.increment_metric_with_tags( + constants::DATADOG_SERVERLESS_EXTENSION_FAILOVER_CONFIG_ISSUE_METRIC, + timestamp, + dynamic_tags, + ); + } + pub fn set_init_duration_metric( &mut self, init_type: InitType, @@ -127,13 +151,23 @@ impl Lambda { } fn increment_metric(&self, metric_name: &str, timestamp: i64) { + self.increment_metric_with_tags(metric_name, timestamp, self.get_dynamic_value_tags()); + } + + /// Helper function to emit metric with supplied tags + fn increment_metric_with_tags( + &self, + metric_name: &str, + timestamp: i64, + tags: Option, + ) { if !self.config.enhanced_metrics { return; } let metric = Metric::new( metric_name.into(), MetricValue::distribution(1f64), - self.get_dynamic_value_tags(), + tags, Some(timestamp), ); if let Err(e) = self @@ -817,10 +851,20 @@ mod tests { metric_id: &str, value: f64, timestamp: i64, + ) { + assert_sketch_with_tag(aggregator_mutex, metric_id, value, timestamp, None); + } + + fn assert_sketch_with_tag( + aggregator_mutex: &Mutex, + metric_id: &str, + value: f64, + timestamp: i64, + tags: Option, ) { let ts = (timestamp / 10) * 10; let aggregator = aggregator_mutex.lock().unwrap(); - if let Some(e) = aggregator.get_entry_by_id(metric_id.into(), &None, ts) { + if let Some(e) = aggregator.get_entry_by_id(metric_id.into(), &tags, ts) { let metric = e.value.get_sketch().unwrap(); assert!((metric.max().unwrap() - value).abs() < PRECISION); assert!((metric.min().unwrap() - value).abs() < PRECISION); @@ -1329,4 +1373,29 @@ mod tests { .is_none() ); } + + #[test] + fn test_set_config_load_issue_metric() { + let (metrics_aggr, my_config) = setup(); + let lambda = Lambda::new(metrics_aggr.clone(), my_config); + let now: i64 = std::time::UNIX_EPOCH + .elapsed() + .expect("unable to poll clock, unrecoverable") + .as_secs() + .try_into() + .unwrap_or_default(); + let test_reason = "test_config_issue"; + + lambda.set_config_load_issue_metric(now, test_reason); + + // Create the expected tags for the metric lookup + let expected_tags = SortedTags::parse(&format!("reason:{test_reason}")).ok(); + assert_sketch_with_tag( + &metrics_aggr, + constants::DATADOG_SERVERLESS_EXTENSION_FAILOVER_CONFIG_ISSUE_METRIC, + 1f64, + now, + expected_tags, + ); + } } diff --git a/bottlecap/src/otlp/mod.rs b/bottlecap/src/otlp/mod.rs index 292a80180..4e0f4aed7 100644 --- a/bottlecap/src/otlp/mod.rs +++ b/bottlecap/src/otlp/mod.rs @@ -37,7 +37,7 @@ mod tests { ", )?; - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); // Since the default for traces is `true`, we don't need to set it. assert!(should_enable_otlp_agent(&Arc::new(config))); @@ -55,7 +55,7 @@ mod tests { "0.0.0.0:4318", ); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); // Since the default for traces is `true`, we don't need to set it. assert!(should_enable_otlp_agent(&Arc::new(config))); @@ -74,7 +74,7 @@ mod tests { "0.0.0.0:4318", ); - let config = get_config(Path::new("")).expect("should parse config"); + let config = get_config(Path::new("")); assert!(!should_enable_otlp_agent(&Arc::new(config)));