relay_filter/
interface.rs1use relay_conventions::{
4 BROWSER_NAME, BROWSER_VERSION, RELEASE, 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#[derive(Clone, Debug, Default)]
15pub struct UserAgent<'a> {
16 pub raw: Option<&'a str>,
18 pub parsed: Option<relay_ua::UserAgent<'a>>,
20}
21
22impl<'a> UserAgent<'a> {
23 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
35pub trait Filterable {
37 fn csp(&self) -> Option<&Csp> {
39 None
40 }
41
42 fn exceptions(&self) -> Option<&Values<Exception>> {
44 None
45 }
46
47 fn ip_addr(&self) -> Option<&str> {
49 None
50 }
51
52 fn logentry(&self) -> Option<&LogEntry> {
54 None
55 }
56
57 fn release(&self) -> Option<&str> {
59 None
60 }
61
62 fn transaction(&self) -> Option<&str> {
64 None
65 }
66
67 fn url(&self) -> Option<Url> {
69 None
70 }
71
72 fn user_agent(&self) -> UserAgent<'_> {
74 Default::default()
75 }
76
77 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.value()?.get_value(RELEASE)?.as_str()
202 }
203
204 fn transaction(&self) -> Option<&str> {
205 self.attributes.value()?.get_value(SEGMENT_NAME)?.as_str()
206 }
207
208 fn user_agent(&self) -> UserAgent<'_> {
209 user_agent_from_attributes(&self.attributes)
210 }
211}
212
213impl Filterable for SessionUpdate {
214 fn ip_addr(&self) -> Option<&str> {
215 self.attributes
216 .ip_address
217 .as_ref()
218 .map(|addr| addr.as_str())
219 }
220
221 fn release(&self) -> Option<&str> {
222 Some(&self.attributes.release)
223 }
224
225 fn user_agent(&self) -> UserAgent<'_> {
226 UserAgent {
227 raw: self.attributes.user_agent.as_deref(),
228 parsed: None,
229 }
230 }
231}
232
233impl Filterable for SessionAggregates {
234 fn ip_addr(&self) -> Option<&str> {
235 self.attributes
236 .ip_address
237 .as_ref()
238 .map(|addr| addr.as_str())
239 }
240
241 fn release(&self) -> Option<&str> {
242 Some(&self.attributes.release)
243 }
244
245 fn user_agent(&self) -> UserAgent<'_> {
246 UserAgent {
247 raw: self.attributes.user_agent.as_deref(),
248 parsed: None,
249 }
250 }
251}
252
253impl Filterable for OurLog {
254 fn release(&self) -> Option<&str> {
255 self.attributes.value()?.get_value(RELEASE)?.as_str()
256 }
257
258 fn user_agent(&self) -> UserAgent<'_> {
259 user_agent_from_attributes(&self.attributes)
260 }
261}
262
263impl Filterable for TraceMetric {
264 fn release(&self) -> Option<&str> {
265 self.attributes.value()?.get_value(RELEASE)?.as_str()
266 }
267
268 fn user_agent(&self) -> UserAgent<'_> {
269 user_agent_from_attributes(&self.attributes)
270 }
271}
272
273fn user_agent_from_attributes(attributes: &relay_protocol::Annotated<Attributes>) -> UserAgent<'_> {
274 let parsed = (|| {
275 let attributes = attributes.value()?;
276
277 let family = attributes.get_value(BROWSER_NAME)?.as_str()?;
278 let version = attributes.get_value(BROWSER_VERSION)?.as_str()?;
279 let mut parts = version.splitn(3, '.');
280
281 Some(relay_ua::UserAgent {
282 family: family.into(),
283 major: parts.next().map(Into::into),
284 minor: parts.next().map(Into::into),
285 patch: parts.next().map(Into::into),
286 })
287 })();
288
289 let raw = attributes
290 .value()
291 .and_then(|attr| attr.get_value(USER_AGENT_ORIGINAL))
292 .and_then(|ua| ua.as_str());
293
294 UserAgent { raw, parsed }
295}