diff --git a/metrics-exporter-prometheus/src/builder.rs b/metrics-exporter-prometheus/src/builder.rs index 78f37010..46638aab 100644 --- a/metrics-exporter-prometheus/src/builder.rs +++ b/metrics-exporter-prometheus/src/builder.rs @@ -118,7 +118,7 @@ impl PrometheusBuilder { /// It only affects matching metrics if set_buckets was not used. pub fn set_buckets_for_metric(mut self, matcher: Matcher, values: &[f64]) -> Self { let buckets = self.bucket_overrides.get_or_insert_with(HashMap::new); - buckets.insert(matcher, values.to_vec()); + buckets.insert(matcher.sanitized(), values.to_vec()); self } @@ -349,27 +349,27 @@ mod tests { let recorder = PrometheusBuilder::new() .set_buckets_for_metric( - Matcher::Full("metrics_testing_foo".to_owned()), + Matcher::Full("metrics.testing foo".to_owned()), &FULL_VALUES[..], ) .set_buckets_for_metric( - Matcher::Prefix("metrics_testing".to_owned()), + Matcher::Prefix("metrics.testing".to_owned()), &PREFIX_VALUES[..], ) .set_buckets_for_metric(Matcher::Suffix("foo".to_owned()), &SUFFIX_VALUES[..]) .set_buckets(&DEFAULT_VALUES[..]) .build(); - let full_key = Key::from_name("metrics_testing_foo"); + let full_key = Key::from_name("metrics.testing_foo"); recorder.record_histogram(&full_key, FULL_VALUES[0]); - let prefix_key = Key::from_name("metrics_testing_bar"); + let prefix_key = Key::from_name("metrics.testing_bar"); recorder.record_histogram(&prefix_key, PREFIX_VALUES[1]); let suffix_key = Key::from_name("metrics_testin_foo"); recorder.record_histogram(&suffix_key, SUFFIX_VALUES[2]); - let default_key = Key::from_name("metrics_wee"); + let default_key = Key::from_name("metrics.wee"); recorder.record_histogram(&default_key, DEFAULT_VALUES[2] + 1.0); let full_data = concat!( diff --git a/metrics-exporter-prometheus/src/common.rs b/metrics-exporter-prometheus/src/common.rs index b1ec6e63..81f31304 100644 --- a/metrics-exporter-prometheus/src/common.rs +++ b/metrics-exporter-prometheus/src/common.rs @@ -29,6 +29,15 @@ impl Matcher { Matcher::Full(full) => key == full, } } + + /// Creates a sanitized version of this matcher. + pub(crate) fn sanitized(self) -> Matcher { + match self { + Matcher::Prefix(prefix) => Matcher::Prefix(sanitize_key_name(prefix.as_str())), + Matcher::Suffix(suffix) => Matcher::Suffix(sanitize_key_name(suffix.as_str())), + Matcher::Full(full) => Matcher::Full(sanitize_key_name(full.as_str())), + } + } } /// Errors that could occur while installing a Prometheus recorder/exporter. @@ -53,3 +62,29 @@ pub struct Snapshot { pub gauges: HashMap, f64>>, pub distributions: HashMap, Distribution>>, } + +pub fn sanitize_key_name(key: &str) -> String { + // Replace anything that isn't [a-zA-Z0-9_:]. + let sanitize = |c: char| !(c.is_alphanumeric() || c == '_' || c == ':'); + key.to_string().replace(sanitize, "_") +} + +#[cfg(test)] +mod tests { + use super::sanitize_key_name; + + #[test] + fn test_sanitize_key_name() { + let test_cases = vec![ + ("____", "____"), + ("foo bar", "foo_bar"), + ("abcd:efgh", "abcd:efgh"), + ("lars.andersen", "lars_andersen"), + ]; + + for (input, expected) in test_cases { + let result = sanitize_key_name(input); + assert_eq!(expected, result); + } + } +} diff --git a/metrics-exporter-prometheus/src/recorder.rs b/metrics-exporter-prometheus/src/recorder.rs index 32c16ac6..923cc8ec 100644 --- a/metrics-exporter-prometheus/src/recorder.rs +++ b/metrics-exporter-prometheus/src/recorder.rs @@ -6,7 +6,7 @@ use parking_lot::RwLock; use metrics::{GaugeValue, Key, Recorder, Unit}; use metrics_util::{Handle, MetricKind, Recency, Registry}; -use crate::common::Snapshot; +use crate::common::{sanitize_key_name, Snapshot}; use crate::distribution::{Distribution, DistributionBuilder}; pub(crate) struct Inner { @@ -294,8 +294,7 @@ impl PrometheusHandle { } fn key_to_parts(key: &Key, defaults: &HashMap) -> (String, Vec) { - let sanitize = |c| c == '.' || c == '=' || c == '{' || c == '}' || c == '+' || c == '-'; - let name = key.name().to_string().replace(sanitize, "_"); + let name = sanitize_key_name(key.name()); let mut values = defaults.clone(); key.labels().into_iter().for_each(|label| { values.insert(label.key().into(), label.value().into());