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.max_bytes().is_some() || state.attrs().max_depth.is_some() {
70 self.size_state.push(SizeState {
71 size_remaining: state.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 self.size_state
99 .pop_if(|size_state| state.depth() == size_state.encountered_at_depth);
100
101 if state.entered_anything() && !self.size_state.is_empty() {
109 let item_length = state
111 .bytes_size()
112 .unwrap_or_else(|| relay_protocol::estimate_size_flat(value) + 1);
113 for size_state in self.size_state.iter_mut() {
114 size_state.size_remaining = size_state
115 .size_remaining
116 .map(|size| size.saturating_sub(item_length));
117 }
118 }
119
120 Ok(())
121 }
122
123 fn process_string(
124 &mut self,
125 value: &mut String,
126 meta: &mut Meta,
127 state: &ProcessingState<'_>,
128 ) -> ProcessingResult {
129 if let Some(max_chars) = state.max_chars() {
130 trim_string(value, meta, max_chars, state.attrs().max_chars_allowance);
131 }
132
133 if !state.attrs().trim {
134 return Ok(());
135 }
136
137 if let Some(size_remaining) = self.remaining_size() {
138 trim_string(value, meta, size_remaining, 0);
139 }
140
141 Ok(())
142 }
143
144 fn process_array<T>(
145 &mut self,
146 value: &mut Array<T>,
147 meta: &mut Meta,
148 state: &ProcessingState<'_>,
149 ) -> ProcessingResult
150 where
151 T: ProcessValue,
152 {
153 if !state.attrs().trim {
154 return Ok(());
155 }
156
157 if !self.size_state.is_empty() {
159 let original_length = value.len();
160
161 if self.should_remove_container(value, state) {
162 return Err(ProcessingAction::DeleteValueHard);
163 }
164
165 let mut split_index = None;
166 for (index, item) in value.iter_mut().enumerate() {
167 if self.remaining_size() == Some(0) {
168 split_index = Some(index);
169 break;
170 }
171
172 let item_state = state.enter_index(index, None, ValueType::for_field(item));
173 processor::process_value(item, self, &item_state)?;
174 }
175
176 if let Some(split_index) = split_index {
177 let _ = value.split_off(split_index);
178 }
179
180 if value.len() != original_length {
181 meta.set_original_length(Some(original_length));
182 }
183 } else {
184 value.process_child_values(self, state)?;
185 }
186
187 Ok(())
188 }
189
190 fn process_object<T>(
191 &mut self,
192 value: &mut Object<T>,
193 meta: &mut Meta,
194 state: &ProcessingState<'_>,
195 ) -> ProcessingResult
196 where
197 T: ProcessValue,
198 {
199 if !state.attrs().trim {
200 return Ok(());
201 }
202
203 if !self.size_state.is_empty() {
205 let original_length = value.len();
206
207 if self.should_remove_container(value, state) {
208 return Err(ProcessingAction::DeleteValueHard);
209 }
210
211 let mut split_key = None;
212 for (key, item) in value.iter_mut() {
213 if self.remaining_size() == Some(0) {
214 split_key = Some(key.to_owned());
215 break;
216 }
217
218 let item_state = state.enter_borrowed(key, None, ValueType::for_field(item));
219 processor::process_value(item, self, &item_state)?;
220 }
221
222 if let Some(split_key) = split_key {
223 let _ = value.split_off(&split_key);
224 }
225
226 if value.len() != original_length {
227 meta.set_original_length(Some(original_length));
228 }
229 } else {
230 value.process_child_values(self, state)?;
231 }
232
233 Ok(())
234 }
235
236 fn process_value(
237 &mut self,
238 value: &mut Value,
239 _meta: &mut Meta,
240 state: &ProcessingState<'_>,
241 ) -> ProcessingResult {
242 if !state.attrs().trim {
243 return Ok(());
244 }
245
246 match value {
247 Value::Array(_) | Value::Object(_) => {
248 if self.remaining_depth(state) == Some(1)
249 && let Ok(x) = serde_json::to_string(&value)
250 {
251 *value = Value::String(x);
253 }
254 }
255 _ => (),
256 }
257
258 value.process_child_values(self, state)?;
259 Ok(())
260 }
261
262 fn process_replay(
263 &mut self,
264 replay: &mut Replay,
265 _: &mut Meta,
266 state: &ProcessingState<'_>,
267 ) -> ProcessingResult {
268 replay.process_child_values(self, state)
269 }
270
271 fn process_raw_stacktrace(
272 &mut self,
273 stacktrace: &mut RawStacktrace,
274 _meta: &mut Meta,
275 state: &ProcessingState<'_>,
276 ) -> ProcessingResult {
277 if !state.attrs().trim {
278 return Ok(());
279 }
280
281 processor::apply(&mut stacktrace.frames, |frames, meta| {
282 enforce_frame_hard_limit(frames, meta, 200, 50);
283 Ok(())
284 })?;
285
286 stacktrace.process_child_values(self, state)?;
287
288 processor::apply(&mut stacktrace.frames, |frames, _meta| {
289 slim_frame_data(frames, 50);
290 Ok(())
291 })?;
292
293 Ok(())
294 }
295}
296
297pub(crate) fn trim_string(
299 value: &mut String,
300 meta: &mut Meta,
301 max_chars: usize,
302 max_chars_allowance: usize,
303) {
304 let hard_limit = max_chars + max_chars_allowance;
305
306 if bytecount::num_chars(value.as_bytes()) <= hard_limit {
307 return;
308 }
309
310 processor::process_chunked_value(value, meta, |chunks| {
311 let mut length = 0;
312 let mut new_chunks = vec![];
313
314 for chunk in chunks {
315 let chunk_chars = chunk.count();
316
317 if length + chunk_chars < max_chars {
319 new_chunks.push(chunk);
320 length += chunk_chars;
321 continue;
322 }
323
324 match chunk {
325 Chunk::Redaction { .. } => {
328 if length + chunk_chars + 3 < hard_limit {
329 new_chunks.push(chunk);
330 }
331 }
332
333 Chunk::Text { text } => {
335 let mut remaining = String::new();
336 for c in text.chars() {
337 if length + 3 < max_chars {
338 remaining.push(c);
339 } else {
340 break;
341 }
342 length += 1;
343 }
344
345 new_chunks.push(Chunk::Text {
346 text: Cow::Owned(remaining),
347 });
348 }
349 }
350
351 new_chunks.push(Chunk::Redaction {
352 text: Cow::Borrowed("..."),
353 rule_id: Cow::Borrowed("!limit"),
354 ty: RemarkType::Substituted,
355 });
356 break;
357 }
358
359 new_chunks
360 });
361}
362
363fn enforce_frame_hard_limit(
373 frames: &mut Array<Frame>,
374 meta: &mut Meta,
375 recent_frames: usize,
376 old_frames: usize,
377) {
378 let original_length = frames.len();
379 let limit = recent_frames + old_frames;
380 if original_length > limit {
381 meta.set_original_length(Some(original_length));
382 let _ = frames.drain(old_frames..original_length - recent_frames);
383 }
384}
385
386fn slim_frame_data(frames: &mut Array<Frame>, frame_allowance: usize) {
390 let frames_len = frames.len();
391
392 if frames_len <= frame_allowance {
393 return;
394 }
395
396 let mut app_frame_indices = Vec::with_capacity(frames_len);
398 let mut system_frame_indices = Vec::with_capacity(frames_len);
399
400 for (i, frame) in frames.iter().enumerate() {
401 if let Some(frame) = frame.value() {
402 match frame.in_app.value() {
403 Some(true) => app_frame_indices.push(i),
404 _ => system_frame_indices.push(i),
405 }
406 }
407 }
408
409 let app_count = app_frame_indices.len();
410 let system_allowance_half = frame_allowance.saturating_sub(app_count) / 2;
411 let system_frames_to_remove = system_frame_indices
412 .get(system_allowance_half..system_frame_indices.len() - system_allowance_half)
413 .unwrap_or(&[]);
414
415 let remaining = frames_len
416 .saturating_sub(frame_allowance)
417 .saturating_sub(system_frames_to_remove.len());
418 let app_allowance_half = app_count.saturating_sub(remaining) / 2;
419 let app_frames_to_remove = app_frame_indices
420 .get(app_allowance_half..app_frame_indices.len() - app_allowance_half)
421 .unwrap_or(&[]);
422
423 for i in system_frames_to_remove.iter().chain(app_frames_to_remove) {
426 if let Some(frame) = frames.get_mut(*i)
427 && let Some(ref mut frame) = frame.value_mut().as_mut()
428 {
429 frame.vars = Annotated::empty();
430 frame.pre_context = Annotated::empty();
431 frame.post_context = Annotated::empty();
432 }
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use std::iter::repeat_n;
439
440 use crate::MaxChars;
441 use chrono::DateTime;
442 use relay_event_schema::protocol::{
443 Breadcrumb, Context, Contexts, Event, Exception, ExtraValue, PairList, SentryTags, Span,
444 SpanId, TagEntry, Tags, Timestamp, TraceId, Values,
445 };
446 use relay_protocol::{FromValue, IntoValue, Map, Remark, SerializableAnnotated, get_value};
447 use similar_asserts::assert_eq;
448
449 use super::*;
450
451 #[test]
452 fn test_string_trimming() {
453 let mut value = Annotated::new("This is my long string I want to have trimmed!".to_owned());
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_owned(),
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_owned()));
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_owned(),
526 Annotated::new(ExtraValue(Value::String("value 1".to_owned()))),
527 );
528 map.insert(
529 "key_2".to_owned(),
530 make_nested_object(8).map_value(ExtraValue),
531 );
532 map.insert(
533 "key_3".to_owned(),
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]
605 fn test_string_trimming_limits() {
606 #[derive(ProcessValue, IntoValue, FromValue, Empty, Debug, Clone)]
607 struct Outer {
608 #[metastructure(max_bytes = 10)]
609 inner: Annotated<Inner>,
610 }
611
612 #[derive(ProcessValue, IntoValue, FromValue, Empty, Debug, Clone)]
613 struct Inner {
614 #[metastructure(max_bytes = 20)]
615 innerer: Annotated<String>,
616 }
617
618 let mut processor = TrimmingProcessor::new();
619
620 let mut outer = Annotated::new({
621 Outer {
622 inner: Annotated::new(Inner {
623 innerer: Annotated::new("This string is 28 bytes long".into()),
624 }),
625 }
626 });
627
628 processor::process_value(&mut outer, &mut processor, ProcessingState::root()).unwrap();
629 let stripped = SerializableAnnotated(&outer);
630
631 insta::assert_ron_snapshot!(stripped, @r###"
632 {
633 "inner": {
634 "innerer": "This st...",
635 },
636 "_meta": {
637 "inner": {
638 "innerer": {
639 "": Meta(Some(MetaInner(
640 rem: [
641 [
642 "!limit",
643 s,
644 7,
645 10,
646 ],
647 ],
648 len: Some(28),
649 ))),
650 },
651 },
652 },
653 }
654 "###);
655 }
656
657 #[test]
658 fn test_tags_stripping() {
659 let mut processor = TrimmingProcessor::new();
660
661 let mut event = Annotated::new(Event {
662 tags: Annotated::new(Tags(
663 vec![Annotated::new(TagEntry(
664 Annotated::new("x".repeat(300)),
665 Annotated::new("x".repeat(300)),
666 ))]
667 .into(),
668 )),
669 ..Default::default()
670 });
671
672 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
673 let json = event
674 .value()
675 .unwrap()
676 .tags
677 .payload_to_json_pretty()
678 .unwrap();
679
680 assert_eq!(
681 json,
682 r#"[
683 [
684 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...",
685 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx..."
686 ]
687]"#
688 );
689 }
690
691 #[test]
692 fn test_databag_state_leak() {
693 let event = Annotated::new(Event {
694 breadcrumbs: Annotated::new(Values::new(
695 repeat_n(
696 Annotated::new(Breadcrumb {
697 data: {
698 let mut map = Map::new();
699 map.insert(
700 "spamspamspam".to_owned(),
701 Annotated::new(Value::String("blablabla".to_owned())),
702 );
703 Annotated::new(map)
704 },
705 ..Default::default()
706 }),
707 200,
708 )
709 .collect(),
710 )),
711 exceptions: Annotated::new(Values::new(vec![Annotated::new(Exception {
712 ty: Annotated::new("TypeError".to_owned()),
713 value: Annotated::new("important error message".to_owned().into()),
714 stacktrace: Annotated::new(
715 RawStacktrace {
716 frames: Annotated::new(
717 repeat_n(
718 Annotated::new(Frame {
719 function: Annotated::new("importantFunctionName".to_owned()),
720 symbol: Annotated::new("important_symbol".to_owned()),
721 ..Default::default()
722 }),
723 200,
724 )
725 .collect(),
726 ),
727 ..Default::default()
728 }
729 .into(),
730 ),
731 ..Default::default()
732 })])),
733 ..Default::default()
734 });
735
736 let mut processor = TrimmingProcessor::new();
737 let mut stripped_event = event.clone();
738 processor::process_value(&mut stripped_event, &mut processor, ProcessingState::root())
739 .unwrap();
740
741 assert_eq!(
742 event.to_json_pretty().unwrap(),
743 stripped_event.to_json_pretty().unwrap()
744 );
745 }
746
747 #[test]
748 fn test_custom_context_trimming() {
749 let mut contexts = Contexts::new();
750 for i in 1..2 {
751 contexts.insert(format!("despacito{i}"), {
752 let mut context = Object::new();
753 context.insert(
754 "foo".to_owned(),
755 Annotated::new(Value::String("a".repeat(4000))),
756 );
757 context.insert(
758 "bar".to_owned(),
759 Annotated::new(Value::String("a".repeat(5000))),
760 );
761 Context::Other(context)
762 });
763 }
764
765 let mut contexts = Annotated::new(contexts);
766 let mut processor = TrimmingProcessor::new();
767 processor::process_value(&mut contexts, &mut processor, ProcessingState::root()).unwrap();
768
769 let contexts = contexts.value().unwrap();
770 for i in 1..2 {
771 let other = match contexts.get_key(format!("despacito{i}")).unwrap() {
772 Context::Other(x) => x,
773 _ => panic!("Context has changed type!"),
774 };
775
776 assert_eq!(
777 other
778 .get("bar")
779 .unwrap()
780 .value()
781 .unwrap()
782 .as_str()
783 .unwrap()
784 .len(),
785 5000
786 );
787 assert_eq!(
788 other
789 .get("foo")
790 .unwrap()
791 .value()
792 .unwrap()
793 .as_str()
794 .unwrap()
795 .len(),
796 3189
797 );
798 }
799 }
800
801 #[test]
802 fn test_extra_trimming_long_arrays() {
803 let mut extra = Object::new();
804 extra.insert("foo".to_owned(), {
805 Annotated::new(ExtraValue(Value::Array(
806 repeat_n(Annotated::new(Value::U64(1)), 200_000).collect(),
807 )))
808 });
809
810 let mut event = Annotated::new(Event {
811 extra: Annotated::new(extra),
812 ..Default::default()
813 });
814
815 let mut processor = TrimmingProcessor::new();
816 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
817
818 let arr = match event
819 .value()
820 .unwrap()
821 .extra
822 .value()
823 .unwrap()
824 .get("foo")
825 .unwrap()
826 .value()
827 .unwrap()
828 {
829 ExtraValue(Value::Array(x)) => x,
830 x => panic!("Wrong type: {x:?}"),
831 };
832
833 assert_eq!(arr.len(), 8192);
835 }
836
837 #[test]
880 fn test_frameqty_equals_limit() {
881 fn create_frame(filename: &str) -> Annotated<Frame> {
882 Annotated::new(Frame {
883 filename: Annotated::new(filename.into()),
884 ..Default::default()
885 })
886 }
887
888 let mut frames = Annotated::new(vec![
889 create_frame("foo3.py"),
890 create_frame("foo4.py"),
891 create_frame("foo5.py"),
892 ]);
893
894 processor::apply(&mut frames, |f, m| {
895 enforce_frame_hard_limit(f, m, 3, 0);
896 Ok(())
897 })
898 .unwrap();
899
900 processor::apply(&mut frames, |f, m| {
901 enforce_frame_hard_limit(f, m, 1, 2);
902 Ok(())
903 })
904 .unwrap();
905
906 assert!(frames.meta().original_length().is_none());
908 }
909
910 #[test]
911 fn test_frame_hard_limit() {
912 fn create_frame(filename: &str) -> Annotated<Frame> {
913 Annotated::new(Frame {
914 filename: Annotated::new(filename.into()),
915 ..Default::default()
916 })
917 }
918
919 let mut frames = Annotated::new(vec![
920 create_frame("foo1.py"),
921 create_frame("foo2.py"),
922 create_frame("foo3.py"),
923 create_frame("foo4.py"),
924 create_frame("foo5.py"),
925 ]);
926
927 processor::apply(&mut frames, |f, m| {
928 enforce_frame_hard_limit(f, m, 3, 0);
929 Ok(())
930 })
931 .unwrap();
932
933 let mut expected_meta = Meta::default();
934 expected_meta.set_original_length(Some(5));
935
936 assert_eq!(
937 frames,
938 Annotated(
939 Some(vec![
940 create_frame("foo3.py"),
941 create_frame("foo4.py"),
942 create_frame("foo5.py"),
943 ]),
944 expected_meta
945 )
946 );
947 }
948
949 #[test]
950 fn test_frame_hard_limit_recent_old() {
951 fn create_frame(filename: &str) -> Annotated<Frame> {
952 Annotated::new(Frame {
953 filename: Annotated::new(filename.into()),
954 ..Default::default()
955 })
956 }
957
958 let mut frames = Annotated::new(vec![
959 create_frame("foo1.py"),
960 create_frame("foo2.py"),
961 create_frame("foo3.py"),
962 create_frame("foo4.py"),
963 create_frame("foo5.py"),
964 ]);
965
966 processor::apply(&mut frames, |f, m| {
967 enforce_frame_hard_limit(f, m, 2, 1);
968 Ok(())
969 })
970 .unwrap();
971
972 let mut expected_meta = Meta::default();
973 expected_meta.set_original_length(Some(5));
974
975 assert_eq!(
976 frames,
977 Annotated(
978 Some(vec![
979 create_frame("foo1.py"),
980 create_frame("foo4.py"),
981 create_frame("foo5.py"),
982 ]),
983 expected_meta
984 )
985 );
986 }
987
988 #[test]
989 fn test_slim_frame_data_under_max() {
990 let mut frames = vec![Annotated::new(Frame {
991 filename: Annotated::new("foo".into()),
992 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
993 context_line: Annotated::new("b".to_owned()),
994 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
995 ..Default::default()
996 })];
997
998 let old_frames = frames.clone();
999 slim_frame_data(&mut frames, 4);
1000
1001 assert_eq!(frames, old_frames);
1002 }
1003
1004 #[test]
1005 fn test_slim_frame_data_over_max() {
1006 let mut frames = vec![];
1007
1008 for n in 0..5 {
1009 frames.push(Annotated::new(Frame {
1010 filename: Annotated::new(format!("foo {n}").into()),
1011 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
1012 context_line: Annotated::new("b".to_owned()),
1013 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
1014 ..Default::default()
1015 }));
1016 }
1017
1018 slim_frame_data(&mut frames, 4);
1019
1020 let expected = vec![
1021 Annotated::new(Frame {
1022 filename: Annotated::new("foo 0".into()),
1023 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
1024 context_line: Annotated::new("b".to_owned()),
1025 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
1026 ..Default::default()
1027 }),
1028 Annotated::new(Frame {
1029 filename: Annotated::new("foo 1".into()),
1030 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
1031 context_line: Annotated::new("b".to_owned()),
1032 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
1033 ..Default::default()
1034 }),
1035 Annotated::new(Frame {
1036 filename: Annotated::new("foo 2".into()),
1037 context_line: Annotated::new("b".to_owned()),
1038 ..Default::default()
1039 }),
1040 Annotated::new(Frame {
1041 filename: Annotated::new("foo 3".into()),
1042 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
1043 context_line: Annotated::new("b".to_owned()),
1044 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
1045 ..Default::default()
1046 }),
1047 Annotated::new(Frame {
1048 filename: Annotated::new("foo 4".into()),
1049 pre_context: Annotated::new(vec![Annotated::new("a".to_owned())]),
1050 context_line: Annotated::new("b".to_owned()),
1051 post_context: Annotated::new(vec![Annotated::new("c".to_owned())]),
1052 ..Default::default()
1053 }),
1054 ];
1055
1056 assert_eq!(frames, expected);
1057 }
1058
1059 #[test]
1060 fn test_too_many_spans_trimmed() {
1061 let span = Span {
1062 platform: Annotated::new("a".repeat(1024 * 90)),
1063 sentry_tags: Annotated::new(SentryTags {
1064 release: Annotated::new("b".repeat(1024 * 100)),
1065 ..Default::default()
1066 }),
1067 ..Default::default()
1068 };
1069 let spans: Vec<_> = std::iter::repeat_with(|| Annotated::new(span.clone()))
1070 .take(10)
1071 .collect();
1072
1073 let mut event = Annotated::new(Event {
1074 spans: Annotated::new(spans.clone()),
1075 ..Default::default()
1076 });
1077
1078 let mut processor = TrimmingProcessor::new();
1079 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
1080
1081 let trimmed_spans = event.0.unwrap().spans.0.unwrap();
1082 assert_eq!(trimmed_spans.len(), 5);
1083
1084 assert_eq!(trimmed_spans.as_slice(), &spans[0..5]);
1086 }
1087
1088 #[test]
1089 fn test_untrimmable_fields() {
1090 let original_description = "a".repeat(819163);
1091 let original_trace_id: TraceId = "b".repeat(32).parse().unwrap();
1092 let mut event = Annotated::new(Event {
1093 spans: Annotated::new(vec![
1094 Span {
1095 description: original_description.clone().into(),
1096 ..Default::default()
1097 }
1098 .into(),
1099 Span {
1100 trace_id: original_trace_id.into(),
1101 ..Default::default()
1102 }
1103 .into(),
1104 ]),
1105 ..Default::default()
1106 });
1107
1108 let mut processor = TrimmingProcessor::new();
1109 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
1110
1111 assert_eq!(
1112 get_value!(event.spans[0].description!),
1113 &original_description
1114 );
1115 assert_eq!(get_value!(event.spans[1].trace_id!), &original_trace_id);
1117 }
1118
1119 #[test]
1120 fn test_untrimmable_fields_drop() {
1121 let original_description = "a".repeat(819164);
1122 let original_span_id: SpanId = "b".repeat(16).parse().unwrap();
1123 let original_trace_id: TraceId = "c".repeat(32).parse().unwrap();
1124 let original_segment_id: SpanId = "d".repeat(16).parse().unwrap();
1125 let original_op = "e".repeat(129);
1126
1127 let mut event = Annotated::new(Event {
1128 spans: Annotated::new(vec![
1129 Span {
1130 description: original_description.clone().into(),
1131 ..Default::default()
1132 }
1133 .into(),
1134 Span {
1135 span_id: original_span_id.into(),
1136 trace_id: original_trace_id.into(),
1137 segment_id: original_segment_id.into(),
1138 is_segment: false.into(),
1139 op: original_op.clone().into(),
1140 start_timestamp: Timestamp(
1141 DateTime::parse_from_rfc3339("1996-12-19T16:39:57Z")
1142 .unwrap()
1143 .into(),
1144 )
1145 .into(),
1146 timestamp: Timestamp(
1147 DateTime::parse_from_rfc3339("1996-12-19T16:39:58Z")
1148 .unwrap()
1149 .into(),
1150 )
1151 .into(),
1152 ..Default::default()
1153 }
1154 .into(),
1155 ]),
1156 ..Default::default()
1157 });
1158
1159 let mut processor = TrimmingProcessor::new();
1160 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
1161
1162 assert_eq!(
1163 get_value!(event.spans[0].description!),
1164 &original_description
1165 );
1166 assert_eq!(get_value!(event.spans[1].span_id!), &original_span_id);
1168 assert_eq!(get_value!(event.spans[1].trace_id!), &original_trace_id);
1169 assert_eq!(get_value!(event.spans[1].segment_id!), &original_segment_id);
1170 assert_eq!(get_value!(event.spans[1].is_segment!), &false);
1171 assert_eq!(get_value!(event.spans[1].op!).len(), 128);
1173 assert!(get_value!(event.spans[1].start_timestamp).is_some());
1174 assert!(get_value!(event.spans[1].timestamp).is_some());
1175 }
1176
1177 #[test]
1178 fn test_too_long_tags() {
1179 let mut event = Annotated::new(Event {
1180 tags: Annotated::new(Tags(PairList(
1181 vec![Annotated::new(TagEntry(
1182 Annotated::new("foobar".to_owned()),
1183 Annotated::new("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".to_owned()),
1184 )), Annotated::new(TagEntry(
1185 Annotated::new("foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo".to_owned()),
1186 Annotated::new("bar".to_owned()),
1187 ))]),
1188 )),
1189 ..Event::default()
1190 });
1191
1192 let mut processor = TrimmingProcessor::new();
1193 processor::process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
1194
1195 insta::assert_debug_snapshot!(get_value!(event.tags!), @r###"
1196 Tags(
1197 PairList(
1198 [
1199 TagEntry(
1200 "foobar",
1201 Annotated(
1202 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...",
1203 Meta {
1204 remarks: [
1205 Remark {
1206 ty: Substituted,
1207 rule_id: "!limit",
1208 range: Some(
1209 (
1210 197,
1211 200,
1212 ),
1213 ),
1214 },
1215 ],
1216 errors: [],
1217 original_length: Some(
1218 203,
1219 ),
1220 original_value: None,
1221 },
1222 ),
1223 ),
1224 TagEntry(
1225 Annotated(
1226 "foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo...",
1227 Meta {
1228 remarks: [
1229 Remark {
1230 ty: Substituted,
1231 rule_id: "!limit",
1232 range: Some(
1233 (
1234 197,
1235 200,
1236 ),
1237 ),
1238 },
1239 ],
1240 errors: [],
1241 original_length: Some(
1242 203,
1243 ),
1244 original_value: None,
1245 },
1246 ),
1247 "bar",
1248 ),
1249 ],
1250 ),
1251 )
1252 "###);
1253 }
1254
1255 #[test]
1256 fn test_fixed_item_size() {
1257 #[derive(Debug, Clone, Empty, IntoValue, FromValue, ProcessValue)]
1258 struct TestObject {
1259 #[metastructure(max_bytes = 28)]
1260 inner: Annotated<TestObjectInner>,
1261 }
1262 #[derive(Debug, Clone, Empty, IntoValue, FromValue, ProcessValue)]
1263 struct TestObjectInner {
1264 #[metastructure(max_chars = 10, trim = true)]
1265 body: Annotated<String>,
1266 #[metastructure(trim = false, bytes_size = "always_zero")]
1268 number: Annotated<u64>,
1269 #[metastructure(trim = false, bytes_size = 10)]
1271 other_number: Annotated<u64>,
1272 #[metastructure(trim = true)]
1273 footer: Annotated<String>,
1274 }
1275
1276 fn always_zero(_state: &ProcessingState) -> Option<usize> {
1277 Some(0)
1278 }
1279
1280 let mut object = Annotated::new(TestObject {
1281 inner: Annotated::new(TestObjectInner {
1282 body: Annotated::new("Longer than 10 chars".to_owned()),
1283 number: Annotated::new(13),
1284 other_number: Annotated::new(12),
1285 footer: Annotated::new("There should only be 'Th...' left".to_owned()),
1286 }),
1287 });
1288
1289 let mut processor = TrimmingProcessor::new();
1290 processor::process_value(&mut object, &mut processor, ProcessingState::root()).unwrap();
1291
1292 insta::assert_ron_snapshot!(SerializableAnnotated(&object), @r###"
1297 {
1298 "inner": {
1299 "body": "Longer ...",
1300 "number": 13,
1301 "other_number": 12,
1302 "footer": "Th...",
1303 },
1304 "_meta": {
1305 "inner": {
1306 "body": {
1307 "": Meta(Some(MetaInner(
1308 rem: [
1309 [
1310 "!limit",
1311 s,
1312 7,
1313 10,
1314 ],
1315 ],
1316 len: Some(20),
1317 ))),
1318 },
1319 "footer": {
1320 "": Meta(Some(MetaInner(
1321 rem: [
1322 [
1323 "!limit",
1324 s,
1325 2,
1326 5,
1327 ],
1328 ],
1329 len: Some(33),
1330 ))),
1331 },
1332 },
1333 },
1334 }
1335 "###);
1336 }
1337}