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 =
453 Annotated::new("This is my long string I want to have trimmed!".to_string());
454 processor::apply(&mut value, |v, m| {
455 trim_string(v, m, 20, 0);
456 Ok(())
457 })
458 .unwrap();
459
460 assert_eq!(
461 value,
462 Annotated(Some("This is my long s...".into()), {
463 let mut meta = Meta::default();
464 meta.add_remark(Remark {
465 ty: RemarkType::Substituted,
466 rule_id: "!limit".to_string(),
467 range: Some((17, 20)),
468 });
469 meta.set_original_length(Some(46));
470 meta
471 })
472 );
473 }
474
475 #[test]
476 fn test_basic_trimming() {
477 let mut processor = TrimmingProcessor::new();
478
479 let mut event = Annotated::new(Event {
480 logger: Annotated::new("x".repeat(300)),
481 ..Default::default()
482 });
483
484 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
485
486 let mut expected = Annotated::new("x".repeat(300));
487 processor::apply(&mut expected, |v, m| {
488 trim_string(v, m, MaxChars::Logger.limit(), 0);
489 Ok(())
490 })
491 .unwrap();
492
493 assert_eq!(event.value().unwrap().logger, expected);
494 }
495
496 #[test]
497 fn test_max_char_allowance() {
498 let string = "This string requires some allowance to fit!";
499 let mut value = Annotated::new(string.to_owned()); processor::apply(&mut value, |v, m| {
501 trim_string(v, m, 40, 5);
502 Ok(())
503 })
504 .unwrap();
505
506 assert_eq!(value, Annotated::new(string.to_owned()));
507 }
508
509 #[test]
510 fn test_databag_stripping() {
511 let mut processor = TrimmingProcessor::new();
512
513 fn make_nested_object(depth: usize) -> Annotated<Value> {
514 if depth == 0 {
515 return Annotated::new(Value::String("max depth".to_string()));
516 }
517 let mut rv = Object::new();
518 rv.insert(format!("key{depth}"), make_nested_object(depth - 1));
519 Annotated::new(Value::Object(rv))
520 }
521
522 let databag = Annotated::new({
523 let mut map = Object::new();
524 map.insert(
525 "key_1".to_string(),
526 Annotated::new(ExtraValue(Value::String("value 1".to_string()))),
527 );
528 map.insert(
529 "key_2".to_string(),
530 make_nested_object(8).map_value(ExtraValue),
531 );
532 map.insert(
533 "key_3".to_string(),
534 make_nested_object(5).map_value(ExtraValue),
536 );
537 map
538 });
539 let mut event = Annotated::new(Event {
540 extra: databag,
541 ..Default::default()
542 });
543
544 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
545 let stripped_extra = &event.value().unwrap().extra;
546 let json = stripped_extra.to_json_pretty().unwrap();
547
548 assert_eq!(
549 json,
550 r#"{
551 "key_1": "value 1",
552 "key_2": {
553 "key8": {
554 "key7": {
555 "key6": {
556 "key5": {
557 "key4": "{\"key3\":{\"key2\":{\"key1\":\"max depth\"}}}"
558 }
559 }
560 }
561 }
562 },
563 "key_3": {
564 "key5": {
565 "key4": {
566 "key3": {
567 "key2": {
568 "key1": "max depth"
569 }
570 }
571 }
572 }
573 }
574}"#
575 );
576 }
577
578 #[test]
579 fn test_databag_array_stripping() {
580 let mut processor = TrimmingProcessor::new();
581
582 let databag = Annotated::new({
583 let mut map = Object::new();
584 for idx in 0..100 {
585 map.insert(
586 format!("key_{idx}"),
587 Annotated::new(ExtraValue(Value::String("x".repeat(50000)))),
588 );
589 }
590 map
591 });
592 let mut event = Annotated::new(Event {
593 extra: databag,
594 ..Default::default()
595 });
596
597 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
598 let stripped_extra = SerializableAnnotated(&event.value().unwrap().extra);
599
600 insta::assert_ron_snapshot!(stripped_extra);
601 }
602
603 #[test]
604 fn test_tags_stripping() {
605 let mut processor = TrimmingProcessor::new();
606
607 let mut event = Annotated::new(Event {
608 tags: Annotated::new(Tags(
609 vec![Annotated::new(TagEntry(
610 Annotated::new("x".repeat(300)),
611 Annotated::new("x".repeat(300)),
612 ))]
613 .into(),
614 )),
615 ..Default::default()
616 });
617
618 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
619 let json = event
620 .value()
621 .unwrap()
622 .tags
623 .payload_to_json_pretty()
624 .unwrap();
625
626 assert_eq!(
627 json,
628 r#"[
629 [
630 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...",
631 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx..."
632 ]
633]"#
634 );
635 }
636
637 #[test]
638 fn test_databag_state_leak() {
639 let event = Annotated::new(Event {
640 breadcrumbs: Annotated::new(Values::new(
641 repeat_n(
642 Annotated::new(Breadcrumb {
643 data: {
644 let mut map = Map::new();
645 map.insert(
646 "spamspamspam".to_string(),
647 Annotated::new(Value::String("blablabla".to_string())),
648 );
649 Annotated::new(map)
650 },
651 ..Default::default()
652 }),
653 200,
654 )
655 .collect(),
656 )),
657 exceptions: Annotated::new(Values::new(vec![Annotated::new(Exception {
658 ty: Annotated::new("TypeError".to_string()),
659 value: Annotated::new("important error message".to_string().into()),
660 stacktrace: Annotated::new(
661 RawStacktrace {
662 frames: Annotated::new(
663 repeat_n(
664 Annotated::new(Frame {
665 function: Annotated::new("importantFunctionName".to_string()),
666 symbol: Annotated::new("important_symbol".to_string()),
667 ..Default::default()
668 }),
669 200,
670 )
671 .collect(),
672 ),
673 ..Default::default()
674 }
675 .into(),
676 ),
677 ..Default::default()
678 })])),
679 ..Default::default()
680 });
681
682 let mut processor = TrimmingProcessor::new();
683 let mut stripped_event = event.clone();
684 processor::process_value(&mut stripped_event, &mut processor, ProcessingState::root())
685 .unwrap();
686
687 assert_eq!(
688 event.to_json_pretty().unwrap(),
689 stripped_event.to_json_pretty().unwrap()
690 );
691 }
692
693 #[test]
694 fn test_custom_context_trimming() {
695 let mut contexts = Contexts::new();
696 for i in 1..2 {
697 contexts.insert(format!("despacito{i}"), {
698 let mut context = Object::new();
699 context.insert(
700 "foo".to_string(),
701 Annotated::new(Value::String("a".repeat(4000))),
702 );
703 context.insert(
704 "bar".to_string(),
705 Annotated::new(Value::String("a".repeat(5000))),
706 );
707 Context::Other(context)
708 });
709 }
710
711 let mut contexts = Annotated::new(contexts);
712 let mut processor = TrimmingProcessor::new();
713 processor::process_value(&mut contexts, &mut processor, ProcessingState::root()).unwrap();
714
715 let contexts = contexts.value().unwrap();
716 for i in 1..2 {
717 let other = match contexts.get_key(format!("despacito{i}")).unwrap() {
718 Context::Other(x) => x,
719 _ => panic!("Context has changed type!"),
720 };
721
722 assert_eq!(
723 other
724 .get("bar")
725 .unwrap()
726 .value()
727 .unwrap()
728 .as_str()
729 .unwrap()
730 .len(),
731 5000
732 );
733 assert_eq!(
734 other
735 .get("foo")
736 .unwrap()
737 .value()
738 .unwrap()
739 .as_str()
740 .unwrap()
741 .len(),
742 3189
743 );
744 }
745 }
746
747 #[test]
748 fn test_extra_trimming_long_arrays() {
749 let mut extra = Object::new();
750 extra.insert("foo".to_string(), {
751 Annotated::new(ExtraValue(Value::Array(
752 repeat_n(Annotated::new(Value::U64(1)), 200_000).collect(),
753 )))
754 });
755
756 let mut event = Annotated::new(Event {
757 extra: Annotated::new(extra),
758 ..Default::default()
759 });
760
761 let mut processor = TrimmingProcessor::new();
762 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
763
764 let arr = match event
765 .value()
766 .unwrap()
767 .extra
768 .value()
769 .unwrap()
770 .get("foo")
771 .unwrap()
772 .value()
773 .unwrap()
774 {
775 ExtraValue(Value::Array(x)) => x,
776 x => panic!("Wrong type: {x:?}"),
777 };
778
779 assert_eq!(arr.len(), 8192);
781 }
782
783 #[test]
826 fn test_frameqty_equals_limit() {
827 fn create_frame(filename: &str) -> Annotated<Frame> {
828 Annotated::new(Frame {
829 filename: Annotated::new(filename.into()),
830 ..Default::default()
831 })
832 }
833
834 let mut frames = Annotated::new(vec![
835 create_frame("foo3.py"),
836 create_frame("foo4.py"),
837 create_frame("foo5.py"),
838 ]);
839
840 processor::apply(&mut frames, |f, m| {
841 enforce_frame_hard_limit(f, m, 3, 0);
842 Ok(())
843 })
844 .unwrap();
845
846 processor::apply(&mut frames, |f, m| {
847 enforce_frame_hard_limit(f, m, 1, 2);
848 Ok(())
849 })
850 .unwrap();
851
852 assert!(frames.meta().original_length().is_none());
854 }
855
856 #[test]
857 fn test_frame_hard_limit() {
858 fn create_frame(filename: &str) -> Annotated<Frame> {
859 Annotated::new(Frame {
860 filename: Annotated::new(filename.into()),
861 ..Default::default()
862 })
863 }
864
865 let mut frames = Annotated::new(vec![
866 create_frame("foo1.py"),
867 create_frame("foo2.py"),
868 create_frame("foo3.py"),
869 create_frame("foo4.py"),
870 create_frame("foo5.py"),
871 ]);
872
873 processor::apply(&mut frames, |f, m| {
874 enforce_frame_hard_limit(f, m, 3, 0);
875 Ok(())
876 })
877 .unwrap();
878
879 let mut expected_meta = Meta::default();
880 expected_meta.set_original_length(Some(5));
881
882 assert_eq!(
883 frames,
884 Annotated(
885 Some(vec![
886 create_frame("foo3.py"),
887 create_frame("foo4.py"),
888 create_frame("foo5.py"),
889 ]),
890 expected_meta
891 )
892 );
893 }
894
895 #[test]
896 fn test_frame_hard_limit_recent_old() {
897 fn create_frame(filename: &str) -> Annotated<Frame> {
898 Annotated::new(Frame {
899 filename: Annotated::new(filename.into()),
900 ..Default::default()
901 })
902 }
903
904 let mut frames = Annotated::new(vec![
905 create_frame("foo1.py"),
906 create_frame("foo2.py"),
907 create_frame("foo3.py"),
908 create_frame("foo4.py"),
909 create_frame("foo5.py"),
910 ]);
911
912 processor::apply(&mut frames, |f, m| {
913 enforce_frame_hard_limit(f, m, 2, 1);
914 Ok(())
915 })
916 .unwrap();
917
918 let mut expected_meta = Meta::default();
919 expected_meta.set_original_length(Some(5));
920
921 assert_eq!(
922 frames,
923 Annotated(
924 Some(vec![
925 create_frame("foo1.py"),
926 create_frame("foo4.py"),
927 create_frame("foo5.py"),
928 ]),
929 expected_meta
930 )
931 );
932 }
933
934 #[test]
935 fn test_slim_frame_data_under_max() {
936 let mut frames = vec![Annotated::new(Frame {
937 filename: Annotated::new("foo".into()),
938 pre_context: Annotated::new(vec![Annotated::new("a".to_string())]),
939 context_line: Annotated::new("b".to_string()),
940 post_context: Annotated::new(vec![Annotated::new("c".to_string())]),
941 ..Default::default()
942 })];
943
944 let old_frames = frames.clone();
945 slim_frame_data(&mut frames, 4);
946
947 assert_eq!(frames, old_frames);
948 }
949
950 #[test]
951 fn test_slim_frame_data_over_max() {
952 let mut frames = vec![];
953
954 for n in 0..5 {
955 frames.push(Annotated::new(Frame {
956 filename: Annotated::new(format!("foo {n}").into()),
957 pre_context: Annotated::new(vec![Annotated::new("a".to_string())]),
958 context_line: Annotated::new("b".to_string()),
959 post_context: Annotated::new(vec![Annotated::new("c".to_string())]),
960 ..Default::default()
961 }));
962 }
963
964 slim_frame_data(&mut frames, 4);
965
966 let expected = vec![
967 Annotated::new(Frame {
968 filename: Annotated::new("foo 0".into()),
969 pre_context: Annotated::new(vec![Annotated::new("a".to_string())]),
970 context_line: Annotated::new("b".to_string()),
971 post_context: Annotated::new(vec![Annotated::new("c".to_string())]),
972 ..Default::default()
973 }),
974 Annotated::new(Frame {
975 filename: Annotated::new("foo 1".into()),
976 pre_context: Annotated::new(vec![Annotated::new("a".to_string())]),
977 context_line: Annotated::new("b".to_string()),
978 post_context: Annotated::new(vec![Annotated::new("c".to_string())]),
979 ..Default::default()
980 }),
981 Annotated::new(Frame {
982 filename: Annotated::new("foo 2".into()),
983 context_line: Annotated::new("b".to_string()),
984 ..Default::default()
985 }),
986 Annotated::new(Frame {
987 filename: Annotated::new("foo 3".into()),
988 pre_context: Annotated::new(vec![Annotated::new("a".to_string())]),
989 context_line: Annotated::new("b".to_string()),
990 post_context: Annotated::new(vec![Annotated::new("c".to_string())]),
991 ..Default::default()
992 }),
993 Annotated::new(Frame {
994 filename: Annotated::new("foo 4".into()),
995 pre_context: Annotated::new(vec![Annotated::new("a".to_string())]),
996 context_line: Annotated::new("b".to_string()),
997 post_context: Annotated::new(vec![Annotated::new("c".to_string())]),
998 ..Default::default()
999 }),
1000 ];
1001
1002 assert_eq!(frames, expected);
1003 }
1004
1005 #[test]
1006 fn test_too_many_spans_trimmed() {
1007 let span = Span {
1008 platform: Annotated::new("a".repeat(1024 * 90)),
1009 sentry_tags: Annotated::new(SentryTags {
1010 release: Annotated::new("b".repeat(1024 * 100)),
1011 ..Default::default()
1012 }),
1013 ..Default::default()
1014 };
1015 let spans: Vec<_> = std::iter::repeat_with(|| Annotated::new(span.clone()))
1016 .take(10)
1017 .collect();
1018
1019 let mut event = Annotated::new(Event {
1020 spans: Annotated::new(spans.clone()),
1021 ..Default::default()
1022 });
1023
1024 let mut processor = TrimmingProcessor::new();
1025 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
1026
1027 let trimmed_spans = event.0.unwrap().spans.0.unwrap();
1028 assert_eq!(trimmed_spans.len(), 5);
1029
1030 assert_eq!(trimmed_spans.as_slice(), &spans[0..5]);
1032 }
1033
1034 #[test]
1035 fn test_untrimmable_fields() {
1036 let original_description = "a".repeat(819163);
1037 let original_trace_id: TraceId = "b".repeat(32).parse().unwrap();
1038 let mut event = Annotated::new(Event {
1039 spans: Annotated::new(vec![
1040 Span {
1041 description: original_description.clone().into(),
1042 ..Default::default()
1043 }
1044 .into(),
1045 Span {
1046 trace_id: original_trace_id.into(),
1047 ..Default::default()
1048 }
1049 .into(),
1050 ]),
1051 ..Default::default()
1052 });
1053
1054 let mut processor = TrimmingProcessor::new();
1055 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
1056
1057 assert_eq!(
1058 get_value!(event.spans[0].description!),
1059 &original_description
1060 );
1061 assert_eq!(get_value!(event.spans[1].trace_id!), &original_trace_id);
1063 }
1064
1065 #[test]
1066 fn test_untrimmable_fields_drop() {
1067 let original_description = "a".repeat(819164);
1068 let original_span_id = SpanId("b".repeat(48));
1069 let original_trace_id: TraceId = "c".repeat(32).parse().unwrap();
1070 let original_segment_id = SpanId("d".repeat(48));
1071 let original_op = "e".repeat(129);
1072
1073 let mut event = Annotated::new(Event {
1074 spans: Annotated::new(vec![
1075 Span {
1076 description: original_description.clone().into(),
1077 ..Default::default()
1078 }
1079 .into(),
1080 Span {
1081 span_id: original_span_id.clone().into(),
1082 trace_id: original_trace_id.into(),
1083 segment_id: original_segment_id.clone().into(),
1084 is_segment: false.into(),
1085 op: original_op.clone().into(),
1086 start_timestamp: Timestamp(
1087 DateTime::parse_from_rfc3339("1996-12-19T16:39:57Z")
1088 .unwrap()
1089 .into(),
1090 )
1091 .into(),
1092 timestamp: Timestamp(
1093 DateTime::parse_from_rfc3339("1996-12-19T16:39:58Z")
1094 .unwrap()
1095 .into(),
1096 )
1097 .into(),
1098 ..Default::default()
1099 }
1100 .into(),
1101 ]),
1102 ..Default::default()
1103 });
1104
1105 let mut processor = TrimmingProcessor::new();
1106 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
1107
1108 assert_eq!(
1109 get_value!(event.spans[0].description!),
1110 &original_description
1111 );
1112 assert_eq!(get_value!(event.spans[1].span_id!), &original_span_id);
1114 assert_eq!(get_value!(event.spans[1].trace_id!), &original_trace_id);
1115 assert_eq!(get_value!(event.spans[1].segment_id!), &original_segment_id);
1116 assert_eq!(get_value!(event.spans[1].is_segment!), &false);
1117 assert_eq!(get_value!(event.spans[1].op!).len(), 128);
1119 assert!(get_value!(event.spans[1].start_timestamp).is_some());
1120 assert!(get_value!(event.spans[1].timestamp).is_some());
1121 }
1122}