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 "*/actuator/health{/*,}",
55 "*/manage/health{/*,}",
56 ]
57 .map(|val| val.to_owned())
58 .to_vec();
59
60 IgnoreTransactionsFilterConfig {
61 patterns: TypedPatterns::from(patterns_raw),
62 is_enabled: true,
63 }
64 }
65
66 #[test]
68 fn test_matches() {
69 let config = _get_config();
70
71 let transaction_names = [
72 "a/b/healthcheck/c",
73 "a_HEALTHCHECK_b",
74 "healthcheck",
75 "/health",
76 "a/HEALTH",
77 "/health",
78 "a/HEALTH",
79 "live",
80 "livez",
81 "live/123",
82 "live-33",
83 "x-live",
84 "abc/live",
85 "abc/live-23",
86 "ready",
87 "123/ready",
88 "readyz",
89 "ready/123",
90 "abc/ready/1234",
91 "abcheartbeat123",
92 "123/health",
93 "123/healthz",
94 "123/ping",
95 "/up",
96 "123/up",
97 "/actuator/health",
98 "/actuator/health/",
99 "foo/actuator/health/db",
100 "/manage/health",
101 "/manage/health/",
102 "foo/manage/health/db",
103 ];
104
105 for name in transaction_names {
106 let event = Event {
107 transaction: Annotated::new(name.into()),
108 ty: Annotated::new(EventType::Transaction),
109 ..Event::default()
110 };
111 assert!(
112 should_filter(&event, &config).is_err(),
113 "Did not match `{name}`"
114 )
115 }
116 }
117 #[test]
119 fn test_does_not_match() {
120 let transaction_names = [
121 "bad",
122 "/bad",
123 "a/b/c",
124 "health",
125 "healthz",
126 "/healthz/",
127 "/healthx",
128 "/healthzx",
129 "service-delivery",
130 "delivery",
131 "notready",
132 "already",
133 "/upload",
134 "foo/health/bar",
135 ];
136 let config = _get_config();
137
138 for name in transaction_names {
139 let event = Event {
140 transaction: Annotated::new(name.into()),
141 ty: Annotated::new(EventType::Transaction),
142 ..Event::default()
143 };
144 assert!(
145 should_filter(&event, &config).is_ok(),
146 "Did match `{name}` but it shouldn't have."
147 )
148 }
149 }
150
151 #[test]
153 fn test_does_not_match_missing_transaction() {
154 let event = Event {
155 ty: Annotated::new(EventType::Transaction),
156 ..Event::default()
157 };
158 let config = _get_config();
159 assert!(
160 should_filter(&event, &config).is_ok(),
161 "Did match with empty transaction but it shouldn't have."
162 )
163 }
164
165 #[test]
166 fn test_filters_when_matching() {
167 let event = Event {
168 transaction: Annotated::new("/health".into()),
169 ty: Annotated::new(EventType::Transaction),
170 ..Event::default()
171 };
172 let config = _get_config();
173
174 let filter_result = should_filter(&event, &config);
175 assert_eq!(
176 filter_result,
177 Err(FilterStatKey::FilteredTransactions),
178 "Event was not filtered "
179 )
180 }
181
182 #[test]
183 fn test_does_not_filter_when_disabled() {
184 let event = Event {
185 transaction: Annotated::new("/health".into()),
186 ty: Annotated::new(EventType::Transaction),
187 ..Event::default()
188 };
189 let filter_result = should_filter(
190 &event,
191 &IgnoreTransactionsFilterConfig {
192 patterns: TypedPatterns::default(),
193 is_enabled: true,
194 },
195 );
196 assert_eq!(
197 filter_result,
198 Ok(()),
199 "Event filtered although filter should have been disabled"
200 )
201 }
202 #[test]
203 fn test_does_not_filter_when_disabled_with_flag() {
205 let event = Event {
206 transaction: Annotated::new("/health".into()),
207 ty: Annotated::new(EventType::Transaction),
208 ..Event::default()
209 };
210 let mut config = _get_config();
211 config.is_enabled = false;
212 let filter_result = should_filter(&event, &config);
213 assert_eq!(
214 filter_result,
215 Ok(()),
216 "Event filtered although filter should have been disabled"
217 )
218 }
219
220 #[test]
221 fn test_does_not_filter_when_not_matching() {
222 let event = Event {
223 transaction: Annotated::new("/a/b/c".into()),
224 ty: Annotated::new(EventType::Transaction),
225 ..Event::default()
226 };
227 let filter_result = should_filter(&event, &_get_config());
228 assert_eq!(
229 filter_result,
230 Ok(()),
231 "Event filtered although filter should have not matched"
232 )
233 }
234
235 #[test]
236 fn test_only_filters_transactions_not_anything_else() {
237 let config = _get_config();
238
239 for event_type in [EventType::Transaction, EventType::Error, EventType::Csp] {
240 let expect_to_filter = event_type == EventType::Transaction;
241 let event = Event {
242 transaction: Annotated::new("/health".into()),
243 ty: Annotated::new(event_type),
244 ..Event::default()
245 };
246 let filter_result = should_filter(&event, &config);
247
248 if expect_to_filter {
249 assert_eq!(
250 filter_result,
251 Err(FilterStatKey::FilteredTransactions),
252 "Event was not filtered "
253 );
254 } else {
255 assert_eq!(
256 filter_result,
257 Ok(()),
258 "Event filtered for event_type={event_type} although filter should have not matched"
259 )
260 }
261 }
262 }
263}