relay_event_normalization/normalize/
utils.rs1use std::f64::consts::SQRT_2;
7
8use relay_event_schema::protocol::{Event, ResponseContext, Span, TraceContext, User};
9use relay_protocol::Value;
10
11pub const MOBILE_SDKS: [&str; 4] = [
13 "sentry.cocoa",
14 "sentry.dart.flutter",
15 "sentry.java.android",
16 "sentry.javascript.react-native",
17];
18
19pub const MAIN_THREAD_NAME: &str = "main";
21
22pub const MAX_DURATION_MOBILE_MS: f64 = 180_000.0;
27
28pub fn http_status_code_from_span(span: &Span) -> Option<String> {
30 if let Some(status_code) = span
32 .data
33 .value()
34 .and_then(|data| data.http_response_status_code.value())
35 .map(|v| match v {
36 Value::String(s) => Some(s.as_str().to_owned()),
37 Value::I64(i) => Some(i.to_string()),
38 Value::U64(u) => Some(u.to_string()),
39 _ => None,
40 })
41 {
42 return status_code;
43 }
44
45 if let Some(status_code) = span
47 .tags
48 .value()
49 .and_then(|tags| tags.get("http.status_code"))
50 .and_then(|v| v.as_str())
51 .map(|v| v.to_owned())
52 {
53 return Some(status_code);
54 }
55
56 None
57}
58
59pub fn extract_http_status_code(event: &Event) -> Option<String> {
61 if let Some(status_code) = event.tag_value("http.status_code") {
63 return Some(status_code.to_owned());
64 }
65
66 if let Some(spans) = event.spans.value() {
67 for span in spans {
68 if let Some(span_value) = span.value() {
69 if let Some(status_code) = http_status_code_from_span(span_value) {
70 return Some(status_code);
71 }
72 }
73 }
74 }
75
76 if let Some(breadcrumbs) = event.breadcrumbs.value() {
78 if let Some(values) = breadcrumbs.values.value() {
79 for breadcrumb in values {
80 if let Some(crumb) = breadcrumb
82 .value()
83 .filter(|bc| bc.ty.as_str() == Some("http"))
84 {
85 if let Some(status_code) = crumb.data.value().and_then(|v| v.get("status_code"))
87 {
88 return status_code.value().and_then(|v| v.as_str()).map(Into::into);
89 }
90 }
91 }
92 }
93 }
94
95 if let Some(response_context) = event.context::<ResponseContext>() {
97 let status_code = response_context
98 .status_code
99 .value()
100 .map(|code| code.to_string());
101 return status_code;
102 }
103
104 None
105}
106
107pub fn get_event_user_tag(user: &User) -> Option<String> {
137 if let Some(id) = user.id.as_str() {
138 return Some(format!("id:{id}"));
139 }
140
141 if let Some(username) = user.username.as_str() {
142 return Some(format!("username:{username}"));
143 }
144
145 if let Some(email) = user.email.as_str() {
146 return Some(format!("email:{email}"));
147 }
148
149 if let Some(ip_address) = user.ip_address.as_str() {
150 return Some(format!("ip:{ip_address}"));
151 }
152
153 None
154}
155
156pub fn extract_transaction_op(trace_context: &TraceContext) -> Option<String> {
158 let op = trace_context.op.value()?;
159 if op == "default" {
160 return None;
166 }
167 Some(op.to_string())
168}
169
170fn erf(x: f64) -> f64 {
174 let a1 = 0.254829592;
176 let a2 = -0.284496736;
177 let a3 = 1.421413741;
178 let a4 = -1.453152027;
179 let a5 = 1.061405429;
180 let p = 0.3275911;
181 let sign = if x < 0.0 { -1.0 } else { 1.0 };
183 let x = x.abs();
184 let t = 1.0 / (1.0 + p * x);
186 let y = 1.0 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * (-x * x).exp();
187 sign * y
188}
189
190fn calculate_cdf_sigma(p10: f64, p50: f64) -> f64 {
192 (p10.ln() - p50.ln()).abs() / (SQRT_2 * 0.9061938024368232)
193}
194
195pub fn calculate_cdf_score(value: f64, p10: f64, p50: f64) -> f64 {
197 0.5 * (1.0 - erf((f64::ln(value) - f64::ln(p50)) / (SQRT_2 * calculate_cdf_sigma(p50, p10))))
198}
199
200#[cfg(test)]
201mod tests {
202 use crate::utils::{get_event_user_tag, http_status_code_from_span};
203 use relay_event_schema::protocol::{Span, User};
204 use relay_protocol::Annotated;
205
206 #[test]
207 fn test_get_event_user_tag() {
208 let user = User {
212 id: Annotated::new("ident".to_owned().into()),
213 username: Annotated::new("username".to_owned().into()),
214 email: Annotated::new("email".to_owned()),
215 ip_address: Annotated::new("127.0.0.1".parse().unwrap()),
216 ..User::default()
217 };
218
219 assert_eq!(get_event_user_tag(&user).unwrap(), "id:ident");
220
221 let user = User {
222 username: Annotated::new("username".to_owned().into()),
223 email: Annotated::new("email".to_owned()),
224 ip_address: Annotated::new("127.0.0.1".parse().unwrap()),
225 ..User::default()
226 };
227
228 assert_eq!(get_event_user_tag(&user).unwrap(), "username:username");
229
230 let user = User {
231 email: Annotated::new("email".to_owned()),
232 ip_address: Annotated::new("127.0.0.1".parse().unwrap()),
233 ..User::default()
234 };
235
236 assert_eq!(get_event_user_tag(&user).unwrap(), "email:email");
237
238 let user = User {
239 ip_address: Annotated::new("127.0.0.1".parse().unwrap()),
240 ..User::default()
241 };
242
243 assert_eq!(get_event_user_tag(&user).unwrap(), "ip:127.0.0.1");
244
245 let user = User::default();
246
247 assert!(get_event_user_tag(&user).is_none());
248 }
249
250 #[test]
251 fn test_extracts_http_status_code_when_int() {
252 let span = Annotated::<Span>::from_json(
253 r#"{
254 "data": {
255 "http.response.status_code": 400
256 }
257 }"#,
258 )
259 .unwrap()
260 .into_value()
261 .unwrap();
262
263 let result = http_status_code_from_span(&span);
264
265 assert_eq!(result, Some("400".to_string()));
266 }
267}