Skip to main content

relay_filter/
transaction_name.rs

1//! Implements event filtering based on whether the endpoint called is a healthcheck endpoint.
2//!
3//! If this filter is enabled transactions from healthcheck endpoints will be filtered out.
4
5use 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
13/// Filters [Transaction](relay_event_schema::protocol::EventType::Transaction) events based on a list of provided transaction
14/// name globs.
15pub 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    /// tests matching for various transactions
67    #[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    /// tests non matching transactions transactions
118    #[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 it doesn't match when the transaction name is missing
152    #[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    // Tests that is_enabled flag disables the transaction name filter
204    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}