Skip to main content

relay_filter/
interface.rs

1//! This module contains the trait for items that can be filtered by Inbound Filters, plus
2//! the implementation for [`Event`].
3use relay_conventions::attributes::{
4    BROWSER__NAME, BROWSER__VERSION, SENTRY__RELEASE, SENTRY__SEGMENT__NAME, USER_AGENT__ORIGINAL,
5};
6use url::Url;
7
8use relay_event_schema::protocol::{
9    Attributes, Csp, Event, EventType, Exception, LogEntry, OurLog, Replay, SessionAggregates,
10    SessionUpdate, Span, SpanV2, TraceMetric, Values,
11};
12
13/// A user agent returned from [`Filterable::user_agent`].
14#[derive(Clone, Debug, Default)]
15pub struct UserAgent<'a> {
16    /// The raw, unparsed user agent string, if available.
17    pub raw: Option<&'a str>,
18    /// A already fully parsed user agent, if available.
19    pub parsed: Option<relay_ua::UserAgent<'a>>,
20}
21
22impl<'a> UserAgent<'a> {
23    /// Parses the raw user agent, if it is not already parsed and returns it.
24    pub fn parsed(&mut self) -> Option<&'_ relay_ua::UserAgent<'a>> {
25        match &mut self.parsed {
26            Some(parsed) => Some(parsed),
27            parsed @ None => {
28                *parsed = Some(relay_ua::parse_user_agent(self.raw?));
29                parsed.as_ref()
30            }
31        }
32    }
33}
34
35/// A data item to which filters can be applied.
36pub trait Filterable {
37    /// The CSP report contained in the item. Only for CSP reports.
38    fn csp(&self) -> Option<&Csp> {
39        None
40    }
41
42    /// The exception values of the item. Only for error events.
43    fn exceptions(&self) -> Option<&Values<Exception>> {
44        None
45    }
46
47    /// The IP address of the client that sent the data.
48    fn ip_addr(&self) -> Option<&str> {
49        None
50    }
51
52    /// The logentry message. Only for error events.
53    fn logentry(&self) -> Option<&LogEntry> {
54        None
55    }
56
57    /// The release string of the data item.
58    fn release(&self) -> Option<&str> {
59        None
60    }
61
62    /// The transaction name. Only for transaction events.
63    fn transaction(&self) -> Option<&str> {
64        None
65    }
66
67    /// The URL from which the request originates. Used for localhost filtering.
68    fn url(&self) -> Option<Url> {
69        None
70    }
71
72    /// The user agent of the client that sent the data.
73    fn user_agent(&self) -> UserAgent<'_> {
74        Default::default()
75    }
76
77    /// Retrieves a request headers from the item.
78    ///
79    /// For example this is used for localhost filtering by inspecting relevant headers that may
80    /// be included in the `request.headers` of an error.
81    ///
82    /// This **does not** return header information from the request that reached relay.
83    fn header(&self, _header_name: &str) -> Option<&str> {
84        None
85    }
86}
87
88impl Filterable for Event {
89    fn csp(&self) -> Option<&Csp> {
90        if self.ty.value() != Some(&EventType::Csp) {
91            return None;
92        }
93        self.csp.value()
94    }
95
96    fn exceptions(&self) -> Option<&Values<Exception>> {
97        self.exceptions.value()
98    }
99
100    fn ip_addr(&self) -> Option<&str> {
101        let user = self.user.value()?;
102        Some(user.ip_address.value()?.as_ref())
103    }
104
105    fn logentry(&self) -> Option<&LogEntry> {
106        self.logentry.value()
107    }
108
109    fn release(&self) -> Option<&str> {
110        self.release.as_str()
111    }
112
113    fn transaction(&self) -> Option<&str> {
114        if self.ty.value() != Some(&EventType::Transaction) {
115            return None;
116        }
117        self.transaction.as_str()
118    }
119
120    fn url(&self) -> Option<Url> {
121        let url_str = self.request.value()?.url.value()?;
122        Url::parse(url_str).ok()
123    }
124
125    fn user_agent(&self) -> UserAgent<'_> {
126        UserAgent {
127            raw: self.user_agent(),
128            parsed: None,
129        }
130    }
131
132    fn header(&self, header_name: &str) -> Option<&str> {
133        self.request
134            .value()?
135            .headers
136            .value()?
137            .get_header(header_name)
138    }
139}
140
141impl Filterable for Replay {
142    fn ip_addr(&self) -> Option<&str> {
143        let user = self.user.value()?;
144        Some(user.ip_address.value()?.as_ref())
145    }
146
147    fn release(&self) -> Option<&str> {
148        self.release.as_str()
149    }
150
151    fn url(&self) -> Option<Url> {
152        let url_str = self.request.value()?.url.value()?;
153        Url::parse(url_str).ok()
154    }
155
156    fn user_agent(&self) -> UserAgent<'_> {
157        UserAgent {
158            raw: self.user_agent(),
159            parsed: None,
160        }
161    }
162
163    fn header(&self, header_name: &str) -> Option<&str> {
164        self.request
165            .value()?
166            .headers
167            .value()?
168            .get_header(header_name)
169    }
170}
171
172impl Filterable for Span {
173    fn ip_addr(&self) -> Option<&str> {
174        self.data.value()?.client_address.as_str()
175    }
176
177    fn release(&self) -> Option<&str> {
178        self.data.value()?.release.as_str()
179    }
180
181    fn transaction(&self) -> Option<&str> {
182        self.data.value()?.segment_name.as_str()
183    }
184
185    fn url(&self) -> Option<Url> {
186        let url_str = self.data.value()?.url_full.as_str()?;
187        Url::parse(url_str).ok()
188    }
189
190    fn user_agent(&self) -> UserAgent<'_> {
191        let raw = self
192            .data
193            .value()
194            .and_then(|data| data.user_agent_original.as_str());
195        UserAgent { raw, parsed: None }
196    }
197}
198
199impl Filterable for SpanV2 {
200    fn release(&self) -> Option<&str> {
201        self.attributes
202            .value()?
203            .get_value(SENTRY__RELEASE)?
204            .as_str()
205    }
206
207    fn transaction(&self) -> Option<&str> {
208        self.attributes
209            .value()?
210            .get_value(SENTRY__SEGMENT__NAME)?
211            .as_str()
212    }
213
214    fn user_agent(&self) -> UserAgent<'_> {
215        user_agent_from_attributes(&self.attributes)
216    }
217}
218
219impl Filterable for SessionUpdate {
220    fn ip_addr(&self) -> Option<&str> {
221        self.attributes
222            .ip_address
223            .as_ref()
224            .map(|addr| addr.as_str())
225    }
226
227    fn release(&self) -> Option<&str> {
228        Some(&self.attributes.release)
229    }
230
231    fn user_agent(&self) -> UserAgent<'_> {
232        UserAgent {
233            raw: self.attributes.user_agent.as_deref(),
234            parsed: None,
235        }
236    }
237}
238
239impl Filterable for SessionAggregates {
240    fn ip_addr(&self) -> Option<&str> {
241        self.attributes
242            .ip_address
243            .as_ref()
244            .map(|addr| addr.as_str())
245    }
246
247    fn release(&self) -> Option<&str> {
248        Some(&self.attributes.release)
249    }
250
251    fn user_agent(&self) -> UserAgent<'_> {
252        UserAgent {
253            raw: self.attributes.user_agent.as_deref(),
254            parsed: None,
255        }
256    }
257}
258
259impl Filterable for OurLog {
260    fn release(&self) -> Option<&str> {
261        self.attributes
262            .value()?
263            .get_value(SENTRY__RELEASE)?
264            .as_str()
265    }
266
267    fn user_agent(&self) -> UserAgent<'_> {
268        user_agent_from_attributes(&self.attributes)
269    }
270}
271
272impl Filterable for TraceMetric {
273    fn release(&self) -> Option<&str> {
274        self.attributes
275            .value()?
276            .get_value(SENTRY__RELEASE)?
277            .as_str()
278    }
279
280    fn user_agent(&self) -> UserAgent<'_> {
281        user_agent_from_attributes(&self.attributes)
282    }
283}
284
285fn user_agent_from_attributes(attributes: &relay_protocol::Annotated<Attributes>) -> UserAgent<'_> {
286    let parsed = (|| {
287        let attributes = attributes.value()?;
288
289        let family = attributes.get_value(BROWSER__NAME)?.as_str()?;
290        let version = attributes.get_value(BROWSER__VERSION)?.as_str()?;
291        let mut parts = version.splitn(3, '.');
292
293        Some(relay_ua::UserAgent {
294            family: family.into(),
295            major: parts.next().map(Into::into),
296            minor: parts.next().map(Into::into),
297            patch: parts.next().map(Into::into),
298        })
299    })();
300
301    let raw = attributes
302        .value()
303        .and_then(|attr| attr.get_value(USER_AGENT__ORIGINAL))
304        .and_then(|ua| ua.as_str());
305
306    UserAgent { raw, parsed }
307}