1use std::borrow::Cow;
2
3use relay_event_schema::processor::{
4 self, Chunk, ProcessValue, ProcessingAction, ProcessingResult, ProcessingState, Processor,
5 ValueType,
6};
7use relay_event_schema::protocol::{Frame, RawStacktrace, Replay};
8use relay_protocol::{Annotated, Array, Empty, Meta, Object, RemarkType, Value};
9
10#[derive(Clone, Debug)]
11struct SizeState {
12 max_depth: Option<usize>,
13 encountered_at_depth: usize,
14 size_remaining: Option<usize>,
15}
16
17#[derive(Default)]
19pub struct TrimmingProcessor {
20 size_state: Vec<SizeState>,
21}
22
23impl TrimmingProcessor {
24 pub fn new() -> Self {
26 Self::default()
27 }
28
29 fn should_remove_container<T: Empty>(&self, value: &T, state: &ProcessingState<'_>) -> bool {
30 self.remaining_depth(state) == Some(1) && !value.is_empty()
33 }
34
35 #[inline]
36 fn remaining_depth(&self, state: &ProcessingState<'_>) -> Option<usize> {
37 self.size_state
38 .iter()
39 .filter_map(|size_state| {
40 let current_depth = state.depth() - size_state.encountered_at_depth;
43 size_state
44 .max_depth
45 .map(|max_depth| max_depth.saturating_sub(current_depth))
46 })
47 .min()
48 }
49
50 #[inline]
51 fn remaining_size(&self) -> Option<usize> {
52 self.size_state
53 .iter()
54 .filter_map(|x| x.size_remaining)
55 .min()
56 }
57}
58
59impl Processor for TrimmingProcessor {
60 fn before_process<T: ProcessValue>(
61 &mut self,
62 _: Option<&T>,
63 _: &mut Meta,
64 state: &ProcessingState<'_>,
65 ) -> ProcessingResult {
66 if state.attrs().max_bytes.is_some() || state.attrs().max_depth.is_some() {
70 self.size_state.push(SizeState {
71 size_remaining: state.attrs().max_bytes,
72 encountered_at_depth: state.depth(),
73 max_depth: state.attrs().max_depth,
74 });
75 }
76
77 if state.attrs().trim {
78 if self.remaining_size() == Some(0) {
79 return Err(ProcessingAction::DeleteValueHard);
81 }
82 if self.remaining_depth(state) == Some(0) {
83 return Err(ProcessingAction::DeleteValueHard);
85 }
86 }
87 Ok(())
88 }
89
90 fn after_process<T: ProcessValue>(
91 &mut self,
92 value: Option<&T>,
93 _: &mut Meta,
94 state: &ProcessingState<'_>,
95 ) -> ProcessingResult {
96 if let Some(size_state) = self.size_state.last() {
97 if state.depth() == size_state.encountered_at_depth {
100 self.size_state.pop().unwrap();
101 }
102 }
103
104 for size_state in self.size_state.iter_mut() {
105 if state.entered_anything() {
114 let item_length = relay_protocol::estimate_size_flat(value) + 1;
116 size_state.size_remaining = size_state
117 .size_remaining
118 .map(|size| size.saturating_sub(item_length));
119 }
120 }
121
122 Ok(())
123 }
124
125 fn process_string(
126 &mut self,
127 value: &mut String,
128 meta: &mut Meta,
129 state: &ProcessingState<'_>,
130 ) -> ProcessingResult {
131 if let Some(max_chars) = state.attrs().max_chars {
132 trim_string(value, meta, max_chars, state.attrs().max_chars_allowance);
133 }
134
135 if !state.attrs().trim {
136 return Ok(());
137 }
138
139 if let Some(size_state) = self.size_state.last() {
140 if let Some(size_remaining) = size_state.size_remaining {
141 trim_string(value, meta, size_remaining, 0);
142 }
143 }
144
145 Ok(())
146 }
147
148 fn process_array<T>(
149 &mut self,
150 value: &mut Array<T>,
151 meta: &mut Meta,
152 state: &ProcessingState<'_>,
153 ) -> ProcessingResult
154 where
155 T: ProcessValue,
156 {
157 if !state.attrs().trim {
158 return Ok(());
159 }
160
161 if !self.size_state.is_empty() {
163 let original_length = value.len();
164
165 if self.should_remove_container(value, state) {
166 return Err(ProcessingAction::DeleteValueHard);
167 }
168
169 let mut split_index = None;
170 for (index, item) in value.iter_mut().enumerate() {
171 if self.remaining_size() == Some(0) {
172 split_index = Some(index);
173 break;
174 }
175
176 let item_state = state.enter_index(index, None, ValueType::for_field(item));
177 processor::process_value(item, self, &item_state)?;
178 }
179
180 if let Some(split_index) = split_index {
181 let _ = value.split_off(split_index);
182 }
183
184 if value.len() != original_length {
185 meta.set_original_length(Some(original_length));
186 }
187 } else {
188 value.process_child_values(self, state)?;
189 }
190
191 Ok(())
192 }
193
194 fn process_object<T>(
195 &mut self,
196 value: &mut Object<T>,
197 meta: &mut Meta,
198 state: &ProcessingState<'_>,
199 ) -> ProcessingResult
200 where
201 T: ProcessValue,
202 {
203 if !state.attrs().trim {
204 return Ok(());
205 }
206
207 if !self.size_state.is_empty() {
209 let original_length = value.len();
210
211 if self.should_remove_container(value, state) {
212 return Err(ProcessingAction::DeleteValueHard);
213 }
214
215 let mut split_key = None;
216 for (key, item) in value.iter_mut() {
217 if self.remaining_size() == Some(0) {
218 split_key = Some(key.to_owned());
219 break;
220 }
221
222 let item_state = state.enter_borrowed(key, None, ValueType::for_field(item));
223 processor::process_value(item, self, &item_state)?;
224 }
225
226 if let Some(split_key) = split_key {
227 let _ = value.split_off(&split_key);
228 }
229
230 if value.len() != original_length {
231 meta.set_original_length(Some(original_length));
232 }
233 } else {
234 value.process_child_values(self, state)?;
235 }
236
237 Ok(())
238 }
239
240 fn process_value(
241 &mut self,
242 value: &mut Value,
243 _meta: &mut Meta,
244 state: &ProcessingState<'_>,
245 ) -> ProcessingResult {
246 if !state.attrs().trim {
247 return Ok(());
248 }
249
250 match value {
251 Value::Array(_) | Value::Object(_) => {
252 if self.remaining_depth(state) == Some(1) {
253 if let Ok(x) = serde_json::to_string(&value) {
254 *value = Value::String(x);
256 }
257 }
258 }
259 _ => (),
260 }
261
262 value.process_child_values(self, state)?;
263 Ok(())
264 }
265
266 fn process_replay(
267 &mut self,
268 replay: &mut Replay,
269 _: &mut Meta,
270 state: &ProcessingState<'_>,
271 ) -> ProcessingResult {
272 replay.process_child_values(self, state)
273 }
274
275 fn process_raw_stacktrace(
276 &mut self,
277 stacktrace: &mut RawStacktrace,
278 _meta: &mut Meta,
279 state: &ProcessingState<'_>,
280 ) -> ProcessingResult {
281 if !state.attrs().trim {
282 return Ok(());
283 }
284
285 processor::apply(&mut stacktrace.frames, |frames, meta| {
286 enforce_frame_hard_limit(frames, meta, 200, 50);
287 Ok(())
288 })?;
289
290 stacktrace.process_child_values(self, state)?;
291
292 processor::apply(&mut stacktrace.frames, |frames, _meta| {
293 slim_frame_data(frames, 50);
294 Ok(())
295 })?;
296
297 Ok(())
298 }
299}
300
301fn trim_string(value: &mut String, meta: &mut Meta, max_chars: usize, max_chars_allowance: usize) {
303 let hard_limit = max_chars + max_chars_allowance;
304
305 if bytecount::num_chars(value.as_bytes()) <= hard_limit {
306 return;
307 }
308
309 processor::process_chunked_value(value, meta, |chunks| {
310 let mut length = 0;
311 let mut new_chunks = vec![];
312
313 for chunk in chunks {
314 let chunk_chars = chunk.count();
315
316 if length + chunk_chars < max_chars {
318 new_chunks.push(chunk);
319 length += chunk_chars;
320 continue;
321 }
322
323 match chunk {
324 Chunk::Redaction { .. } => {
327 if length + chunk_chars + 3 < hard_limit {
328 new_chunks.push(chunk);
329 }
330 }
331
332 Chunk::Text { text } => {
334 let mut remaining = String::new();
335 for c in text.chars() {
336 if length + 3 < max_chars {
337 remaining.push(c);
338 } else {
339 break;
340 }
341 length += 1;
342 }
343
344 new_chunks.push(Chunk::Text {
345 text: Cow::Owned(remaining),
346 });
347 }
348 }
349
350 new_chunks.push(Chunk::Redaction {
351 text: Cow::Borrowed("..."),
352 rule_id: Cow::Borrowed("!limit"),
353 ty: RemarkType::Substituted,
354 });
355 break;
356 }
357
358 new_chunks
359 });
360}
361
362fn enforce_frame_hard_limit(
372 frames: &mut Array<Frame>,
373 meta: &mut Meta,
374 recent_frames: usize,
375 old_frames: usize,
376) {
377 let original_length = frames.len();
378 let limit = recent_frames + old_frames;
379 if original_length > limit {
380 meta.set_original_length(Some(original_length));
381 let _ = frames.drain(old_frames..original_length - recent_frames);
382 }
383}
384
385fn slim_frame_data(frames: &mut Array<Frame>, frame_allowance: usize) {
389 let frames_len = frames.len();
390
391 if frames_len <= frame_allowance {
392 return;
393 }
394
395 let mut app_frame_indices = Vec::with_capacity(frames_len);
397 let mut system_frame_indices = Vec::with_capacity(frames_len);
398
399 for (i, frame) in frames.iter().enumerate() {
400 if let Some(frame) = frame.value() {
401 match frame.in_app.value() {
402 Some(true) => app_frame_indices.push(i),
403 _ => system_frame_indices.push(i),
404 }
405 }
406 }
407
408 let app_count = app_frame_indices.len();
409 let system_allowance_half = frame_allowance.saturating_sub(app_count) / 2;
410 let system_frames_to_remove = system_frame_indices
411 .get(system_allowance_half..system_frame_indices.len() - system_allowance_half)
412 .unwrap_or(&[]);
413
414 let remaining = frames_len
415 .saturating_sub(frame_allowance)
416 .saturating_sub(system_frames_to_remove.len());
417 let app_allowance_half = app_count.saturating_sub(remaining) / 2;
418 let app_frames_to_remove = app_frame_indices
419 .get(app_allowance_half..app_frame_indices.len() - app_allowance_half)
420 .unwrap_or(&[]);
421
422 for i in system_frames_to_remove.iter().chain(app_frames_to_remove) {
425 if let Some(frame) = frames.get_mut(*i) {
426 if let Some(ref mut frame) = frame.value_mut().as_mut() {
427 frame.vars = Annotated::empty();
428 frame.pre_context = Annotated::empty();
429 frame.post_context = Annotated::empty();
430 }
431 }
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use std::iter::repeat_n;
438
439 use crate::MaxChars;
440 use chrono::DateTime;
441 use relay_event_schema::protocol::{
442 Breadcrumb, Context, Contexts, Event, Exception, ExtraValue, SentryTags, Span, SpanId,
443 TagEntry, Tags, Timestamp, TraceId, Values,
444 };
445 use relay_protocol::{Map, Remark, SerializableAnnotated, get_value};
446 use similar_asserts::assert_eq;
447
448 use super::*;
449
450 #[test]
451 fn test_string_trimming() {
452 let mut value = Annotated::new("This is my long string I want to have trimmed!".to_owned());
453 processor::apply(&mut value, |v, m| {
454 trim_string(v, m, 20, 0);
455 Ok(())
456 })
457 .unwrap();
458
459 assert_eq!(
460 value,
461 Annotated(Some("This is my long s...".into()), {
462 let mut meta = Meta::default();
463 meta.add_remark(Remark {
464 ty: RemarkType::Substituted,
465 rule_id: "!limit".to_owned(),
466 range: Some((17, 20)),
467 });
468 meta.set_original_length(Some(46));
469 meta
470 })
471 );
472 }
473
474 #[test]
475 fn test_basic_trimming() {
476 let mut processor = TrimmingProcessor::new();
477
478 let mut event = Annotated::new(Event {
479 logger: Annotated::new("x".repeat(300)),
480 ..Default::default()
481 });
482
483 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
484
485 let mut expected = Annotated::new("x".repeat(300));
486 processor::apply(&mut expected, |v, m| {
487 trim_string(v, m, MaxChars::Logger.limit(), 0);
488 Ok(())
489 })
490 .unwrap();
491
492 assert_eq!(event.value().unwrap().logger, expected);
493 }
494
495 #[test]
496 fn test_max_char_allowance() {
497 let string = "This string requires some allowance to fit!";
498 let mut value = Annotated::new(string.to_owned()); processor::apply(&mut value, |v, m| {
500 trim_string(v, m, 40, 5);
501 Ok(())
502 })
503 .unwrap();
504
505 assert_eq!(value, Annotated::new(string.to_owned()));
506 }
507
508 #[test]
509 fn test_databag_stripping() {
510 let mut processor = TrimmingProcessor::new();
511
512 fn make_nested_object(depth: usize) -> Annotated<Value> {
513 if depth == 0 {
514 return Annotated::new(Value::String("max depth".to_owned()));
515 }
516 let mut rv = Object::new();
517 rv.insert(format!("key{depth}"), make_nested_object(depth - 1));
518 Annotated::new(Value::Object(rv))
519 }
520
521 let databag = Annotated::new({
522 let mut map = Object::new();
523 map.insert(
524 "key_1".to_owned(),
525 Annotated::new(ExtraValue(Value::String("value 1".to_owned()))),
526 );
527 map.insert(
528 "key_2".to_owned(),
529 make_nested_object(8).map_value(ExtraValue),
530 );
531 map.insert(
532 "key_3".to_owned(),
533 make_nested_object(5).map_value(ExtraValue),
535 );
536 map
537 });
538 let mut event = Annotated::new(Event {
539 extra: databag,
540 ..Default::default()
541 });
542
543 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
544 let stripped_extra = &event.value().unwrap().extra;
545 let json = stripped_extra.to_json_pretty().unwrap();
546
547 assert_eq!(
548 json,
549 r#"{
550 "key_1": "value 1",
551 "key_2": {
552 "key8": {
553 "key7": {
554 "key6": {
555 "key5": {
556 "key4": "{\"key3\":{\"key2\":{\"key1\":\"max depth\"}}}"
557 }
558 }
559 }
560 }
561 },
562 "key_3": {
563 "key5": {
564 "key4": {
565 "key3": {
566 "key2": {
567 "key1": "max depth"
568 }
569 }
570 }
571 }
572 }
573}"#
574 );
575 }
576
577 #[test]
578 fn test_databag_array_stripping() {
579 let mut processor = TrimmingProcessor::new();
580
581 let databag = Annotated::new({
582 let mut map = Object::new();
583 for idx in 0..100 {
584 map.insert(
585 format!("key_{idx}"),
586 Annotated::new(ExtraValue(Value::String("x".repeat(50000)))),
587 );
588 }
589 map
590 });
591 let mut event = Annotated::new(Event {
592 extra: databag,
593 ..Default::default()
594 });
595
596 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
597 let stripped_extra = SerializableAnnotated(&event.value().unwrap().extra);
598
599 insta::assert_ron_snapshot!(stripped_extra);
600 }
601
602 #[test]
603 fn test_tags_stripping() {
604 let mut processor = TrimmingProcessor::new();
605
606 let mut event = Annotated::new(Event {
607 tags: Annotated::new(Tags(
608 vec![Annotated::new(TagEntry(
609 Annotated::new("x".repeat(300)),
610 Annotated::new("x".repeat(300)),
611 ))]
612 .into(),
613 )),
614 ..Default::default()
615 });
616
617 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
618 let json = event
619 .value()
620 .unwrap()
621 .tags
622 .payload_to_json_pretty()
623 .unwrap();
624
625 assert_eq!(
626 json,
627 r#"[
628 [
629 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...",
630 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx..."
631 ]
632]"#
633 );
634 }
635
636 #[test]
637 fn test_databag_state_leak() {
638 let event = Annotated::new(Event {
639 breadcrumbs: Annotated::new(Values::new(
640 repeat_n(
641 Annotated::new(Breadcrumb {
642 data: {
643 let mut map = Map::new();
644 map.insert(
645 "spamspamspam".to_owned(),
646 Annotated::new(Value::String("blablabla".to_owned())),
647 );
648 Annotated::new(map)
649 },
650 ..Default::default()
651 }),
652 200,
653 )
654 .collect(),
655 )),
656 exceptions: Annotated::new(Values::new(vec![Annotated::new(Exception {
657 ty: Annotated::new("TypeError".to_owned()),
658 value: Annotated::new("important error message".to_owned().into()),
659 stacktrace: Annotated::new(
660 RawStacktrace {
661 frames: Annotated::new(
662 repeat_n(
663 Annotated::new(Frame {
664 function: Annotated::new("importantFunctionName".to_owned()),
665 symbol: Annotated::new("important_symbol".to_owned()),
666 ..Default::default()
667 }),
668 200,
669 )
670 .collect(),
671 ),
672 ..Default::default()
673 }
674 .into(),
675 ),
676 ..Default::default()
677 })])),
678 ..Default::default()
679 });
680
681 let mut processor = TrimmingProcessor::new();
682 let mut stripped_event = event.clone();
683 processor::process_value(&mut stripped_event, &mut processor, ProcessingState::root())
684 .unwrap();
685
686 assert_eq!(
687 event.to_json_pretty().unwrap(),
688 stripped_event.to_json_pretty().unwrap()
689 );
690 }
691
692 #[test]
693 fn test_custom_context_trimming() {
694 let mut contexts = Contexts::new();
695 for i in 1..2 {
696 contexts.insert(format!("despacito{i}"), {
697 let mut context = Object::new();
698 context.insert(
699 "foo".to_owned(),
700 Annotated::new(Value::String("a".repeat(4000))),
701 );
702 context.insert(
703 "bar".to_owned(),
704 Annotated::new(Value::String("a".repeat(5000))),
705 );
706 Context::Other(context)
707 });
708 }
709
710 let mut contexts = Annotated::new(contexts);
711 let mut processor = TrimmingProcessor::new();
712 processor::process_value(&mut contexts, &mut processor, ProcessingState::root()).unwrap();
713
714 let contexts = contexts.value().unwrap();
715 for i in 1..2 {
716 let other = match contexts.get_key(format!("despacito{i}")).unwrap() {
717 Context::Other(x) => x,
718 _ => panic!("Context has changed type!"),
719 };
720
721 assert_eq!(
722 other
723 .get("bar")
724 .unwrap()
725 .value()
726 .unwrap()
727 .as_str()
728 .unwrap()
729 .len(),
730 5000
731 );
732 assert_eq!(
733 other
734 .get("foo")
735 .unwrap()
736 .value()
737 .unwrap()
738 .as_str()
739 .unwrap()
740 .len(),
741 3189
742 );
743 }
744 }
745
746 #[test]
747 fn test_extra_trimming_long_arrays() {
748 let mut extra = Object::new();
749 extra.insert("foo".to_owned(), {
750 Annotated::new(ExtraValue(Value::Array(
751 repeat_n(Annotated::new(Value::U64(1)), 200_000).collect(),
752 )))
753 });
754
755 let mut event = Annotated::new(Event {
756 extra: Annotated::new(extra),
757 ..Default::default()
758 });
759
760 let mut processor = TrimmingProcessor::new();
761 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
762
763 let arr = match event
764 .value()
765 .unwrap()
766 .extra
767 .value()
768 .unwrap()
769 .get("foo")
770 .unwrap()
771 .value()
772 .unwrap()
773 {
774 ExtraValue(Value::Array(x)) => x,
775 x => panic!("Wrong type: {x:?}"),
776 };
777
778 assert_eq!(arr.len(), 8192);
780 }
781
782 #[test]
825 fn test_frameqty_equals_limit() {
826 fn create_frame(filename: &str) -> Annotated<Frame> {
827 Annotated::new(Frame {
828 filename: Annotated::new(filename.into()),
829 ..Default::default()
830 })
831 }
832
833 let mut frames = Annotated::new(vec![
834 create_frame("foo3.py"),
835 create_frame("foo4.py"),
836 create_frame("foo5.py"),
837 ]);
838
839 processor::apply(&mut frames, |f, m| {
840 enforce_frame_hard_limit(f, m, 3, 0);
841 Ok(())
842 })
843 .unwrap();
844
845 processor::apply(&mut frames, |f, m| {
846 enforce_frame_hard_limit(f, m, 1, 2);
847 Ok(())
848 })
849 .unwrap();
850
851 assert!(frames.meta().original_length().is_none());
853 }
854
855 #[test]
856 fn test_frame_hard_limit() {
857 fn create_frame(filename: &str) -> Annotated<Frame> {
858 Annotated::new(Frame {
859 filename: Annotated::new(filename.into()),
860 ..Default::default()
861 })
862 }
863
864 let mut frames = Annotated::new(vec![
865 create_frame("foo1.py"),
866 create_frame("foo2.py"),
867 create_frame("foo3.py"),
868 create_frame("foo4.py"),
869 create_frame("foo5.py"),
870 ]);
871
872 processor::apply(&mut frames, |f, m| {
873 enforce_frame_hard_limit(f, m, 3, 0);
874 Ok(())
875 })
876 .unwrap();
877
878 let mut expected_meta = Meta::default();
879 expected_meta.set_original_length(Some(5));
880
881 assert_eq!(
882 frames,
883 Annotated(
884 Some(vec![
885 create_frame("foo3.py"),
886 create_frame("foo4.py"),
887 create_frame("foo5.py"),
888 ]),
889 expected_meta
890 )
891 );
892 }
893
894 #[test]
895 fn test_frame_hard_limit_recent_old() {
896 fn create_frame(filename: &str) -> Annotated<Frame> {
897 Annotated::new(Frame {
898 filename: Annotated::new(filename.into()),
899 ..Default::default()
900 })
901 }
902
903 let mut frames = Annotated::new(vec![
904 create_frame("foo1.py"),
905 create_frame("foo2.py"),
906 create_frame("foo3.py"),
907 create_frame("foo4.py"),
908 create_frame("foo5.py"),
909 ]);
910
911 processor::apply(&mut frames, |f, m| {
912 enforce_frame_hard_limit(f, m, 2, 1);
913 Ok(())
914 })
915 .unwrap();
916
917 let mut expected_meta = Meta::default();
918 expected_meta.set_original_length(Some(5));
919
920 assert_eq!(
921 frames,
922 Annotated(
923 Some(vec![
924 create_frame("foo1.py"),
925 create_frame("foo4.py"),
926 create_frame("foo5.py"),
927 ]),
928 expected_meta
929 )
930 );
931 }
932
933 #[test]
934 fn test_slim_frame_data_under_max() {
935 let mut frames = vec![Annotated::new(Frame {
936 filename: Annotated::new("foo".into()),
937 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
938 context_line: Annotated::new("b".to_owned()),
939 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
940 ..Default::default()
941 })];
942
943 let old_frames = frames.clone();
944 slim_frame_data(&mut frames, 4);
945
946 assert_eq!(frames, old_frames);
947 }
948
949 #[test]
950 fn test_slim_frame_data_over_max() {
951 let mut frames = vec![];
952
953 for n in 0..5 {
954 frames.push(Annotated::new(Frame {
955 filename: Annotated::new(format!("foo {n}").into()),
956 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
957 context_line: Annotated::new("b".to_owned()),
958 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
959 ..Default::default()
960 }));
961 }
962
963 slim_frame_data(&mut frames, 4);
964
965 let expected = vec![
966 Annotated::new(Frame {
967 filename: Annotated::new("foo 0".into()),
968 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
969 context_line: Annotated::new("b".to_owned()),
970 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
971 ..Default::default()
972 }),
973 Annotated::new(Frame {
974 filename: Annotated::new("foo 1".into()),
975 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
976 context_line: Annotated::new("b".to_owned()),
977 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
978 ..Default::default()
979 }),
980 Annotated::new(Frame {
981 filename: Annotated::new("foo 2".into()),
982 context_line: Annotated::new("b".to_owned()),
983 ..Default::default()
984 }),
985 Annotated::new(Frame {
986 filename: Annotated::new("foo 3".into()),
987 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
988 context_line: Annotated::new("b".to_owned()),
989 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
990 ..Default::default()
991 }),
992 Annotated::new(Frame {
993 filename: Annotated::new("foo 4".into()),
994 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
995 context_line: Annotated::new("b".to_owned()),
996 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
997 ..Default::default()
998 }),
999 ];
1000
1001 assert_eq!(frames, expected);
1002 }
1003
1004 #[test]
1005 fn test_too_many_spans_trimmed() {
1006 let span = Span {
1007 platform: Annotated::new("a".repeat(1024 * 90)),
1008 sentry_tags: Annotated::new(SentryTags {
1009 release: Annotated::new("b".repeat(1024 * 100)),
1010 ..Default::default()
1011 }),
1012 ..Default::default()
1013 };
1014 let spans: Vec<_> = std::iter::repeat_with(|| Annotated::new(span.clone()))
1015 .take(10)
1016 .collect();
1017
1018 let mut event = Annotated::new(Event {
1019 spans: Annotated::new(spans.clone()),
1020 ..Default::default()
1021 });
1022
1023 let mut processor = TrimmingProcessor::new();
1024 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
1025
1026 let trimmed_spans = event.0.unwrap().spans.0.unwrap();
1027 assert_eq!(trimmed_spans.len(), 5);
1028
1029 assert_eq!(trimmed_spans.as_slice(), &spans[0..5]);
1031 }
1032
1033 #[test]
1034 fn test_untrimmable_fields() {
1035 let original_description = "a".repeat(819163);
1036 let original_trace_id: TraceId = "b".repeat(32).parse().unwrap();
1037 let mut event = Annotated::new(Event {
1038 spans: Annotated::new(vec![
1039 Span {
1040 description: original_description.clone().into(),
1041 ..Default::default()
1042 }
1043 .into(),
1044 Span {
1045 trace_id: original_trace_id.into(),
1046 ..Default::default()
1047 }
1048 .into(),
1049 ]),
1050 ..Default::default()
1051 });
1052
1053 let mut processor = TrimmingProcessor::new();
1054 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
1055
1056 assert_eq!(
1057 get_value!(event.spans[0].description!),
1058 &original_description
1059 );
1060 assert_eq!(get_value!(event.spans[1].trace_id!), &original_trace_id);
1062 }
1063
1064 #[test]
1065 fn test_untrimmable_fields_drop() {
1066 let original_description = "a".repeat(819164);
1067 let original_span_id: SpanId = "b".repeat(16).parse().unwrap();
1068 let original_trace_id: TraceId = "c".repeat(32).parse().unwrap();
1069 let original_segment_id: SpanId = "d".repeat(16).parse().unwrap();
1070 let original_op = "e".repeat(129);
1071
1072 let mut event = Annotated::new(Event {
1073 spans: Annotated::new(vec![
1074 Span {
1075 description: original_description.clone().into(),
1076 ..Default::default()
1077 }
1078 .into(),
1079 Span {
1080 span_id: original_span_id.into(),
1081 trace_id: original_trace_id.into(),
1082 segment_id: original_segment_id.into(),
1083 is_segment: false.into(),
1084 op: original_op.clone().into(),
1085 start_timestamp: Timestamp(
1086 DateTime::parse_from_rfc3339("1996-12-19T16:39:57Z")
1087 .unwrap()
1088 .into(),
1089 )
1090 .into(),
1091 timestamp: Timestamp(
1092 DateTime::parse_from_rfc3339("1996-12-19T16:39:58Z")
1093 .unwrap()
1094 .into(),
1095 )
1096 .into(),
1097 ..Default::default()
1098 }
1099 .into(),
1100 ]),
1101 ..Default::default()
1102 });
1103
1104 let mut processor = TrimmingProcessor::new();
1105 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
1106
1107 assert_eq!(
1108 get_value!(event.spans[0].description!),
1109 &original_description
1110 );
1111 assert_eq!(get_value!(event.spans[1].span_id!), &original_span_id);
1113 assert_eq!(get_value!(event.spans[1].trace_id!), &original_trace_id);
1114 assert_eq!(get_value!(event.spans[1].segment_id!), &original_segment_id);
1115 assert_eq!(get_value!(event.spans[1].is_segment!), &false);
1116 assert_eq!(get_value!(event.spans[1].op!).len(), 128);
1118 assert!(get_value!(event.spans[1].start_timestamp).is_some());
1119 assert!(get_value!(event.spans[1].timestamp).is_some());
1120 }
1121}