metrics_util/layers/
prefix.rs

1use crate::layers::Layer;
2use metrics::{Counter, Gauge, Histogram, Key, KeyName, Metadata, Recorder, SharedString, Unit};
3
4/// Applies a prefix to every metric key.
5///
6/// Keys will be prefixed in the format of `<prefix>.<remaining>`.
7pub struct Prefix<R> {
8    prefix: SharedString,
9    inner: R,
10}
11
12impl<R> Prefix<R> {
13    fn prefix_key(&self, key: &Key) -> Key {
14        let mut new_name = String::with_capacity(self.prefix.len() + 1 + key.name().len());
15        new_name.push_str(self.prefix.as_ref());
16        new_name.push('.');
17        new_name.push_str(key.name());
18
19        Key::from_parts(new_name, key.labels())
20    }
21
22    fn prefix_key_name(&self, key_name: KeyName) -> KeyName {
23        let mut new_name = String::with_capacity(self.prefix.len() + 1 + key_name.as_str().len());
24        new_name.push_str(self.prefix.as_ref());
25        new_name.push('.');
26        new_name.push_str(key_name.as_str());
27
28        KeyName::from(new_name)
29    }
30}
31
32impl<R: Recorder> Recorder for Prefix<R> {
33    fn describe_counter(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
34        let new_key_name = self.prefix_key_name(key_name);
35        self.inner.describe_counter(new_key_name, unit, description)
36    }
37
38    fn describe_gauge(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
39        let new_key_name = self.prefix_key_name(key_name);
40        self.inner.describe_gauge(new_key_name, unit, description)
41    }
42
43    fn describe_histogram(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
44        let new_key_name = self.prefix_key_name(key_name);
45        self.inner.describe_histogram(new_key_name, unit, description)
46    }
47
48    fn register_counter(&self, key: &Key, metadata: &Metadata<'_>) -> Counter {
49        let new_key = self.prefix_key(key);
50        self.inner.register_counter(&new_key, metadata)
51    }
52
53    fn register_gauge(&self, key: &Key, metadata: &Metadata<'_>) -> Gauge {
54        let new_key = self.prefix_key(key);
55        self.inner.register_gauge(&new_key, metadata)
56    }
57
58    fn register_histogram(&self, key: &Key, metadata: &Metadata<'_>) -> Histogram {
59        let new_key = self.prefix_key(key);
60        self.inner.register_histogram(&new_key, metadata)
61    }
62}
63
64/// A layer for applying a prefix to every metric key.
65///
66/// More information on the behavior of the layer can be found in [`Prefix`].
67pub struct PrefixLayer(&'static str);
68
69impl PrefixLayer {
70    /// Creates a new `PrefixLayer` based on the given prefix.
71    pub fn new<S: Into<String>>(prefix: S) -> PrefixLayer {
72        PrefixLayer(Box::leak(prefix.into().into_boxed_str()))
73    }
74}
75
76impl<R> Layer<R> for PrefixLayer {
77    type Output = Prefix<R>;
78
79    fn layer(&self, inner: R) -> Self::Output {
80        Prefix { prefix: self.0.into(), inner }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::{Prefix, PrefixLayer};
87    use crate::layers::Layer;
88    use crate::test_util::*;
89    use metrics::{Counter, Gauge, Histogram, Key, KeyName, Unit};
90
91    static METADATA: metrics::Metadata =
92        metrics::Metadata::new(module_path!(), metrics::Level::INFO, Some(module_path!()));
93
94    #[test]
95    fn test_basic_functionality() {
96        let inputs = vec![
97            RecorderOperation::DescribeCounter(
98                "counter_key".into(),
99                Some(Unit::Count),
100                "counter desc".into(),
101            ),
102            RecorderOperation::DescribeGauge(
103                "gauge_key".into(),
104                Some(Unit::Bytes),
105                "gauge desc".into(),
106            ),
107            RecorderOperation::DescribeHistogram(
108                "histogram_key".into(),
109                Some(Unit::Nanoseconds),
110                "histogram desc".into(),
111            ),
112            RecorderOperation::RegisterCounter("counter_key".into(), Counter::noop(), &METADATA),
113            RecorderOperation::RegisterGauge("gauge_key".into(), Gauge::noop(), &METADATA),
114            RecorderOperation::RegisterHistogram(
115                "histogram_key".into(),
116                Histogram::noop(),
117                &METADATA,
118            ),
119        ];
120
121        let expectations = vec![
122            RecorderOperation::DescribeCounter(
123                "testing.counter_key".into(),
124                Some(Unit::Count),
125                "counter desc".into(),
126            ),
127            RecorderOperation::DescribeGauge(
128                "testing.gauge_key".into(),
129                Some(Unit::Bytes),
130                "gauge desc".into(),
131            ),
132            RecorderOperation::DescribeHistogram(
133                "testing.histogram_key".into(),
134                Some(Unit::Nanoseconds),
135                "histogram desc".into(),
136            ),
137            RecorderOperation::RegisterCounter(
138                "testing.counter_key".into(),
139                Counter::noop(),
140                &METADATA,
141            ),
142            RecorderOperation::RegisterGauge("testing.gauge_key".into(), Gauge::noop(), &METADATA),
143            RecorderOperation::RegisterHistogram(
144                "testing.histogram_key".into(),
145                Histogram::noop(),
146                &METADATA,
147            ),
148        ];
149
150        let recorder = MockBasicRecorder::from_operations(expectations);
151        let prefix = PrefixLayer::new("testing");
152        let prefix = prefix.layer(recorder);
153
154        for operation in inputs {
155            operation.apply_to_recorder(&prefix);
156        }
157    }
158
159    #[test]
160    fn test_key_vs_key_name() {
161        let prefix = Prefix { prefix: "foobar".into(), inner: () };
162
163        let key_name = KeyName::from("my_key");
164        let key = Key::from_name(key_name.clone());
165
166        let prefixed_key = prefix.prefix_key(&key);
167        let prefixed_key_name = prefix.prefix_key_name(key_name);
168
169        assert_eq!(
170            prefixed_key.name(),
171            prefixed_key_name.as_str(),
172            "prefixed key and prefixed key name should match"
173        );
174    }
175}