relay_base_schema/metrics/mod.rs
1//! Type definitions for Sentry metrics.
2
3mod mri;
4mod name;
5mod units;
6
7pub use self::mri::*;
8pub use self::name::*;
9pub use self::units::*;
10
11use regex::Regex;
12use std::{borrow::Cow, sync::OnceLock};
13
14/// The limit is determined by the unit size (15) and the maximum MRI length (200).
15/// By choosing a metric name length of 150, we ensure that 35 characters remain available
16/// for the MRI divider characters, metric type, and namespace, which is a reasonable allocation.
17const CUSTOM_METRIC_NAME_MAX_SIZE: usize = 150;
18
19/// Validates a metric name and normalizes it. This is the statsd name, i.e. without type or unit.
20///
21/// Metric names cannot be empty, must begin with a letter and can consist of ASCII alphanumerics,
22/// underscores, dashes, and periods. The implementation will further replace dashes with
23/// underscores.
24///
25/// The function validates that the first character of the metric must be ASCII alphanumeric, later
26/// consecutive invalid characters in the name will be replaced with underscores.
27pub fn try_normalize_metric_name(name: &str) -> Option<Cow<'_, str>> {
28 static NORMALIZE_RE: OnceLock<Regex> = OnceLock::new();
29
30 if !can_be_valid_metric_name(name) {
31 return None;
32 }
33
34 // Note: `-` intentionally missing from this list.
35 let normalize_re = NORMALIZE_RE.get_or_init(|| Regex::new("[^a-zA-Z0-9_.]+").unwrap());
36 let normalized_name = normalize_re.replace_all(name, "_");
37
38 if normalized_name.len() <= CUSTOM_METRIC_NAME_MAX_SIZE {
39 return Some(normalized_name);
40 }
41
42 // We limit the string to a fixed size.
43 //
44 // Here we are taking slices, assuming that we have a single character per index since we are
45 // normalizing the name above.
46 //
47 // If we allow characters that take more than 1 byte per character, we will need to change this
48 // function. Otherwise, it will panic because the index might cut a character in half.
49 Some(match normalized_name {
50 Cow::Borrowed(value) => Cow::Borrowed(&value[..CUSTOM_METRIC_NAME_MAX_SIZE]),
51 Cow::Owned(mut value) => {
52 value.truncate(CUSTOM_METRIC_NAME_MAX_SIZE);
53 Cow::Owned(value)
54 }
55 })
56}
57
58/// Returns whether [`try_normalize_metric_name`] can normalize the passed name.
59pub fn can_be_valid_metric_name(name: &str) -> bool {
60 name.starts_with(|c: char| c.is_ascii_alphabetic())
61}