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