1use relay_pattern::Patterns;
6
7use crate::{FilterStatKey, Filterable, IgnoreTransactionsFilterConfig};
8
9fn matches(transaction: Option<&str>, patterns: &Patterns) -> bool {
10 transaction.is_some_and(|transaction| patterns.is_match(transaction))
11}
12
13pub fn should_filter<F: Filterable>(
16 item: &F,
17 config: &IgnoreTransactionsFilterConfig,
18) -> Result<(), FilterStatKey> {
19 if config.is_empty() {
20 return Ok(());
21 }
22
23 if matches(item.transaction(), &config.patterns) {
24 return Err(FilterStatKey::FilteredTransactions);
25 }
26 Ok(())
27}
28
29#[cfg(test)]
30mod tests {
31 use relay_event_schema::protocol::{Event, EventType};
32 use relay_pattern::TypedPatterns;
33 use relay_protocol::Annotated;
34
35 use super::*;
36
37 fn _get_config() -> IgnoreTransactionsFilterConfig {
38 let patterns_raw = [
39 "*healthcheck*",
40 "*healthy*",
41 "live",
42 "live[z/-]*",
43 "*[/-]live",
44 "*[/-]live[z/-]*",
45 "ready",
46 "ready[z/-]*",
47 "*[/-]ready",
48 "*[/-]ready[z/-]*",
49 "*heartbeat*",
50 "*/health",
51 "*/healthz",
52 "*/ping",
53 "*/up",
54 ]
55 .map(|val| val.to_string())
56 .to_vec();
57
58 IgnoreTransactionsFilterConfig {
59 patterns: TypedPatterns::from(patterns_raw),
60 is_enabled: true,
61 }
62 }
63
64 #[test]
66 fn test_matches() {
67 let config = _get_config();
68
69 let transaction_names = [
70 "a/b/healthcheck/c",
71 "a_HEALTHCHECK_b",
72 "healthcheck",
73 "/health",
74 "a/HEALTH",
75 "/health",
76 "a/HEALTH",
77 "live",
78 "livez",
79 "live/123",
80 "live-33",
81 "x-live",
82 "abc/live",
83 "abc/live-23",
84 "ready",
85 "123/ready",
86 "readyz",
87 "ready/123",
88 "abc/ready/1234",
89 "abcheartbeat123",
90 "123/health",
91 "123/healthz",
92 "123/ping",
93 "/up",
94 "123/up",
95 ];
96
97 for name in transaction_names {
98 let event = Event {
99 transaction: Annotated::new(name.into()),
100 ty: Annotated::new(EventType::Transaction),
101 ..Event::default()
102 };
103 assert!(
104 should_filter(&event, &config).is_err(),
105 "Did not match `{name}`"
106 )
107 }
108 }
109 #[test]
111 fn test_does_not_match() {
112 let transaction_names = [
113 "bad",
114 "/bad",
115 "a/b/c",
116 "health",
117 "healthz",
118 "/health/",
119 "/healthz/",
120 "/healthx",
121 "/healthzx",
122 "service-delivery",
123 "delivery",
124 "notready",
125 "already",
126 "/upload",
127 ];
128 let config = _get_config();
129
130 for name in transaction_names {
131 let event = Event {
132 transaction: Annotated::new(name.into()),
133 ty: Annotated::new(EventType::Transaction),
134 ..Event::default()
135 };
136 assert!(
137 should_filter(&event, &config).is_ok(),
138 "Did match `{name}` but it shouldn't have."
139 )
140 }
141 }
142
143 #[test]
145 fn test_does_not_match_missing_transaction() {
146 let event = Event {
147 ty: Annotated::new(EventType::Transaction),
148 ..Event::default()
149 };
150 let config = _get_config();
151 assert!(
152 should_filter(&event, &config).is_ok(),
153 "Did match with empty transaction but it shouldn't have."
154 )
155 }
156
157 #[test]
158 fn test_filters_when_matching() {
159 let event = Event {
160 transaction: Annotated::new("/health".into()),
161 ty: Annotated::new(EventType::Transaction),
162 ..Event::default()
163 };
164 let config = _get_config();
165
166 let filter_result = should_filter(&event, &config);
167 assert_eq!(
168 filter_result,
169 Err(FilterStatKey::FilteredTransactions),
170 "Event was not filtered "
171 )
172 }
173
174 #[test]
175 fn test_does_not_filter_when_disabled() {
176 let event = Event {
177 transaction: Annotated::new("/health".into()),
178 ty: Annotated::new(EventType::Transaction),
179 ..Event::default()
180 };
181 let filter_result = should_filter(
182 &event,
183 &IgnoreTransactionsFilterConfig {
184 patterns: TypedPatterns::default(),
185 is_enabled: true,
186 },
187 );
188 assert_eq!(
189 filter_result,
190 Ok(()),
191 "Event filtered although filter should have been disabled"
192 )
193 }
194 #[test]
195 fn test_does_not_filter_when_disabled_with_flag() {
197 let event = Event {
198 transaction: Annotated::new("/health".into()),
199 ty: Annotated::new(EventType::Transaction),
200 ..Event::default()
201 };
202 let mut config = _get_config();
203 config.is_enabled = false;
204 let filter_result = should_filter(&event, &config);
205 assert_eq!(
206 filter_result,
207 Ok(()),
208 "Event filtered although filter should have been disabled"
209 )
210 }
211
212 #[test]
213 fn test_does_not_filter_when_not_matching() {
214 let event = Event {
215 transaction: Annotated::new("/a/b/c".into()),
216 ty: Annotated::new(EventType::Transaction),
217 ..Event::default()
218 };
219 let filter_result = should_filter(&event, &_get_config());
220 assert_eq!(
221 filter_result,
222 Ok(()),
223 "Event filtered although filter should have not matched"
224 )
225 }
226
227 #[test]
228 fn test_only_filters_transactions_not_anything_else() {
229 let config = _get_config();
230
231 for event_type in [EventType::Transaction, EventType::Error, EventType::Csp] {
232 let expect_to_filter = event_type == EventType::Transaction;
233 let event = Event {
234 transaction: Annotated::new("/health".into()),
235 ty: Annotated::new(event_type),
236 ..Event::default()
237 };
238 let filter_result = should_filter(&event, &config);
239
240 if expect_to_filter {
241 assert_eq!(
242 filter_result,
243 Err(FilterStatKey::FilteredTransactions),
244 "Event was not filtered "
245 );
246 } else {
247 assert_eq!(
248 filter_result,
249 Ok(()),
250 "Event filtered for event_type={} although filter should have not matched",
251 event_type
252 )
253 }
254 }
255 }
256}