1use crate::layers::Layer;
2use aho_corasick::{AhoCorasick, AhoCorasickBuilder, AhoCorasickKind};
3use metrics::{Counter, Gauge, Histogram, Key, KeyName, Metadata, Recorder, SharedString, Unit};
4
5pub struct Filter<R> {
9 inner: R,
10 automaton: AhoCorasick,
11}
12
13impl<R> Filter<R> {
14 fn should_filter(&self, key: &str) -> bool {
15 self.automaton.is_match(key)
16 }
17}
18
19impl<R: Recorder> Recorder for Filter<R> {
20 fn describe_counter(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
21 if self.should_filter(key_name.as_str()) {
22 return;
23 }
24 self.inner.describe_counter(key_name, unit, description)
25 }
26
27 fn describe_gauge(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
28 if self.should_filter(key_name.as_str()) {
29 return;
30 }
31 self.inner.describe_gauge(key_name, unit, description)
32 }
33
34 fn describe_histogram(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
35 if self.should_filter(key_name.as_str()) {
36 return;
37 }
38 self.inner.describe_histogram(key_name, unit, description)
39 }
40
41 fn register_counter(&self, key: &Key, metadata: &Metadata<'_>) -> Counter {
42 if self.should_filter(key.name()) {
43 return Counter::noop();
44 }
45 self.inner.register_counter(key, metadata)
46 }
47
48 fn register_gauge(&self, key: &Key, metadata: &Metadata<'_>) -> Gauge {
49 if self.should_filter(key.name()) {
50 return Gauge::noop();
51 }
52 self.inner.register_gauge(key, metadata)
53 }
54
55 fn register_histogram(&self, key: &Key, metadata: &Metadata<'_>) -> Histogram {
56 if self.should_filter(key.name()) {
57 return Histogram::noop();
58 }
59 self.inner.register_histogram(key, metadata)
60 }
61}
62
63#[derive(Default)]
77pub struct FilterLayer {
78 patterns: Vec<String>,
79 case_insensitive: bool,
80 use_dfa: bool,
81}
82
83impl FilterLayer {
84 pub fn from_patterns<P, I>(patterns: P) -> Self
86 where
87 P: IntoIterator<Item = I>,
88 I: AsRef<str>,
89 {
90 FilterLayer {
91 patterns: patterns.into_iter().map(|s| s.as_ref().to_string()).collect(),
92 case_insensitive: false,
93 use_dfa: true,
94 }
95 }
96
97 pub fn add_pattern<P>(&mut self, pattern: P) -> &mut FilterLayer
99 where
100 P: AsRef<str>,
101 {
102 self.patterns.push(pattern.as_ref().to_string());
103 self
104 }
105
106 pub fn case_insensitive(&mut self, case_insensitive: bool) -> &mut FilterLayer {
110 self.case_insensitive = case_insensitive;
111 self
112 }
113
114 pub fn use_dfa(&mut self, dfa: bool) -> &mut FilterLayer {
131 self.use_dfa = dfa;
132 self
133 }
134}
135
136impl<R> Layer<R> for FilterLayer {
137 type Output = Filter<R>;
138
139 fn layer(&self, inner: R) -> Self::Output {
140 let mut automaton_builder = AhoCorasickBuilder::new();
141 let automaton = automaton_builder
142 .ascii_case_insensitive(self.case_insensitive)
143 .kind(self.use_dfa.then_some(AhoCorasickKind::DFA))
144 .build(&self.patterns)
145 .expect("should not fail to build filter automaton");
150 Filter { inner, automaton }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::FilterLayer;
157 use crate::{layers::Layer, test_util::*};
158 use metrics::{Counter, Gauge, Histogram, Unit};
159
160 static METADATA: metrics::Metadata =
161 metrics::Metadata::new(module_path!(), metrics::Level::INFO, Some(module_path!()));
162
163 #[test]
164 fn test_basic_functionality() {
165 let inputs = vec![
166 RecorderOperation::DescribeCounter(
167 "tokio.loops".into(),
168 Some(Unit::Count),
169 "counter desc".into(),
170 ),
171 RecorderOperation::DescribeGauge(
172 "hyper.bytes_read".into(),
173 Some(Unit::Bytes),
174 "gauge desc".into(),
175 ),
176 RecorderOperation::DescribeHistogram(
177 "hyper.response_latency".into(),
178 Some(Unit::Nanoseconds),
179 "histogram desc".into(),
180 ),
181 RecorderOperation::DescribeCounter(
182 "tokio.spurious_wakeups".into(),
183 Some(Unit::Count),
184 "counter desc".into(),
185 ),
186 RecorderOperation::DescribeGauge(
187 "bb8.pooled_conns".into(),
188 Some(Unit::Count),
189 "gauge desc".into(),
190 ),
191 RecorderOperation::RegisterCounter("tokio.loops".into(), Counter::noop(), &METADATA),
192 RecorderOperation::RegisterGauge("hyper.bytes_read".into(), Gauge::noop(), &METADATA),
193 RecorderOperation::RegisterHistogram(
194 "hyper.response_latency".into(),
195 Histogram::noop(),
196 &METADATA,
197 ),
198 RecorderOperation::RegisterCounter(
199 "tokio.spurious_wakeups".into(),
200 Counter::noop(),
201 &METADATA,
202 ),
203 RecorderOperation::RegisterGauge("bb8.pooled_conns".into(), Gauge::noop(), &METADATA),
204 ];
205
206 let expectations = vec![
207 RecorderOperation::DescribeGauge(
208 "hyper.bytes_read".into(),
209 Some(Unit::Bytes),
210 "gauge desc".into(),
211 ),
212 RecorderOperation::DescribeHistogram(
213 "hyper.response_latency".into(),
214 Some(Unit::Nanoseconds),
215 "histogram desc".into(),
216 ),
217 RecorderOperation::RegisterGauge("hyper.bytes_read".into(), Gauge::noop(), &METADATA),
218 RecorderOperation::RegisterHistogram(
219 "hyper.response_latency".into(),
220 Histogram::noop(),
221 &METADATA,
222 ),
223 ];
224
225 let recorder = MockBasicRecorder::from_operations(expectations);
226 let filter = FilterLayer::from_patterns(&["tokio", "bb8"]);
227 let filter = filter.layer(recorder);
228
229 for operation in inputs {
230 operation.apply_to_recorder(&filter);
231 }
232 }
233
234 #[test]
235 fn test_case_insensitivity() {
236 let inputs = vec![
237 RecorderOperation::DescribeCounter(
238 "tokiO.loops".into(),
239 Some(Unit::Count),
240 "counter desc".into(),
241 ),
242 RecorderOperation::DescribeGauge(
243 "hyper.bytes_read".into(),
244 Some(Unit::Bytes),
245 "gauge desc".into(),
246 ),
247 RecorderOperation::DescribeHistogram(
248 "hyper.response_latency".into(),
249 Some(Unit::Nanoseconds),
250 "histogram desc".into(),
251 ),
252 RecorderOperation::DescribeCounter(
253 "Tokio.spurious_wakeups".into(),
254 Some(Unit::Count),
255 "counter desc".into(),
256 ),
257 RecorderOperation::DescribeGauge(
258 "bB8.pooled_conns".into(),
259 Some(Unit::Count),
260 "gauge desc".into(),
261 ),
262 RecorderOperation::RegisterCounter("tokiO.loops".into(), Counter::noop(), &METADATA),
263 RecorderOperation::RegisterGauge("hyper.bytes_read".into(), Gauge::noop(), &METADATA),
264 RecorderOperation::RegisterHistogram(
265 "hyper.response_latency".into(),
266 Histogram::noop(),
267 &METADATA,
268 ),
269 RecorderOperation::RegisterCounter(
270 "Tokio.spurious_wakeups".into(),
271 Counter::noop(),
272 &METADATA,
273 ),
274 RecorderOperation::RegisterGauge("bB8.pooled_conns".into(), Gauge::noop(), &METADATA),
275 ];
276
277 let expectations = vec![
278 RecorderOperation::DescribeGauge(
279 "hyper.bytes_read".into(),
280 Some(Unit::Bytes),
281 "gauge desc".into(),
282 ),
283 RecorderOperation::DescribeHistogram(
284 "hyper.response_latency".into(),
285 Some(Unit::Nanoseconds),
286 "histogram desc".into(),
287 ),
288 RecorderOperation::RegisterGauge("hyper.bytes_read".into(), Gauge::noop(), &METADATA),
289 RecorderOperation::RegisterHistogram(
290 "hyper.response_latency".into(),
291 Histogram::noop(),
292 &METADATA,
293 ),
294 ];
295
296 let recorder = MockBasicRecorder::from_operations(expectations);
297 let mut filter = FilterLayer::from_patterns(&["tokio", "bb8"]);
298 let filter = filter.case_insensitive(true).layer(recorder);
299
300 for operation in inputs {
301 operation.apply_to_recorder(&filter);
302 }
303 }
304}