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