1use std::hash::Hash;
2
3use relay_base_schema::metrics::MetricUnit;
4use relay_common::glob2::LazyGlob;
5use relay_event_schema::protocol::{Event, VALID_PLATFORMS};
6use relay_protocol::RuleCondition;
7use serde::{Deserialize, Serialize};
8
9pub mod breakdowns;
10pub mod contexts;
11pub mod nel;
12pub mod request;
13pub mod span;
14pub mod user_agent;
15pub mod utils;
16
17#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Hash, Eq)]
19#[serde(default, rename_all = "camelCase")]
20pub struct BuiltinMeasurementKey {
21 name: String,
22 unit: MetricUnit,
23 #[serde(skip_serializing_if = "is_false")]
24 allow_negative: bool,
25}
26
27fn is_false(b: &bool) -> bool {
28 !b
29}
30
31impl BuiltinMeasurementKey {
32 pub fn new(name: impl Into<String>, unit: MetricUnit) -> Self {
34 Self {
35 name: name.into(),
36 unit,
37 allow_negative: false,
38 }
39 }
40
41 pub fn name(&self) -> &str {
43 &self.name
44 }
45
46 pub fn unit(&self) -> &MetricUnit {
48 &self.unit
49 }
50
51 pub fn allow_negative(&self) -> &bool {
53 &self.allow_negative
54 }
55}
56
57#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Hash)]
59#[serde(default, rename_all = "camelCase")]
60pub struct MeasurementsConfig {
61 #[serde(default, skip_serializing_if = "Vec::is_empty")]
63 pub builtin_measurements: Vec<BuiltinMeasurementKey>,
64
65 pub max_custom_measurements: usize,
67}
68
69impl MeasurementsConfig {
70 pub const MEASUREMENT_MRI_OVERHEAD: usize = 29;
73}
74
75pub fn is_valid_platform(platform: &str) -> bool {
79 VALID_PLATFORMS.contains(&platform)
80}
81
82pub fn normalize_app_start_spans(event: &mut Event) {
86 if !event.sdk_name().eq("sentry.javascript.react-native")
87 || !(event.sdk_version().starts_with("4.4")
88 || event.sdk_version().starts_with("4.3")
89 || event.sdk_version().starts_with("4.2")
90 || event.sdk_version().starts_with("4.1")
91 || event.sdk_version().starts_with("4.0")
92 || event.sdk_version().starts_with('3'))
93 {
94 return;
95 }
96
97 if let Some(spans) = event.spans.value_mut() {
98 for span in spans {
99 if let Some(span) = span.value_mut() {
100 if let Some(op) = span.op.value() {
101 if op == "app_start_cold" {
102 span.op.set_value(Some("app.start.cold".to_string()));
103 break;
104 } else if op == "app_start_warm" {
105 span.op.set_value(Some("app.start.warm".to_string()));
106 break;
107 }
108 }
109 }
110 }
111 }
112}
113
114#[derive(Clone, Debug)]
117pub struct CombinedMeasurementsConfig<'a> {
118 project: Option<&'a MeasurementsConfig>,
119 global: Option<&'a MeasurementsConfig>,
120}
121
122impl<'a> CombinedMeasurementsConfig<'a> {
123 pub fn new(
125 project: Option<&'a MeasurementsConfig>,
126 global: Option<&'a MeasurementsConfig>,
127 ) -> Self {
128 CombinedMeasurementsConfig { project, global }
129 }
130
131 pub fn builtin_measurement_keys(
136 &'a self,
137 ) -> impl Iterator<Item = &'a BuiltinMeasurementKey> + 'a {
138 let project = self
139 .project
140 .map(|p| p.builtin_measurements.as_slice())
141 .unwrap_or_default();
142
143 let global = self
144 .global
145 .map(|g| g.builtin_measurements.as_slice())
146 .unwrap_or_default();
147
148 project
149 .iter()
150 .chain(global.iter().filter(|key| !project.contains(key)))
151 }
152
153 pub fn max_custom_measurements(&'a self) -> Option<usize> {
156 match (&self.project, &self.global) {
157 (None, None) => None,
158 (None, Some(global)) => Some(global.max_custom_measurements),
159 (Some(project), None) => Some(project.max_custom_measurements),
160 (Some(project), Some(global)) => Some(std::cmp::min(
161 project.max_custom_measurements,
162 global.max_custom_measurements,
163 )),
164 }
165 }
166}
167
168#[derive(Debug, Default, Clone, Serialize, Deserialize)]
173pub struct PerformanceScoreWeightedComponent {
174 pub measurement: String,
177 pub weight: f64,
179 pub p10: f64,
181 pub p50: f64,
183 #[serde(default)]
186 pub optional: bool,
187}
188
189#[derive(Debug, Default, Clone, Serialize, Deserialize)]
194#[serde(rename_all = "camelCase")]
195pub struct PerformanceScoreProfile {
196 pub name: Option<String>,
198 #[serde(default, skip_serializing_if = "Vec::is_empty")]
200 pub score_components: Vec<PerformanceScoreWeightedComponent>,
201 pub condition: Option<RuleCondition>,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub version: Option<String>,
206}
207
208#[derive(Debug, Default, Clone, Serialize, Deserialize)]
213pub struct PerformanceScoreConfig {
214 #[serde(default, skip_serializing_if = "Vec::is_empty")]
216 pub profiles: Vec<PerformanceScoreProfile>,
217}
218
219#[derive(Clone, Default, Debug, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct ModelCosts {
223 pub version: u16,
225
226 #[serde(skip_serializing_if = "Vec::is_empty")]
228 pub costs: Vec<ModelCost>,
229}
230
231impl ModelCosts {
232 const MAX_SUPPORTED_VERSION: u16 = 1;
233
234 pub fn is_enabled(&self) -> bool {
236 self.version > 0 && self.version <= ModelCosts::MAX_SUPPORTED_VERSION
237 }
238
239 pub fn cost_per_1k_tokens(&self, model_id: &str, for_completion: bool) -> Option<f64> {
241 self.costs
242 .iter()
243 .find(|cost| cost.matches(model_id, for_completion))
244 .map(|c| c.cost_per_1k_tokens)
245 }
246}
247
248#[derive(Clone, Debug, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct ModelCost {
252 pub(crate) model_id: LazyGlob,
253 pub(crate) for_completion: bool,
254 pub(crate) cost_per_1k_tokens: f64,
255}
256
257impl ModelCost {
258 pub fn matches(&self, model_id: &str, for_completion: bool) -> bool {
260 self.for_completion == for_completion && self.model_id.compiled().is_match(model_id)
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use chrono::{TimeZone, Utc};
267 use insta::{assert_debug_snapshot, assert_json_snapshot};
268 use itertools::Itertools;
269 use relay_base_schema::events::EventType;
270 use relay_base_schema::metrics::DurationUnit;
271 use relay_base_schema::spans::SpanStatus;
272 use relay_event_schema::protocol::{
273 ClientSdkInfo, Context, ContextInner, Contexts, DebugImage, DebugMeta, EventId, Exception,
274 Frame, Geo, IpAddr, LenientString, Level, LogEntry, PairList, RawStacktrace, ReplayContext,
275 Request, Span, SpanId, Stacktrace, TagEntry, Tags, TraceContext, User, Values,
276 };
277 use relay_protocol::{
278 Annotated, Error, ErrorKind, FromValue, Object, SerializableAnnotated, Value,
279 assert_annotated_snapshot, get_path, get_value,
280 };
281 use serde_json::json;
282 use similar_asserts::assert_eq;
283 use uuid::Uuid;
284
285 use crate::stacktrace::normalize_non_raw_frame;
286 use crate::validation::validate_event;
287 use crate::{EventValidationConfig, GeoIpLookup, NormalizationConfig, normalize_event};
288
289 use super::*;
290
291 #[test]
292 fn test_model_cost_config() {
293 let original = r#"{"version":1,"costs":[{"modelId":"babbage-002.ft-*","forCompletion":false,"costPer1kTokens":0.0016}]}"#;
294 let deserialized: ModelCosts = serde_json::from_str(original).unwrap();
295 assert_debug_snapshot!(deserialized, @r###"
296 ModelCosts {
297 version: 1,
298 costs: [
299 ModelCost {
300 model_id: LazyGlob("babbage-002.ft-*"),
301 for_completion: false,
302 cost_per_1k_tokens: 0.0016,
303 },
304 ],
305 }
306 "###);
307
308 let serialized = serde_json::to_string(&deserialized).unwrap();
309 assert_eq!(&serialized, original);
310 }
311
312 #[test]
313 fn test_merge_builtin_measurement_keys() {
314 let foo = BuiltinMeasurementKey::new("foo", MetricUnit::Duration(DurationUnit::Hour));
315 let bar = BuiltinMeasurementKey::new("bar", MetricUnit::Duration(DurationUnit::Day));
316 let baz = BuiltinMeasurementKey::new("baz", MetricUnit::Duration(DurationUnit::Week));
317
318 let proj = MeasurementsConfig {
319 builtin_measurements: vec![foo.clone(), bar.clone()],
320 max_custom_measurements: 4,
321 };
322
323 let glob = MeasurementsConfig {
324 builtin_measurements: vec![baz.clone(), bar.clone()],
326 max_custom_measurements: 4,
327 };
328 let dynamic_config = CombinedMeasurementsConfig::new(Some(&proj), Some(&glob));
329
330 let keys = dynamic_config.builtin_measurement_keys().collect_vec();
331
332 assert_eq!(keys, vec![&foo, &bar, &baz]);
333 }
334
335 #[test]
336 fn test_max_custom_measurement() {
337 let dynamic_config = CombinedMeasurementsConfig::new(None, None);
339 assert!(dynamic_config.max_custom_measurements().is_none());
340
341 let proj = MeasurementsConfig {
342 builtin_measurements: vec![],
343 max_custom_measurements: 3,
344 };
345
346 let glob = MeasurementsConfig {
347 builtin_measurements: vec![],
348 max_custom_measurements: 4,
349 };
350
351 let dynamic_config = CombinedMeasurementsConfig::new(Some(&proj), None);
353 assert_eq!(dynamic_config.max_custom_measurements().unwrap(), 3);
354
355 let dynamic_config = CombinedMeasurementsConfig::new(None, Some(&glob));
357 assert_eq!(dynamic_config.max_custom_measurements().unwrap(), 4);
358
359 let dynamic_config = CombinedMeasurementsConfig::new(Some(&proj), Some(&glob));
361 assert_eq!(dynamic_config.max_custom_measurements().unwrap(), 3);
362 }
363
364 #[test]
365 fn test_geo_from_ip_address() {
366 let lookup = GeoIpLookup::open("tests/fixtures/GeoIP2-Enterprise-Test.mmdb").unwrap();
367
368 let json = r#"{
369 "user": {
370 "ip_address": "2.125.160.216"
371 }
372 }"#;
373 let mut event = Annotated::<Event>::from_json(json).unwrap();
374
375 normalize_event(
376 &mut event,
377 &NormalizationConfig {
378 geoip_lookup: Some(&lookup),
379 ..Default::default()
380 },
381 );
382
383 let expected = Annotated::new(Geo {
384 country_code: Annotated::new("GB".to_string()),
385 city: Annotated::new("Boxford".to_string()),
386 subdivision: Annotated::new("England".to_string()),
387 region: Annotated::new("United Kingdom".to_string()),
388 ..Geo::default()
389 });
390 assert_eq!(get_value!(event.user!).geo, expected);
391 }
392
393 #[test]
394 fn test_user_ip_from_remote_addr() {
395 let mut event = Annotated::new(Event {
396 request: Annotated::from(Request {
397 env: Annotated::new({
398 let mut map = Object::new();
399 map.insert(
400 "REMOTE_ADDR".to_string(),
401 Annotated::new(Value::String("2.125.160.216".to_string())),
402 );
403 map
404 }),
405 ..Request::default()
406 }),
407 platform: Annotated::new("javascript".to_owned()),
408 ..Event::default()
409 });
410
411 normalize_event(&mut event, &NormalizationConfig::default());
412
413 let ip_addr = get_value!(event.user.ip_address!);
414 assert_eq!(ip_addr, &IpAddr("2.125.160.216".to_string()));
415 }
416
417 #[test]
418 fn test_user_ip_from_invalid_remote_addr() {
419 let mut event = Annotated::new(Event {
420 request: Annotated::from(Request {
421 env: Annotated::new({
422 let mut map = Object::new();
423 map.insert(
424 "REMOTE_ADDR".to_string(),
425 Annotated::new(Value::String("whoops".to_string())),
426 );
427 map
428 }),
429 ..Request::default()
430 }),
431 platform: Annotated::new("javascript".to_owned()),
432 ..Event::default()
433 });
434
435 normalize_event(&mut event, &NormalizationConfig::default());
436
437 assert_eq!(Annotated::empty(), event.value().unwrap().user);
438 }
439
440 #[test]
441 fn test_user_ip_from_client_ip_without_auto() {
442 let mut event = Annotated::new(Event {
443 platform: Annotated::new("javascript".to_owned()),
444 ..Default::default()
445 });
446
447 let ip_address = IpAddr::parse("2.125.160.216").unwrap();
448
449 normalize_event(
450 &mut event,
451 &NormalizationConfig {
452 client_ip: Some(&ip_address),
453 infer_ip_address: true,
454 ..Default::default()
455 },
456 );
457
458 let ip_addr = get_value!(event.user.ip_address!);
459 assert_eq!(ip_addr, &IpAddr("2.125.160.216".to_string()));
460 }
461
462 #[test]
463 fn test_user_ip_from_client_ip_with_auto() {
464 let mut event = Annotated::new(Event {
465 user: Annotated::new(User {
466 ip_address: Annotated::new(IpAddr::auto()),
467 ..Default::default()
468 }),
469 ..Default::default()
470 });
471
472 let ip_address = IpAddr::parse("2.125.160.216").unwrap();
473
474 let geo = GeoIpLookup::open("tests/fixtures/GeoIP2-Enterprise-Test.mmdb").unwrap();
475 normalize_event(
476 &mut event,
477 &NormalizationConfig {
478 client_ip: Some(&ip_address),
479 geoip_lookup: Some(&geo),
480 infer_ip_address: true,
481 ..Default::default()
482 },
483 );
484
485 let user = get_value!(event.user!);
486 let ip_addr = user.ip_address.value().expect("ip address missing");
487
488 assert_eq!(ip_addr, &IpAddr("2.125.160.216".to_string()));
489 assert!(user.geo.value().is_some());
490 }
491
492 #[test]
493 fn test_user_ip_from_client_ip_without_appropriate_platform() {
494 let mut event = Annotated::new(Event::default());
495
496 let ip_address = IpAddr::parse("2.125.160.216").unwrap();
497 let geo = GeoIpLookup::open("tests/fixtures/GeoIP2-Enterprise-Test.mmdb").unwrap();
498 normalize_event(
499 &mut event,
500 &NormalizationConfig {
501 client_ip: Some(&ip_address),
502 geoip_lookup: Some(&geo),
503 ..Default::default()
504 },
505 );
506
507 let user = get_value!(event.user!);
508 assert!(user.ip_address.value().is_none());
509 assert!(user.geo.value().is_some());
510 }
511
512 #[test]
513 fn test_geo_present_if_ip_inferring_disabled() {
514 let mut event = Annotated::new(Event {
515 user: Annotated::new(User {
516 ip_address: Annotated::new(IpAddr::auto()),
517 ..Default::default()
518 }),
519 ..Default::default()
520 });
521
522 let ip_address = IpAddr::parse("2.125.160.216").unwrap();
523 let geo = GeoIpLookup::open("tests/fixtures/GeoIP2-Enterprise-Test.mmdb").unwrap();
524
525 normalize_event(
526 &mut event,
527 &NormalizationConfig {
528 client_ip: Some(&ip_address),
529 geoip_lookup: Some(&geo),
530 infer_ip_address: false,
531 ..Default::default()
532 },
533 );
534
535 let user = get_value!(event.user!);
536 assert!(user.ip_address.value().unwrap().is_auto());
537 assert!(user.geo.value().is_some());
538 }
539
540 #[test]
541 fn test_geo_and_ip_present_if_ip_inferring_enabled() {
542 let mut event = Annotated::new(Event {
543 user: Annotated::new(User {
544 ip_address: Annotated::new(IpAddr::auto()),
545 ..Default::default()
546 }),
547 ..Default::default()
548 });
549
550 let ip_address = IpAddr::parse("2.125.160.216").unwrap();
551 let geo = GeoIpLookup::open("tests/fixtures/GeoIP2-Enterprise-Test.mmdb").unwrap();
552
553 normalize_event(
554 &mut event,
555 &NormalizationConfig {
556 client_ip: Some(&ip_address),
557 geoip_lookup: Some(&geo),
558 infer_ip_address: true,
559 ..Default::default()
560 },
561 );
562
563 let user = get_value!(event.user!);
564 assert_eq!(
565 user.ip_address.value(),
566 Some(&IpAddr::parse("2.125.160.216").unwrap())
567 );
568 assert!(user.geo.value().is_some());
569 }
570
571 #[test]
572 fn test_event_level_defaulted() {
573 let mut event = Annotated::new(Event::default());
574 normalize_event(&mut event, &NormalizationConfig::default());
575 assert_eq!(get_value!(event.level), Some(&Level::Error));
576 }
577
578 #[test]
579 fn test_transaction_level_untouched() {
580 let mut event = Annotated::new(Event {
581 ty: Annotated::new(EventType::Transaction),
582 timestamp: Annotated::new(Utc.with_ymd_and_hms(1987, 6, 5, 4, 3, 2).unwrap().into()),
583 start_timestamp: Annotated::new(
584 Utc.with_ymd_and_hms(1987, 6, 5, 4, 3, 2).unwrap().into(),
585 ),
586 contexts: {
587 let mut contexts = Contexts::new();
588 contexts.add(TraceContext {
589 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
590 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
591 op: Annotated::new("http.server".to_owned()),
592 ..Default::default()
593 });
594 Annotated::new(contexts)
595 },
596 ..Event::default()
597 });
598 normalize_event(&mut event, &NormalizationConfig::default());
599 assert_eq!(get_value!(event.level), Some(&Level::Info));
600 }
601
602 #[test]
603 fn test_environment_tag_is_moved() {
604 let mut event = Annotated::new(Event {
605 tags: Annotated::new(Tags(PairList(vec![Annotated::new(TagEntry(
606 Annotated::new("environment".to_string()),
607 Annotated::new("despacito".to_string()),
608 ))]))),
609 ..Event::default()
610 });
611
612 normalize_event(&mut event, &NormalizationConfig::default());
613
614 let event = event.value().unwrap();
615
616 assert_eq!(event.environment.as_str(), Some("despacito"));
617 assert_eq!(event.tags.value(), Some(&Tags(vec![].into())));
618 }
619
620 #[test]
621 fn test_empty_environment_is_removed_and_overwritten_with_tag() {
622 let mut event = Annotated::new(Event {
623 tags: Annotated::new(Tags(PairList(vec![Annotated::new(TagEntry(
624 Annotated::new("environment".to_string()),
625 Annotated::new("despacito".to_string()),
626 ))]))),
627 environment: Annotated::new("".to_string()),
628 ..Event::default()
629 });
630
631 normalize_event(&mut event, &NormalizationConfig::default());
632
633 let event = event.value().unwrap();
634
635 assert_eq!(event.environment.as_str(), Some("despacito"));
636 assert_eq!(event.tags.value(), Some(&Tags(vec![].into())));
637 }
638
639 #[test]
640 fn test_empty_environment_is_removed() {
641 let mut event = Annotated::new(Event {
642 environment: Annotated::new("".to_string()),
643 ..Event::default()
644 });
645
646 normalize_event(&mut event, &NormalizationConfig::default());
647 assert_eq!(get_value!(event.environment), None);
648 }
649 #[test]
650 fn test_replay_id_added_from_dsc() {
651 let replay_id = Uuid::new_v4();
652 let mut event = Annotated::new(Event {
653 contexts: Annotated::new(Contexts(Object::new())),
654 ..Event::default()
655 });
656 normalize_event(
657 &mut event,
658 &NormalizationConfig {
659 replay_id: Some(replay_id),
660 ..Default::default()
661 },
662 );
663
664 let event = event.value().unwrap();
665
666 assert_eq!(event.contexts, {
667 let mut contexts = Contexts::new();
668 contexts.add(ReplayContext {
669 replay_id: Annotated::new(EventId(replay_id)),
670 other: Object::default(),
671 });
672 Annotated::new(contexts)
673 })
674 }
675
676 #[test]
677 fn test_none_environment_errors() {
678 let mut event = Annotated::new(Event {
679 environment: Annotated::new("none".to_string()),
680 ..Event::default()
681 });
682
683 normalize_event(&mut event, &NormalizationConfig::default());
684
685 let environment = get_path!(event.environment!);
686 let expected_original = &Value::String("none".to_string());
687
688 assert_eq!(
689 environment.meta().iter_errors().collect::<Vec<&Error>>(),
690 vec![&Error::new(ErrorKind::InvalidData)],
691 );
692 assert_eq!(
693 environment.meta().original_value().unwrap(),
694 expected_original
695 );
696 assert_eq!(environment.value(), None);
697 }
698
699 #[test]
700 fn test_invalid_release_removed() {
701 let mut event = Annotated::new(Event {
702 release: Annotated::new(LenientString("Latest".to_string())),
703 ..Event::default()
704 });
705
706 normalize_event(&mut event, &NormalizationConfig::default());
707
708 let release = get_path!(event.release!);
709 let expected_original = &Value::String("Latest".to_string());
710
711 assert_eq!(
712 release.meta().iter_errors().collect::<Vec<&Error>>(),
713 vec![&Error::new(ErrorKind::InvalidData)],
714 );
715 assert_eq!(release.meta().original_value().unwrap(), expected_original);
716 assert_eq!(release.value(), None);
717 }
718
719 #[test]
720 fn test_top_level_keys_moved_into_tags() {
721 let mut event = Annotated::new(Event {
722 server_name: Annotated::new("foo".to_string()),
723 site: Annotated::new("foo".to_string()),
724 tags: Annotated::new(Tags(PairList(vec![
725 Annotated::new(TagEntry(
726 Annotated::new("site".to_string()),
727 Annotated::new("old".to_string()),
728 )),
729 Annotated::new(TagEntry(
730 Annotated::new("server_name".to_string()),
731 Annotated::new("old".to_string()),
732 )),
733 ]))),
734 ..Event::default()
735 });
736
737 normalize_event(&mut event, &NormalizationConfig::default());
738
739 assert_eq!(get_value!(event.site), None);
740 assert_eq!(get_value!(event.server_name), None);
741
742 assert_eq!(
743 get_value!(event.tags!),
744 &Tags(PairList(vec![
745 Annotated::new(TagEntry(
746 Annotated::new("site".to_string()),
747 Annotated::new("foo".to_string()),
748 )),
749 Annotated::new(TagEntry(
750 Annotated::new("server_name".to_string()),
751 Annotated::new("foo".to_string()),
752 )),
753 ]))
754 );
755 }
756
757 #[test]
758 fn test_internal_tags_removed() {
759 let mut event = Annotated::new(Event {
760 tags: Annotated::new(Tags(PairList(vec![
761 Annotated::new(TagEntry(
762 Annotated::new("release".to_string()),
763 Annotated::new("foo".to_string()),
764 )),
765 Annotated::new(TagEntry(
766 Annotated::new("dist".to_string()),
767 Annotated::new("foo".to_string()),
768 )),
769 Annotated::new(TagEntry(
770 Annotated::new("user".to_string()),
771 Annotated::new("foo".to_string()),
772 )),
773 Annotated::new(TagEntry(
774 Annotated::new("filename".to_string()),
775 Annotated::new("foo".to_string()),
776 )),
777 Annotated::new(TagEntry(
778 Annotated::new("function".to_string()),
779 Annotated::new("foo".to_string()),
780 )),
781 Annotated::new(TagEntry(
782 Annotated::new("something".to_string()),
783 Annotated::new("else".to_string()),
784 )),
785 ]))),
786 ..Event::default()
787 });
788
789 normalize_event(&mut event, &NormalizationConfig::default());
790
791 assert_eq!(get_value!(event.tags!).len(), 1);
792 }
793
794 #[test]
795 fn test_empty_tags_removed() {
796 let mut event = Annotated::new(Event {
797 tags: Annotated::new(Tags(PairList(vec![
798 Annotated::new(TagEntry(
799 Annotated::new("".to_string()),
800 Annotated::new("foo".to_string()),
801 )),
802 Annotated::new(TagEntry(
803 Annotated::new("foo".to_string()),
804 Annotated::new("".to_string()),
805 )),
806 Annotated::new(TagEntry(
807 Annotated::new("something".to_string()),
808 Annotated::new("else".to_string()),
809 )),
810 ]))),
811 ..Event::default()
812 });
813
814 normalize_event(&mut event, &NormalizationConfig::default());
815
816 assert_eq!(
817 get_value!(event.tags!),
818 &Tags(PairList(vec![
819 Annotated::new(TagEntry(
820 Annotated::from_error(Error::nonempty(), None),
821 Annotated::new("foo".to_string()),
822 )),
823 Annotated::new(TagEntry(
824 Annotated::new("foo".to_string()),
825 Annotated::from_error(Error::nonempty(), None),
826 )),
827 Annotated::new(TagEntry(
828 Annotated::new("something".to_string()),
829 Annotated::new("else".to_string()),
830 )),
831 ]))
832 );
833 }
834
835 #[test]
836 fn test_tags_deduplicated() {
837 let mut event = Annotated::new(Event {
838 tags: Annotated::new(Tags(PairList(vec![
839 Annotated::new(TagEntry(
840 Annotated::new("foo".to_string()),
841 Annotated::new("1".to_string()),
842 )),
843 Annotated::new(TagEntry(
844 Annotated::new("bar".to_string()),
845 Annotated::new("1".to_string()),
846 )),
847 Annotated::new(TagEntry(
848 Annotated::new("foo".to_string()),
849 Annotated::new("2".to_string()),
850 )),
851 Annotated::new(TagEntry(
852 Annotated::new("bar".to_string()),
853 Annotated::new("2".to_string()),
854 )),
855 Annotated::new(TagEntry(
856 Annotated::new("foo".to_string()),
857 Annotated::new("3".to_string()),
858 )),
859 ]))),
860 ..Event::default()
861 });
862
863 normalize_event(&mut event, &NormalizationConfig::default());
864
865 assert_eq!(
867 get_value!(event.tags!),
868 &Tags(PairList(vec![
869 Annotated::new(TagEntry(
870 Annotated::new("foo".to_string()),
871 Annotated::new("1".to_string()),
872 )),
873 Annotated::new(TagEntry(
874 Annotated::new("bar".to_string()),
875 Annotated::new("1".to_string()),
876 )),
877 ]))
878 );
879 }
880
881 #[test]
882 fn test_transaction_status_defaulted_to_unknown() {
883 let mut object = Object::new();
884 let trace_context = TraceContext {
885 status: Annotated::empty(),
887 ..TraceContext::default()
888 };
889 object.insert(
890 "trace".to_string(),
891 Annotated::new(ContextInner(Context::Trace(Box::new(trace_context)))),
892 );
893
894 let mut event = Annotated::new(Event {
895 contexts: Annotated::new(Contexts(object)),
896 ..Event::default()
897 });
898 normalize_event(&mut event, &NormalizationConfig::default());
899
900 let event = event.value().unwrap();
901
902 let event_trace_context = event.context::<TraceContext>().unwrap();
903 assert_eq!(
904 event_trace_context.status,
905 Annotated::new(SpanStatus::Unknown)
906 )
907 }
908
909 #[test]
910 fn test_unknown_debug_image() {
911 let mut event = Annotated::new(Event {
912 debug_meta: Annotated::new(DebugMeta {
913 images: Annotated::new(vec![Annotated::new(DebugImage::Other(Object::default()))]),
914 ..DebugMeta::default()
915 }),
916 ..Event::default()
917 });
918
919 normalize_event(&mut event, &NormalizationConfig::default());
920
921 assert_eq!(
922 get_path!(event.debug_meta!),
923 &Annotated::new(DebugMeta {
924 images: Annotated::new(vec![Annotated::from_error(
925 Error::invalid("unsupported debug image type"),
926 Some(Value::Object(Object::default())),
927 )]),
928 ..DebugMeta::default()
929 })
930 );
931 }
932
933 #[test]
934 fn test_context_line_default() {
935 let mut frame = Annotated::new(Frame {
936 pre_context: Annotated::new(vec![Annotated::default(), Annotated::new("".to_string())]),
937 post_context: Annotated::new(vec![
938 Annotated::new("".to_string()),
939 Annotated::default(),
940 ]),
941 ..Frame::default()
942 });
943
944 normalize_non_raw_frame(&mut frame);
945
946 let frame = frame.value().unwrap();
947 assert_eq!(frame.context_line.as_str(), Some(""));
948 }
949
950 #[test]
951 fn test_context_line_retain() {
952 let mut frame = Annotated::new(Frame {
953 pre_context: Annotated::new(vec![Annotated::default(), Annotated::new("".to_string())]),
954 post_context: Annotated::new(vec![
955 Annotated::new("".to_string()),
956 Annotated::default(),
957 ]),
958 context_line: Annotated::new("some line".to_string()),
959 ..Frame::default()
960 });
961
962 normalize_non_raw_frame(&mut frame);
963
964 let frame = frame.value().unwrap();
965 assert_eq!(frame.context_line.as_str(), Some("some line"));
966 }
967
968 #[test]
969 fn test_frame_null_context_lines() {
970 let mut frame = Annotated::new(Frame {
971 pre_context: Annotated::new(vec![Annotated::default(), Annotated::new("".to_string())]),
972 post_context: Annotated::new(vec![
973 Annotated::new("".to_string()),
974 Annotated::default(),
975 ]),
976 ..Frame::default()
977 });
978
979 normalize_non_raw_frame(&mut frame);
980
981 assert_eq!(
982 *get_value!(frame.pre_context!),
983 vec![
984 Annotated::new("".to_string()),
985 Annotated::new("".to_string())
986 ],
987 );
988 assert_eq!(
989 *get_value!(frame.post_context!),
990 vec![
991 Annotated::new("".to_string()),
992 Annotated::new("".to_string())
993 ],
994 );
995 }
996
997 #[test]
998 fn test_too_long_tags() {
999 let mut event = Annotated::new(Event {
1000 tags: Annotated::new(Tags(PairList(
1001 vec![Annotated::new(TagEntry(
1002 Annotated::new("foobar".to_string()),
1003 Annotated::new("...........................................................................................................................................................................................................".to_string()),
1004 )), Annotated::new(TagEntry(
1005 Annotated::new("foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo".to_string()),
1006 Annotated::new("bar".to_string()),
1007 ))]),
1008 )),
1009 ..Event::default()
1010 });
1011
1012 normalize_event(&mut event, &NormalizationConfig::default());
1013
1014 assert_eq!(
1015 get_value!(event.tags!),
1016 &Tags(PairList(vec![
1017 Annotated::new(TagEntry(
1018 Annotated::new("foobar".to_string()),
1019 Annotated::from_error(Error::new(ErrorKind::ValueTooLong), None),
1020 )),
1021 Annotated::new(TagEntry(
1022 Annotated::from_error(Error::new(ErrorKind::ValueTooLong), None),
1023 Annotated::new("bar".to_string()),
1024 )),
1025 ]))
1026 );
1027 }
1028
1029 #[test]
1030 fn test_too_long_distribution() {
1031 let json = r#"{
1032 "event_id": "52df9022835246eeb317dbd739ccd059",
1033 "fingerprint": [
1034 "{{ default }}"
1035 ],
1036 "platform": "other",
1037 "dist": "52df9022835246eeb317dbd739ccd059-52df9022835246eeb317dbd739ccd059-52df9022835246eeb317dbd739ccd059"
1038}"#;
1039
1040 let mut event = Annotated::<Event>::from_json(json).unwrap();
1041
1042 normalize_event(&mut event, &NormalizationConfig::default());
1043
1044 let dist = &event.value().unwrap().dist;
1045 let result = &Annotated::<String>::from_error(
1046 Error::new(ErrorKind::ValueTooLong),
1047 Some(Value::String("52df9022835246eeb317dbd739ccd059-52df9022835246eeb317dbd739ccd059-52df9022835246eeb317dbd739ccd059".to_string()))
1048 );
1049 assert_eq!(dist, result);
1050 }
1051
1052 #[test]
1053 fn test_regression_backfills_abs_path_even_when_moving_stacktrace() {
1054 let mut event = Annotated::new(Event {
1055 exceptions: Annotated::new(Values::new(vec![Annotated::new(Exception {
1056 ty: Annotated::new("FooDivisionError".to_string()),
1057 value: Annotated::new("hi".to_string().into()),
1058 ..Exception::default()
1059 })])),
1060 stacktrace: Annotated::new(
1061 RawStacktrace {
1062 frames: Annotated::new(vec![Annotated::new(Frame {
1063 module: Annotated::new("MyModule".to_string()),
1064 filename: Annotated::new("MyFilename".into()),
1065 function: Annotated::new("Void FooBar()".to_string()),
1066 ..Frame::default()
1067 })]),
1068 ..RawStacktrace::default()
1069 }
1070 .into(),
1071 ),
1072 ..Event::default()
1073 });
1074
1075 normalize_event(&mut event, &NormalizationConfig::default());
1076
1077 assert_eq!(
1078 get_value!(event.exceptions.values[0].stacktrace!),
1079 &Stacktrace(RawStacktrace {
1080 frames: Annotated::new(vec![Annotated::new(Frame {
1081 module: Annotated::new("MyModule".to_string()),
1082 filename: Annotated::new("MyFilename".into()),
1083 abs_path: Annotated::new("MyFilename".into()),
1084 function: Annotated::new("Void FooBar()".to_string()),
1085 ..Frame::default()
1086 })]),
1087 ..RawStacktrace::default()
1088 })
1089 );
1090 }
1091
1092 #[test]
1093 fn test_parses_sdk_info_from_header() {
1094 let mut event = Annotated::new(Event::default());
1095
1096 normalize_event(
1097 &mut event,
1098 &NormalizationConfig {
1099 client: Some("_fooBar/0.0.0".to_string()),
1100 ..Default::default()
1101 },
1102 );
1103
1104 assert_eq!(
1105 get_path!(event.client_sdk!),
1106 &Annotated::new(ClientSdkInfo {
1107 name: Annotated::new("_fooBar".to_string()),
1108 version: Annotated::new("0.0.0".to_string()),
1109 ..ClientSdkInfo::default()
1110 })
1111 );
1112 }
1113
1114 #[test]
1115 fn test_discards_received() {
1116 let mut event = Annotated::new(Event {
1117 received: FromValue::from_value(Annotated::new(Value::U64(696_969_696_969))),
1118 ..Default::default()
1119 });
1120
1121 validate_event(&mut event, &EventValidationConfig::default()).unwrap();
1122 normalize_event(&mut event, &NormalizationConfig::default());
1123
1124 assert_eq!(get_value!(event.received!), get_value!(event.timestamp!));
1125 }
1126
1127 #[test]
1128 fn test_grouping_config() {
1129 let mut event = Annotated::new(Event {
1130 logentry: Annotated::from(LogEntry {
1131 message: Annotated::new("Hello World!".to_string().into()),
1132 ..Default::default()
1133 }),
1134 ..Default::default()
1135 });
1136
1137 validate_event(&mut event, &EventValidationConfig::default()).unwrap();
1138 normalize_event(
1139 &mut event,
1140 &NormalizationConfig {
1141 grouping_config: Some(json!({
1142 "id": "legacy:1234-12-12".to_string(),
1143 })),
1144 ..Default::default()
1145 },
1146 );
1147
1148 insta::assert_ron_snapshot!(SerializableAnnotated(&event), {
1149 ".event_id" => "[event-id]",
1150 ".received" => "[received]",
1151 ".timestamp" => "[timestamp]"
1152 }, @r#"
1153 {
1154 "event_id": "[event-id]",
1155 "level": "error",
1156 "type": "default",
1157 "logentry": {
1158 "formatted": "Hello World!",
1159 },
1160 "logger": "",
1161 "platform": "other",
1162 "timestamp": "[timestamp]",
1163 "received": "[received]",
1164 "grouping_config": {
1165 "id": "legacy:1234-12-12",
1166 },
1167 }
1168 "#);
1169 }
1170
1171 #[test]
1172 fn test_logentry_error() {
1173 let json = r#"
1174{
1175 "event_id": "74ad1301f4df489ead37d757295442b1",
1176 "timestamp": 1668148328.308933,
1177 "received": 1668148328.308933,
1178 "level": "error",
1179 "platform": "python",
1180 "logentry": {
1181 "params": [
1182 "bogus"
1183 ],
1184 "formatted": 42
1185 }
1186}
1187"#;
1188 let mut event = Annotated::<Event>::from_json(json).unwrap();
1189
1190 normalize_event(&mut event, &NormalizationConfig::default());
1191
1192 assert_json_snapshot!(SerializableAnnotated(&event), {".received" => "[received]"}, @r#"
1193 {
1194 "event_id": "74ad1301f4df489ead37d757295442b1",
1195 "level": "error",
1196 "type": "default",
1197 "logentry": null,
1198 "logger": "",
1199 "platform": "python",
1200 "timestamp": 1668148328.308933,
1201 "received": "[received]",
1202 "_meta": {
1203 "logentry": {
1204 "": {
1205 "err": [
1206 [
1207 "invalid_data",
1208 {
1209 "reason": "no message present"
1210 }
1211 ]
1212 ],
1213 "val": {
1214 "formatted": null,
1215 "message": null,
1216 "params": [
1217 "bogus"
1218 ]
1219 }
1220 }
1221 }
1222 }
1223 }"#)
1224 }
1225
1226 #[test]
1227 fn test_future_timestamp() {
1228 let mut event = Annotated::new(Event {
1229 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 3, 0, 2, 0).unwrap().into()),
1230 ..Default::default()
1231 });
1232
1233 let received_at = Some(Utc.with_ymd_and_hms(2000, 1, 3, 0, 0, 0).unwrap());
1234 let max_secs_in_past = Some(30 * 24 * 3600);
1235 let max_secs_in_future = Some(60);
1236
1237 validate_event(
1238 &mut event,
1239 &EventValidationConfig {
1240 received_at,
1241 max_secs_in_past,
1242 max_secs_in_future,
1243 is_validated: false,
1244 ..Default::default()
1245 },
1246 )
1247 .unwrap();
1248 normalize_event(&mut event, &NormalizationConfig::default());
1249
1250 insta::assert_ron_snapshot!(SerializableAnnotated(&event), {
1251 ".event_id" => "[event-id]",
1252 }, @r#"
1253 {
1254 "event_id": "[event-id]",
1255 "level": "error",
1256 "type": "default",
1257 "logger": "",
1258 "platform": "other",
1259 "timestamp": 946857600.0,
1260 "received": 946857600.0,
1261 "_meta": {
1262 "timestamp": {
1263 "": Meta(Some(MetaInner(
1264 err: [
1265 [
1266 "future_timestamp",
1267 {
1268 "sdk_time": "2000-01-03T00:02:00+00:00",
1269 "server_time": "2000-01-03T00:00:00+00:00",
1270 },
1271 ],
1272 ],
1273 ))),
1274 },
1275 },
1276 }
1277 "#);
1278 }
1279
1280 #[test]
1281 fn test_past_timestamp() {
1282 let mut event = Annotated::new(Event {
1283 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 3, 0, 0, 0).unwrap().into()),
1284 ..Default::default()
1285 });
1286
1287 let received_at = Some(Utc.with_ymd_and_hms(2000, 3, 3, 0, 0, 0).unwrap());
1288 let max_secs_in_past = Some(30 * 24 * 3600);
1289 let max_secs_in_future = Some(60);
1290
1291 validate_event(
1292 &mut event,
1293 &EventValidationConfig {
1294 received_at,
1295 max_secs_in_past,
1296 max_secs_in_future,
1297 is_validated: false,
1298 ..Default::default()
1299 },
1300 )
1301 .unwrap();
1302 normalize_event(&mut event, &NormalizationConfig::default());
1303
1304 insta::assert_ron_snapshot!(SerializableAnnotated(&event), {
1305 ".event_id" => "[event-id]",
1306 }, @r#"
1307 {
1308 "event_id": "[event-id]",
1309 "level": "error",
1310 "type": "default",
1311 "logger": "",
1312 "platform": "other",
1313 "timestamp": 952041600.0,
1314 "received": 952041600.0,
1315 "_meta": {
1316 "timestamp": {
1317 "": Meta(Some(MetaInner(
1318 err: [
1319 [
1320 "past_timestamp",
1321 {
1322 "sdk_time": "2000-01-03T00:00:00+00:00",
1323 "server_time": "2000-03-03T00:00:00+00:00",
1324 },
1325 ],
1326 ],
1327 ))),
1328 },
1329 },
1330 }
1331 "#);
1332 }
1333
1334 #[test]
1335 fn test_normalize_logger_empty() {
1336 let mut event = Event::from_value(
1337 serde_json::json!({
1338 "event_id": "7637af36578e4e4592692e28a1d6e2ca",
1339 "platform": "java",
1340 "logger": "",
1341 })
1342 .into(),
1343 );
1344
1345 normalize_event(&mut event, &NormalizationConfig::default());
1346 assert_annotated_snapshot!(event);
1347 }
1348
1349 #[test]
1350 fn test_normalize_logger_trimmed() {
1351 let mut event = Event::from_value(
1352 serde_json::json!({
1353 "event_id": "7637af36578e4e4592692e28a1d6e2ca",
1354 "platform": "java",
1355 "logger": " \t \t ",
1356 })
1357 .into(),
1358 );
1359
1360 normalize_event(&mut event, &NormalizationConfig::default());
1361 assert_annotated_snapshot!(event);
1362 }
1363
1364 #[test]
1365 fn test_normalize_logger_short_no_trimming() {
1366 let mut event = Event::from_value(
1367 serde_json::json!({
1368 "event_id": "7637af36578e4e4592692e28a1d6e2ca",
1369 "platform": "java",
1370 "logger": "my.short-logger.isnt_trimmed",
1371 })
1372 .into(),
1373 );
1374
1375 normalize_event(&mut event, &NormalizationConfig::default());
1376 assert_annotated_snapshot!(event);
1377 }
1378
1379 #[test]
1380 fn test_normalize_logger_exact_length() {
1381 let mut event = Event::from_value(
1382 serde_json::json!({
1383 "event_id": "7637af36578e4e4592692e28a1d6e2ca",
1384 "platform": "java",
1385 "logger": "this_is-exactly-the_max_len.012345678901234567890123456789012345",
1386 })
1387 .into(),
1388 );
1389
1390 normalize_event(&mut event, &NormalizationConfig::default());
1391 assert_annotated_snapshot!(event);
1392 }
1393
1394 #[test]
1395 fn test_normalize_logger_too_long_single_word() {
1396 let mut event = Event::from_value(
1397 serde_json::json!({
1398 "event_id": "7637af36578e4e4592692e28a1d6e2ca",
1399 "platform": "java",
1400 "logger": "this_is-way_too_long-and_we_only_have_one_word-so_we_cant_smart_trim",
1401 })
1402 .into(),
1403 );
1404
1405 normalize_event(&mut event, &NormalizationConfig::default());
1406 assert_annotated_snapshot!(event);
1407 }
1408
1409 #[test]
1410 fn test_normalize_logger_word_trimmed_at_max() {
1411 let mut event = Event::from_value(
1412 serde_json::json!({
1413 "event_id": "7637af36578e4e4592692e28a1d6e2ca",
1414 "platform": "java",
1415 "logger": "already_out.out.in.this_part-is-kept.this_right_here-is_an-extremely_long_word",
1416 })
1417 .into(),
1418 );
1419
1420 normalize_event(&mut event, &NormalizationConfig::default());
1421 assert_annotated_snapshot!(event);
1422 }
1423
1424 #[test]
1425 fn test_normalize_logger_word_trimmed_before_max() {
1426 let mut event = Event::from_value(
1429 serde_json::json!({
1430 "event_id": "7637af36578e4e4592692e28a1d6e2ca",
1431 "platform": "java",
1432 "logger": "super_out.this_is_already_out_too.this_part-is-kept.this_right_here-is_a-very_long_word",
1433 })
1434 .into(),
1435 );
1436
1437 normalize_event(&mut event, &NormalizationConfig::default());
1438 assert_annotated_snapshot!(event);
1439 }
1440
1441 #[test]
1442 fn test_normalize_logger_word_leading_dots() {
1443 let mut event = Event::from_value(
1444 serde_json::json!({
1445 "event_id": "7637af36578e4e4592692e28a1d6e2ca",
1446 "platform": "java",
1447 "logger": "io.this-tests-the-smart-trimming-and-word-removal-around-dot.words",
1448 })
1449 .into(),
1450 );
1451
1452 normalize_event(&mut event, &NormalizationConfig::default());
1453 assert_annotated_snapshot!(event);
1454 }
1455
1456 #[test]
1457 fn test_normalization_is_idempotent() {
1458 let start = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
1460 let end = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 10).unwrap();
1461 let mut event = Annotated::new(Event {
1462 ty: Annotated::new(EventType::Transaction),
1463 transaction: Annotated::new("/".to_owned()),
1464 timestamp: Annotated::new(end.into()),
1465 start_timestamp: Annotated::new(start.into()),
1466 contexts: {
1467 let mut contexts = Contexts::new();
1468 contexts.add(TraceContext {
1469 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
1470 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
1471 op: Annotated::new("http.server".to_owned()),
1472 ..Default::default()
1473 });
1474 Annotated::new(contexts)
1475 },
1476 spans: Annotated::new(vec![Annotated::new(Span {
1477 timestamp: Annotated::new(
1478 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 10).unwrap().into(),
1479 ),
1480 start_timestamp: Annotated::new(
1481 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
1482 ),
1483 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
1484 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
1485
1486 ..Default::default()
1487 })]),
1488 ..Default::default()
1489 });
1490
1491 fn remove_received_from_event(event: &mut Annotated<Event>) -> &mut Annotated<Event> {
1492 relay_event_schema::processor::apply(event, |e, _m| {
1493 e.received = Annotated::empty();
1494 Ok(())
1495 })
1496 .unwrap();
1497 event
1498 }
1499
1500 normalize_event(&mut event, &NormalizationConfig::default());
1501 let first = remove_received_from_event(&mut event.clone())
1502 .to_json()
1503 .unwrap();
1504 normalize_event(&mut event, &NormalizationConfig::default());
1507 let second = remove_received_from_event(&mut event.clone())
1508 .to_json()
1509 .unwrap();
1510 assert_eq!(&first, &second, "idempotency check failed");
1511
1512 normalize_event(&mut event, &NormalizationConfig::default());
1513 let third = remove_received_from_event(&mut event.clone())
1514 .to_json()
1515 .unwrap();
1516 assert_eq!(&second, &third, "idempotency check failed");
1517 }
1518
1519 #[test]
1526 fn test_full_normalization_is_idempotent() {
1527 let start = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
1528 let end = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 10).unwrap();
1529 let mut event = Annotated::new(Event {
1530 ty: Annotated::new(EventType::Transaction),
1531 transaction: Annotated::new("/".to_owned()),
1532 timestamp: Annotated::new(end.into()),
1533 start_timestamp: Annotated::new(start.into()),
1534 contexts: {
1535 let mut contexts = Contexts::new();
1536 contexts.add(TraceContext {
1537 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
1538 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
1539 op: Annotated::new("http.server".to_owned()),
1540 ..Default::default()
1541 });
1542 Annotated::new(contexts)
1543 },
1544 spans: Annotated::new(vec![Annotated::new(Span {
1545 timestamp: Annotated::new(
1546 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 10).unwrap().into(),
1547 ),
1548 start_timestamp: Annotated::new(
1549 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
1550 ),
1551 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
1552 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
1553
1554 ..Default::default()
1555 })]),
1556 ..Default::default()
1557 });
1558
1559 fn remove_received_from_event(event: &mut Annotated<Event>) -> &mut Annotated<Event> {
1560 relay_event_schema::processor::apply(event, |e, _m| {
1561 e.received = Annotated::empty();
1562 Ok(())
1563 })
1564 .unwrap();
1565 event
1566 }
1567
1568 let full_normalization_config = NormalizationConfig {
1569 is_renormalize: false,
1570 enable_trimming: true,
1571 remove_other: true,
1572 emit_event_errors: true,
1573 ..Default::default()
1574 };
1575
1576 normalize_event(&mut event, &full_normalization_config);
1577 let first = remove_received_from_event(&mut event.clone())
1578 .to_json()
1579 .unwrap();
1580 normalize_event(&mut event, &full_normalization_config);
1583 let second = remove_received_from_event(&mut event.clone())
1584 .to_json()
1585 .unwrap();
1586 assert_eq!(&first, &second, "idempotency check failed");
1587
1588 normalize_event(&mut event, &full_normalization_config);
1589 let third = remove_received_from_event(&mut event.clone())
1590 .to_json()
1591 .unwrap();
1592 assert_eq!(&second, &third, "idempotency check failed");
1593 }
1594
1595 #[test]
1596 fn test_normalize_validates_spans() {
1597 let event = Annotated::<Event>::from_json(
1598 r#"
1599 {
1600 "type": "transaction",
1601 "transaction": "/",
1602 "timestamp": 946684810.0,
1603 "start_timestamp": 946684800.0,
1604 "contexts": {
1605 "trace": {
1606 "trace_id": "4c79f60c11214eb38604f4ae0781bfb2",
1607 "span_id": "fa90fdead5f74053",
1608 "op": "http.server",
1609 "type": "trace"
1610 }
1611 },
1612 "spans": []
1613 }
1614 "#,
1615 )
1616 .unwrap();
1617
1618 for span in [
1620 r#"null"#,
1621 r#"{
1622 "timestamp": 946684810.0,
1623 "start_timestamp": 946684900.0,
1624 "span_id": "fa90fdead5f74053",
1625 "trace_id": "4c79f60c11214eb38604f4ae0781bfb2"
1626 }"#,
1627 r#"{
1628 "timestamp": 946684810.0,
1629 "span_id": "fa90fdead5f74053",
1630 "trace_id": "4c79f60c11214eb38604f4ae0781bfb2"
1631 }"#,
1632 r#"{
1633 "timestamp": 946684810.0,
1634 "start_timestamp": 946684800.0,
1635 "trace_id": "4c79f60c11214eb38604f4ae0781bfb2"
1636 }"#,
1637 r#"{
1638 "timestamp": 946684810.0,
1639 "start_timestamp": 946684800.0,
1640 "span_id": "fa90fdead5f74053"
1641 }"#,
1642 ] {
1643 let mut modified_event = event.clone();
1644 let event_ref = modified_event.value_mut().as_mut().unwrap();
1645 event_ref
1646 .spans
1647 .set_value(Some(vec![Annotated::<Span>::from_json(span).unwrap()]));
1648
1649 let res = validate_event(&mut modified_event, &EventValidationConfig::default());
1650
1651 assert!(res.is_err(), "{span:?}");
1652 }
1653 }
1654
1655 #[test]
1656 fn test_normalization_respects_is_renormalize() {
1657 let mut event = Annotated::<Event>::from_json(
1658 r#"
1659 {
1660 "type": "default",
1661 "tags": [["environment", "some_environment"]]
1662 }
1663 "#,
1664 )
1665 .unwrap();
1666
1667 normalize_event(
1668 &mut event,
1669 &NormalizationConfig {
1670 is_renormalize: true,
1671 ..Default::default()
1672 },
1673 );
1674
1675 assert_debug_snapshot!(event.value().unwrap().tags, @r#"
1676 Tags(
1677 PairList(
1678 [
1679 TagEntry(
1680 "environment",
1681 "some_environment",
1682 ),
1683 ],
1684 ),
1685 )
1686 "#);
1687 }
1688
1689 #[test]
1690 fn test_geo_in_normalize() {
1691 let mut event = Annotated::<Event>::from_json(
1692 r#"
1693 {
1694 "type": "transaction",
1695 "transaction": "/foo/",
1696 "timestamp": 946684810.0,
1697 "start_timestamp": 946684800.0,
1698 "contexts": {
1699 "trace": {
1700 "trace_id": "4c79f60c11214eb38604f4ae0781bfb2",
1701 "span_id": "fa90fdead5f74053",
1702 "op": "http.server",
1703 "type": "trace"
1704 }
1705 },
1706 "transaction_info": {
1707 "source": "url"
1708 },
1709 "user": {
1710 "ip_address": "2.125.160.216"
1711 }
1712 }
1713 "#,
1714 )
1715 .unwrap();
1716
1717 let lookup = GeoIpLookup::open("tests/fixtures/GeoIP2-Enterprise-Test.mmdb").unwrap();
1718
1719 let user_geo = event.value().unwrap().user.value().unwrap().geo.value();
1721 assert!(user_geo.is_none());
1722
1723 normalize_event(
1724 &mut event,
1725 &NormalizationConfig {
1726 geoip_lookup: Some(&lookup),
1727 ..Default::default()
1728 },
1729 );
1730
1731 let user_geo = event
1733 .value()
1734 .unwrap()
1735 .user
1736 .value()
1737 .unwrap()
1738 .geo
1739 .value()
1740 .unwrap();
1741
1742 assert_eq!(user_geo.country_code.value().unwrap(), "GB");
1743 assert_eq!(user_geo.city.value().unwrap(), "Boxford");
1744 }
1745
1746 #[test]
1747 fn test_normalize_app_start_spans_only_for_react_native_3_to_4_4() {
1748 let mut event = Event {
1749 spans: Annotated::new(vec![Annotated::new(Span {
1750 op: Annotated::new("app_start_cold".to_owned()),
1751 ..Default::default()
1752 })]),
1753 client_sdk: Annotated::new(ClientSdkInfo {
1754 name: Annotated::new("sentry.javascript.react-native".to_owned()),
1755 version: Annotated::new("4.5.0".to_owned()),
1756 ..Default::default()
1757 }),
1758 ..Default::default()
1759 };
1760 normalize_app_start_spans(&mut event);
1761 assert_debug_snapshot!(event.spans, @r###"
1762 [
1763 Span {
1764 timestamp: ~,
1765 start_timestamp: ~,
1766 exclusive_time: ~,
1767 op: "app_start_cold",
1768 span_id: ~,
1769 parent_span_id: ~,
1770 trace_id: ~,
1771 segment_id: ~,
1772 is_segment: ~,
1773 is_remote: ~,
1774 status: ~,
1775 description: ~,
1776 tags: ~,
1777 origin: ~,
1778 profile_id: ~,
1779 data: ~,
1780 links: ~,
1781 sentry_tags: ~,
1782 received: ~,
1783 measurements: ~,
1784 platform: ~,
1785 was_transaction: ~,
1786 kind: ~,
1787 _performance_issues_spans: ~,
1788 other: {},
1789 },
1790 ]
1791 "###);
1792 }
1793
1794 #[test]
1795 fn test_normalize_app_start_cold_spans_for_react_native() {
1796 let mut event = Event {
1797 spans: Annotated::new(vec![Annotated::new(Span {
1798 op: Annotated::new("app_start_cold".to_owned()),
1799 ..Default::default()
1800 })]),
1801 client_sdk: Annotated::new(ClientSdkInfo {
1802 name: Annotated::new("sentry.javascript.react-native".to_owned()),
1803 version: Annotated::new("4.4.0".to_owned()),
1804 ..Default::default()
1805 }),
1806 ..Default::default()
1807 };
1808 normalize_app_start_spans(&mut event);
1809 assert_debug_snapshot!(event.spans, @r###"
1810 [
1811 Span {
1812 timestamp: ~,
1813 start_timestamp: ~,
1814 exclusive_time: ~,
1815 op: "app.start.cold",
1816 span_id: ~,
1817 parent_span_id: ~,
1818 trace_id: ~,
1819 segment_id: ~,
1820 is_segment: ~,
1821 is_remote: ~,
1822 status: ~,
1823 description: ~,
1824 tags: ~,
1825 origin: ~,
1826 profile_id: ~,
1827 data: ~,
1828 links: ~,
1829 sentry_tags: ~,
1830 received: ~,
1831 measurements: ~,
1832 platform: ~,
1833 was_transaction: ~,
1834 kind: ~,
1835 _performance_issues_spans: ~,
1836 other: {},
1837 },
1838 ]
1839 "###);
1840 }
1841
1842 #[test]
1843 fn test_normalize_app_start_warm_spans_for_react_native() {
1844 let mut event = Event {
1845 spans: Annotated::new(vec![Annotated::new(Span {
1846 op: Annotated::new("app_start_warm".to_owned()),
1847 ..Default::default()
1848 })]),
1849 client_sdk: Annotated::new(ClientSdkInfo {
1850 name: Annotated::new("sentry.javascript.react-native".to_owned()),
1851 version: Annotated::new("4.4.0".to_owned()),
1852 ..Default::default()
1853 }),
1854 ..Default::default()
1855 };
1856 normalize_app_start_spans(&mut event);
1857 assert_debug_snapshot!(event.spans, @r###"
1858 [
1859 Span {
1860 timestamp: ~,
1861 start_timestamp: ~,
1862 exclusive_time: ~,
1863 op: "app.start.warm",
1864 span_id: ~,
1865 parent_span_id: ~,
1866 trace_id: ~,
1867 segment_id: ~,
1868 is_segment: ~,
1869 is_remote: ~,
1870 status: ~,
1871 description: ~,
1872 tags: ~,
1873 origin: ~,
1874 profile_id: ~,
1875 data: ~,
1876 links: ~,
1877 sentry_tags: ~,
1878 received: ~,
1879 measurements: ~,
1880 platform: ~,
1881 was_transaction: ~,
1882 kind: ~,
1883 _performance_issues_spans: ~,
1884 other: {},
1885 },
1886 ]
1887 "###);
1888 }
1889}