relay_filter/
interface.rs1use url::Url;
4
5use relay_event_schema::protocol::{
6 Attributes, Csp, Event, EventType, Exception, LogEntry, OurLog, Replay, SessionAggregates,
7 SessionUpdate, Span, SpanV2, Values,
8};
9
10#[derive(Clone, Debug, Default)]
12pub struct UserAgent<'a> {
13 pub raw: Option<&'a str>,
15 pub parsed: Option<relay_ua::UserAgent<'a>>,
17}
18
19impl<'a> UserAgent<'a> {
20 pub fn parsed(&mut self) -> Option<&'_ relay_ua::UserAgent<'a>> {
22 match &mut self.parsed {
23 Some(parsed) => Some(parsed),
24 parsed @ None => {
25 *parsed = Some(relay_ua::parse_user_agent(self.raw?));
26 parsed.as_ref()
27 }
28 }
29 }
30}
31
32pub trait Filterable {
34 fn csp(&self) -> Option<&Csp> {
36 None
37 }
38
39 fn exceptions(&self) -> Option<&Values<Exception>> {
41 None
42 }
43
44 fn ip_addr(&self) -> Option<&str> {
46 None
47 }
48
49 fn logentry(&self) -> Option<&LogEntry> {
51 None
52 }
53
54 fn release(&self) -> Option<&str> {
56 None
57 }
58
59 fn transaction(&self) -> Option<&str> {
61 None
62 }
63
64 fn url(&self) -> Option<Url> {
66 None
67 }
68
69 fn user_agent(&self) -> UserAgent<'_> {
71 Default::default()
72 }
73
74 fn header(&self, _header_name: &str) -> Option<&str> {
81 None
82 }
83}
84
85impl Filterable for Event {
86 fn csp(&self) -> Option<&Csp> {
87 if self.ty.value() != Some(&EventType::Csp) {
88 return None;
89 }
90 self.csp.value()
91 }
92
93 fn exceptions(&self) -> Option<&Values<Exception>> {
94 self.exceptions.value()
95 }
96
97 fn ip_addr(&self) -> Option<&str> {
98 let user = self.user.value()?;
99 Some(user.ip_address.value()?.as_ref())
100 }
101
102 fn logentry(&self) -> Option<&LogEntry> {
103 self.logentry.value()
104 }
105
106 fn release(&self) -> Option<&str> {
107 self.release.as_str()
108 }
109
110 fn transaction(&self) -> Option<&str> {
111 if self.ty.value() != Some(&EventType::Transaction) {
112 return None;
113 }
114 self.transaction.as_str()
115 }
116
117 fn url(&self) -> Option<Url> {
118 let url_str = self.request.value()?.url.value()?;
119 Url::parse(url_str).ok()
120 }
121
122 fn user_agent(&self) -> UserAgent<'_> {
123 UserAgent {
124 raw: self.user_agent(),
125 parsed: None,
126 }
127 }
128
129 fn header(&self, header_name: &str) -> Option<&str> {
130 self.request
131 .value()?
132 .headers
133 .value()?
134 .get_header(header_name)
135 }
136}
137
138impl Filterable for Replay {
139 fn ip_addr(&self) -> Option<&str> {
140 let user = self.user.value()?;
141 Some(user.ip_address.value()?.as_ref())
142 }
143
144 fn release(&self) -> Option<&str> {
145 self.release.as_str()
146 }
147
148 fn url(&self) -> Option<Url> {
149 let url_str = self.request.value()?.url.value()?;
150 Url::parse(url_str).ok()
151 }
152
153 fn user_agent(&self) -> UserAgent<'_> {
154 UserAgent {
155 raw: self.user_agent(),
156 parsed: None,
157 }
158 }
159
160 fn header(&self, header_name: &str) -> Option<&str> {
161 self.request
162 .value()?
163 .headers
164 .value()?
165 .get_header(header_name)
166 }
167}
168
169impl Filterable for Span {
170 fn ip_addr(&self) -> Option<&str> {
171 self.data.value()?.client_address.as_str()
172 }
173
174 fn release(&self) -> Option<&str> {
175 self.data.value()?.release.as_str()
176 }
177
178 fn transaction(&self) -> Option<&str> {
179 self.data.value()?.segment_name.as_str()
180 }
181
182 fn url(&self) -> Option<Url> {
183 let url_str = self.data.value()?.url_full.as_str()?;
184 Url::parse(url_str).ok()
185 }
186
187 fn user_agent(&self) -> UserAgent<'_> {
188 let raw = self
189 .data
190 .value()
191 .and_then(|data| data.user_agent_original.as_str());
192 UserAgent { raw, parsed: None }
193 }
194}
195
196impl Filterable for SpanV2 {
197 fn release(&self) -> Option<&str> {
198 self.attributes
199 .value()?
200 .get_value("sentry.release")?
201 .as_str()
202 }
203
204 fn transaction(&self) -> Option<&str> {
205 self.attributes
206 .value()?
207 .get_value("sentry.segment.name")?
208 .as_str()
209 }
210
211 fn user_agent(&self) -> UserAgent<'_> {
212 user_agent_from_attributes(&self.attributes)
213 }
214}
215
216impl Filterable for SessionUpdate {
217 fn ip_addr(&self) -> Option<&str> {
218 self.attributes
219 .ip_address
220 .as_ref()
221 .map(|addr| addr.as_str())
222 }
223
224 fn release(&self) -> Option<&str> {
225 Some(&self.attributes.release)
226 }
227
228 fn user_agent(&self) -> UserAgent<'_> {
229 UserAgent {
230 raw: self.attributes.user_agent.as_deref(),
231 parsed: None,
232 }
233 }
234}
235
236impl Filterable for SessionAggregates {
237 fn ip_addr(&self) -> Option<&str> {
238 self.attributes
239 .ip_address
240 .as_ref()
241 .map(|addr| addr.as_str())
242 }
243
244 fn release(&self) -> Option<&str> {
245 Some(&self.attributes.release)
246 }
247
248 fn user_agent(&self) -> UserAgent<'_> {
249 UserAgent {
250 raw: self.attributes.user_agent.as_deref(),
251 parsed: None,
252 }
253 }
254}
255
256impl Filterable for OurLog {
257 fn release(&self) -> Option<&str> {
258 self.attributes
259 .value()?
260 .get_value("sentry.release")?
261 .as_str()
262 }
263
264 fn user_agent(&self) -> UserAgent<'_> {
265 user_agent_from_attributes(&self.attributes)
266 }
267}
268
269fn user_agent_from_attributes(attributes: &relay_protocol::Annotated<Attributes>) -> UserAgent<'_> {
270 let parsed = (|| {
271 let attributes = attributes.value()?;
272
273 let family = attributes.get_value("sentry.browser.name")?.as_str()?;
274 let version = attributes.get_value("sentry.browser.version")?.as_str()?;
275 let mut parts = version.splitn(3, '.');
276
277 Some(relay_ua::UserAgent {
278 family: family.into(),
279 major: parts.next().map(Into::into),
280 minor: parts.next().map(Into::into),
281 patch: parts.next().map(Into::into),
282 })
283 })();
284
285 UserAgent { raw: None, parsed }
286}