Skip to content

Commit 98dcde1

Browse files
authored
[SLES-2547] add metric namespace for DogStatsD (#920)
Follow up from DataDog/serverless-components#48 What does this PR do? Add support for DD_STATSD_METRIC_NAMESPACE. Motivation This was brought up by a customer, they noticed issues migrating to bottlecap. Our docs show we should support this, but we currently don't have it implemented - https://docs.datadoghq.com/serverless/guide/agent_configuration/#dogstatsd-custom-metrics. Additional Notes Requires changes in agent/extension. Will follow up with those PRs. Describe how to test/QA your changes Deployed changes to extension and tested with / without the custom namespace env variable. Confirmed that metrics are getting the prefix attached, [metrics](https://ddserverless.datadoghq.com/metric/explorer?fromUser=false&graph_layout=stacked&start=1762783238873&end=1762784138873&paused=false#N4Ig7glgJg5gpgFxALlAGwIYE8D2BXJVEADxQEYAaELcqyKBAC1pEbghkcLIF8qo4AMwgA7CAgg4RKUAiwAHOChASAtnADOcAE4RNIKtrgBHPJoQaUAbVBGN8qVoD6gnNtUZCKiOq279VKY6epbINiAiGOrKQdpYZAYgUJ4YThr42gDGSsgg6gi6mZaBZnHKGABuMMiZeBoIOKoAdPJYTFJNcMRwtRIdmfgiCMAAVDwgfKCR0bmxWABMickIqel4WTl5iIXFIHPlVcgAVjiMIk3TmvIY2U219Y0tbYwdXT0EkucDeEOj4zwAXSornceEwoXCINUYIwMVK8QmFFAUJhcJ0CwmQJA9SwaByoGueIQCE2UBwMCcmXBGggmUSaFEcCcckUynSDKg9MZTnoTGUIjcHjQiKSEHsmCwzIUmwZIiUgJ4fGx8gZCAAwlJhDAUCIwWgeEA)
1 parent 0a5e11e commit 98dcde1

File tree

6 files changed

+106
-26
lines changed

6 files changed

+106
-26
lines changed

bottlecap/Cargo.lock

Lines changed: 8 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bottlecap/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ datadog-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "ba
6363
datadog-trace-normalization = { git = "https://github.com/DataDog/libdatadog", rev = "ba8955394cf35cf24a1a508fbe6264ad84702567" }
6464
datadog-trace-obfuscation = { git = "https://github.com/DataDog/libdatadog", rev = "ba8955394cf35cf24a1a508fbe6264ad84702567" }
6565
datadog-trace-stats = { git = "https://github.com/DataDog/libdatadog", rev = "ba8955394cf35cf24a1a508fbe6264ad84702567" }
66-
dogstatsd = { git = "https://github.com/DataDog/serverless-components", rev = "05b9dee1ac7c72d296ea12e85685c026cb6dbaaf", default-features = false }
67-
datadog-fips = { git = "https://github.com/DataDog/serverless-components", rev = "05b9dee1ac7c72d296ea12e85685c026cb6dbaaf", default-features = false }
66+
dogstatsd = { git = "https://github.com/DataDog/serverless-components", rev = "aa7961984f221ec3f1048ebf3379c4b94a33be0e", default-features = false }
67+
datadog-fips = { git = "https://github.com/DataDog/serverless-components", rev = "aa7961984f221ec3f1048ebf3379c4b94a33be0e", default-features = false }
6868
libddwaf = { version = "1.28.1", git = "https://github.com/DataDog/libddwaf-rust", rev = "d1534a158d976bd4f747bf9fcc58e0712d2d17fc", default-features = false, features = ["serde"] }
6969

7070
[dev-dependencies]

bottlecap/src/bin/bottlecap/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,7 @@ async fn start_dogstatsd(
11641164
let dogstatsd_config = DogStatsDConfig {
11651165
host: EXTENSION_HOST.to_string(),
11661166
port: DOGSTATSD_PORT,
1167+
metric_namespace: config.statsd_metric_namespace.clone(),
11671168
};
11681169
let cancel_token = tokio_util::sync::CancellationToken::new();
11691170
let dogstatsd_agent = DogStatsD::new(

bottlecap/src/config/env.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,11 @@ pub struct EnvConfig {
266266
#[serde(deserialize_with = "deserialize_option_lossless")]
267267
pub metrics_config_compression_level: Option<i32>,
268268

269+
/// @env `DD_STATSD_METRIC_NAMESPACE`
270+
/// Prefix all `StatsD` metrics with a namespace.
271+
#[serde(deserialize_with = "deserialize_optional_string")]
272+
pub statsd_metric_namespace: Option<String>,
273+
269274
// OTLP
270275
//
271276
// - APM / Traces
@@ -521,6 +526,10 @@ fn merge_config(config: &mut Config, env_config: &EnvConfig) {
521526
);
522527
merge_option_to_value!(config, env_config, metrics_config_compression_level);
523528

529+
if let Some(namespace) = &env_config.statsd_metric_namespace {
530+
config.statsd_metric_namespace = super::validate_metric_namespace(namespace);
531+
}
532+
524533
// OTLP
525534
merge_option_to_value!(config, env_config, otlp_config_traces_enabled);
526535
merge_option_to_value!(
@@ -938,6 +947,7 @@ mod tests {
938947
otlp_config_metrics_summaries_mode: Some("quantiles".to_string()),
939948
otlp_config_traces_probabilistic_sampler_sampling_percentage: Some(50),
940949
otlp_config_logs_enabled: true,
950+
statsd_metric_namespace: None,
941951
api_key_secret_arn: "arn:aws:secretsmanager:region:account:secret:datadog-api-key"
942952
.to_string(),
943953
kms_api_key: "test-kms-key".to_string(),

bottlecap/src/config/mod.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ pub struct Config {
298298

299299
// Metrics
300300
pub metrics_config_compression_level: i32,
301+
pub statsd_metric_namespace: Option<String>,
301302

302303
// OTLP
303304
//
@@ -411,6 +412,7 @@ impl Default for Config {
411412

412413
// Metrics
413414
metrics_config_compression_level: 3,
415+
statsd_metric_namespace: None,
414416

415417
// OTLP
416418
otlp_config_traces_enabled: true,
@@ -699,6 +701,39 @@ where
699701
}
700702
}
701703

704+
fn validate_metric_namespace(namespace: &str) -> Option<String> {
705+
let trimmed = namespace.trim();
706+
if trimmed.is_empty() {
707+
return None;
708+
}
709+
710+
let mut chars = trimmed.chars();
711+
712+
if let Some(first_char) = chars.next() {
713+
if !first_char.is_ascii_alphabetic() {
714+
error!(
715+
"DD_STATSD_METRIC_NAMESPACE must start with a letter, got: '{}'. Ignoring namespace.",
716+
trimmed
717+
);
718+
return None;
719+
}
720+
} else {
721+
return None;
722+
}
723+
724+
if let Some(invalid_char) =
725+
chars.find(|&ch| !ch.is_ascii_alphanumeric() && ch != '_' && ch != '.')
726+
{
727+
error!(
728+
"DD_STATSD_METRIC_NAMESPACE contains invalid character '{}' in '{}'. Only ASCII alphanumerics, underscores, and periods are allowed. Ignoring namespace.",
729+
invalid_char, trimmed
730+
);
731+
return None;
732+
}
733+
734+
Some(trimmed.to_string())
735+
}
736+
702737
pub fn deserialize_option_lossless<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
703738
where
704739
D: Deserializer<'de>,
@@ -1476,4 +1511,53 @@ pub mod tests {
14761511
expected.insert("valid".to_string(), "tag".to_string());
14771512
assert_eq!(result.tags, expected);
14781513
}
1514+
1515+
#[test]
1516+
fn test_validate_metric_namespace_valid() {
1517+
assert_eq!(
1518+
validate_metric_namespace("myapp"),
1519+
Some("myapp".to_string())
1520+
);
1521+
assert_eq!(
1522+
validate_metric_namespace("my_app"),
1523+
Some("my_app".to_string())
1524+
);
1525+
assert_eq!(
1526+
validate_metric_namespace("my.app"),
1527+
Some("my.app".to_string())
1528+
);
1529+
assert_eq!(
1530+
validate_metric_namespace("MyApp123"),
1531+
Some("MyApp123".to_string())
1532+
);
1533+
assert_eq!(
1534+
validate_metric_namespace(" myapp "),
1535+
Some("myapp".to_string())
1536+
);
1537+
}
1538+
1539+
#[test]
1540+
fn test_validate_metric_namespace_empty() {
1541+
assert_eq!(validate_metric_namespace(""), None);
1542+
assert_eq!(validate_metric_namespace(" "), None);
1543+
assert_eq!(validate_metric_namespace("\t\n"), None);
1544+
}
1545+
1546+
#[test]
1547+
fn test_validate_metric_namespace_invalid_first_char() {
1548+
assert_eq!(validate_metric_namespace("1myapp"), None);
1549+
assert_eq!(validate_metric_namespace("_myapp"), None);
1550+
assert_eq!(validate_metric_namespace(".myapp"), None);
1551+
assert_eq!(validate_metric_namespace("-myapp"), None);
1552+
}
1553+
1554+
#[test]
1555+
fn test_validate_metric_namespace_invalid_chars() {
1556+
assert_eq!(validate_metric_namespace("my-app"), None);
1557+
assert_eq!(validate_metric_namespace("my app"), None);
1558+
assert_eq!(validate_metric_namespace("my@app"), None);
1559+
assert_eq!(validate_metric_namespace("my#app"), None);
1560+
assert_eq!(validate_metric_namespace("my$app"), None);
1561+
assert_eq!(validate_metric_namespace("my!app"), None);
1562+
}
14791563
}

bottlecap/src/config/yaml.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,7 @@ api_security_sample_delay: 60 # Seconds
993993
apm_filter_tags_reject: None,
994994
apm_filter_tags_regex_require: None,
995995
apm_filter_tags_regex_reject: None,
996+
statsd_metric_namespace: None,
996997
};
997998

998999
// Assert that

0 commit comments

Comments
 (0)