1#![deny(warnings)]
2#![deny(clippy::unwrap_used)]
3use std::fmt;
104
105use dashmap::DashMap;
106use tracing_core::{
107 Event, Metadata, Subscriber,
108 callsite::Identifier,
109 field::{Field, Value, Visit, display},
110 span,
111 subscriber::Interest,
112};
113use tracing_subscriber::layer::{Context, Layer};
114
115#[cfg(test)]
116#[macro_use]
117extern crate tracing;
118
119#[cfg(not(test))]
120use std::time::Instant;
121
122#[cfg(test)]
123use mock_instant::global::Instant;
124
125const RATE_LIMIT_FIELD: &str = "internal_log_rate_limit";
126const RATE_LIMIT_SECS_FIELD: &str = "internal_log_rate_secs";
127const MESSAGE_FIELD: &str = "message";
128
129const COMPONENT_ID_FIELD: &str = "component_id";
132
133#[derive(Eq, PartialEq, Hash, Clone)]
134struct RateKeyIdentifier {
135 callsite: Identifier,
136 rate_limit_key_values: RateLimitedSpanKeys,
137}
138
139pub struct RateLimitedLayer<S, L>
140where
141 L: Layer<S> + Sized,
142 S: Subscriber,
143{
144 events: DashMap<RateKeyIdentifier, State>,
145 inner: L,
146 internal_log_rate_limit: u64,
147 _subscriber: std::marker::PhantomData<S>,
148}
149
150impl<S, L> RateLimitedLayer<S, L>
151where
152 L: Layer<S> + Sized,
153 S: Subscriber,
154{
155 pub fn new(layer: L) -> Self {
156 RateLimitedLayer {
157 events: Default::default(),
158 internal_log_rate_limit: 10,
159 inner: layer,
160 _subscriber: std::marker::PhantomData,
161 }
162 }
163
164 pub fn with_default_limit(mut self, internal_log_rate_limit: u64) -> Self {
173 self.internal_log_rate_limit = internal_log_rate_limit;
174 self
175 }
176}
177
178impl<S, L> Layer<S> for RateLimitedLayer<S, L>
179where
180 L: Layer<S>,
181 S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
182{
183 #[inline]
184 fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
185 self.inner.register_callsite(metadata)
186 }
187
188 #[inline]
189 fn enabled(&self, metadata: &Metadata<'_>, ctx: Context<'_, S>) -> bool {
190 self.inner.enabled(metadata, ctx)
191 }
192
193 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
195 {
196 let span = ctx.span(id).expect("Span not found, this is a bug");
197 let mut extensions = span.extensions_mut();
198
199 if extensions.get_mut::<RateLimitedSpanKeys>().is_none() {
200 let mut fields = RateLimitedSpanKeys::default();
201 attrs.record(&mut fields);
202 extensions.insert(fields);
203 };
204 }
205 self.inner.on_new_span(attrs, id, ctx);
206 }
207
208 fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
210 {
211 let span = ctx.span(id).expect("Span not found, this is a bug");
212 let mut extensions = span.extensions_mut();
213
214 match extensions.get_mut::<RateLimitedSpanKeys>() {
215 Some(fields) => {
216 values.record(fields);
217 }
218 None => {
219 let mut fields = RateLimitedSpanKeys::default();
220 values.record(&mut fields);
221 extensions.insert(fields);
222 }
223 };
224 }
225 self.inner.on_record(id, values, ctx);
226 }
227
228 #[inline]
229 fn on_follows_from(&self, span: &span::Id, follows: &span::Id, ctx: Context<'_, S>) {
230 self.inner.on_follows_from(span, follows, ctx);
231 }
232
233 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
234 let mut limit_visitor = LimitVisitor::default();
237 event.record(&mut limit_visitor);
238
239 let limit_exists = limit_visitor.limit.unwrap_or(true);
240 if !limit_exists {
241 return self.inner.on_event(event, ctx);
242 }
243
244 let limit = match limit_visitor.limit_secs {
245 Some(limit_secs) => limit_secs, None => self.internal_log_rate_limit,
247 };
248
249 let rate_limit_key_values = {
263 let mut keys = RateLimitedSpanKeys::default();
264 event.record(&mut keys);
266
267 ctx.lookup_current()
269 .into_iter()
270 .flat_map(|span| span.scope().from_root())
271 .fold(keys, |mut keys, span| {
272 let extensions = span.extensions();
273 if let Some(span_keys) = extensions.get::<RateLimitedSpanKeys>() {
274 keys.merge(span_keys);
275 }
276 keys
277 })
278 };
279
280 let metadata = event.metadata();
283 let id = RateKeyIdentifier {
284 callsite: metadata.callsite(),
285 rate_limit_key_values,
286 };
287
288 let mut state = self.events.entry(id).or_insert_with(|| {
289 let mut message_visitor = MessageVisitor::default();
290 event.record(&mut message_visitor);
291
292 let message = message_visitor
293 .message
294 .unwrap_or_else(|| metadata.name().into());
295
296 State::new(message, limit)
297 });
298
299 let previous_count = state.increment_count();
305 if state.should_limit() {
306 match previous_count {
307 0 => self.inner.on_event(event, ctx),
308 1 => {
309 let message = format!(
310 "Internal log [{}] is being suppressed to avoid flooding.",
311 state.message
312 );
313 self.create_event(&ctx, metadata, message, state.limit);
314 }
315 _ => {}
316 }
317 } else {
318 if previous_count > 1 {
321 let message = format!(
322 "Internal log [{}] has been suppressed {} times.",
323 state.message,
324 previous_count - 1
325 );
326
327 self.create_event(&ctx, metadata, message, state.limit);
328 }
329
330 self.inner.on_event(event, ctx);
333
334 state.reset();
335 }
336 }
337
338 #[inline]
339 fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
340 self.inner.on_enter(id, ctx);
341 }
342
343 #[inline]
344 fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
345 self.inner.on_exit(id, ctx);
346 }
347
348 #[inline]
349 fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
350 self.inner.on_close(id, ctx);
351 }
352
353 #[inline]
354 fn on_id_change(&self, old: &span::Id, new: &span::Id, ctx: Context<'_, S>) {
355 self.inner.on_id_change(old, new, ctx);
356 }
357
358 #[inline]
359 fn on_layer(&mut self, subscriber: &mut S) {
360 self.inner.on_layer(subscriber);
361 }
362}
363
364impl<S, L> RateLimitedLayer<S, L>
365where
366 S: Subscriber,
367 L: Layer<S>,
368{
369 fn create_event(
370 &self,
371 ctx: &Context<S>,
372 metadata: &'static Metadata<'static>,
373 message: String,
374 rate_limit: u64,
375 ) {
376 let fields = metadata.fields();
377
378 let message = display(message);
379
380 if let Some(message_field) = fields.field("message") {
381 let values = [(&message_field, Some(&message as &dyn Value))];
382
383 let valueset = fields.value_set(&values);
384 let event = Event::new(metadata, &valueset);
385 self.inner.on_event(&event, ctx.clone());
386 } else if let Some(rate_limit_field) = fields.field(RATE_LIMIT_FIELD) {
387 let values = [(&rate_limit_field, Some(&rate_limit as &dyn Value))];
388
389 let valueset = fields.value_set(&values);
390 let event = Event::new(metadata, &valueset);
391 self.inner.on_event(&event, ctx.clone());
392 } else {
393 }
398 }
399}
400
401#[derive(Debug)]
402struct State {
403 start: Instant,
404 count: u64,
405 limit: u64,
406 message: String,
407}
408
409impl State {
410 fn new(message: String, limit: u64) -> Self {
411 Self {
412 start: Instant::now(),
413 count: 0,
414 limit,
415 message,
416 }
417 }
418
419 fn reset(&mut self) {
420 self.start = Instant::now();
421 self.count = 1;
422 }
423
424 fn increment_count(&mut self) -> u64 {
425 let prev = self.count;
426 self.count += 1;
427 prev
428 }
429
430 fn should_limit(&self) -> bool {
431 self.start.elapsed().as_secs() < self.limit
432 }
433}
434
435#[derive(PartialEq, Eq, Clone, Hash)]
436enum TraceValue {
437 String(String),
438 Int(i64),
439 Uint(u64),
440 Bool(bool),
441}
442
443#[cfg(test)]
444impl fmt::Display for TraceValue {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 match self {
447 TraceValue::String(s) => write!(f, "{}", s),
448 TraceValue::Int(i) => write!(f, "{}", i),
449 TraceValue::Uint(u) => write!(f, "{}", u),
450 TraceValue::Bool(b) => write!(f, "{}", b),
451 }
452 }
453}
454
455impl From<bool> for TraceValue {
456 fn from(b: bool) -> Self {
457 TraceValue::Bool(b)
458 }
459}
460
461impl From<i64> for TraceValue {
462 fn from(i: i64) -> Self {
463 TraceValue::Int(i)
464 }
465}
466
467impl From<u64> for TraceValue {
468 fn from(u: u64) -> Self {
469 TraceValue::Uint(u)
470 }
471}
472
473impl From<String> for TraceValue {
474 fn from(s: String) -> Self {
475 TraceValue::String(s)
476 }
477}
478
479#[derive(Default, Eq, PartialEq, Hash, Clone)]
493struct RateLimitedSpanKeys {
494 component_id: Option<TraceValue>,
495}
496
497impl RateLimitedSpanKeys {
498 fn record(&mut self, field: &Field, value: TraceValue) {
499 if field.name() == COMPONENT_ID_FIELD {
500 self.component_id = Some(value);
501 }
502 }
503
504 fn merge(&mut self, other: &Self) {
505 if let Some(component_id) = &other.component_id {
506 self.component_id = Some(component_id.clone());
507 }
508 }
509}
510
511impl Visit for RateLimitedSpanKeys {
512 fn record_i64(&mut self, field: &Field, value: i64) {
513 self.record(field, value.into());
514 }
515
516 fn record_u64(&mut self, field: &Field, value: u64) {
517 self.record(field, value.into());
518 }
519
520 fn record_bool(&mut self, field: &Field, value: bool) {
521 self.record(field, value.into());
522 }
523
524 fn record_str(&mut self, field: &Field, value: &str) {
525 self.record(field, value.to_owned().into());
526 }
527
528 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
529 self.record(field, format!("{value:?}").into());
530 }
531}
532
533#[derive(Default)]
534struct LimitVisitor {
535 pub limit: Option<bool>,
536 pub limit_secs: Option<u64>,
537}
538
539impl Visit for LimitVisitor {
540 fn record_bool(&mut self, field: &Field, value: bool) {
541 if field.name() == RATE_LIMIT_FIELD {
542 self.limit = Some(value);
543 }
544 }
545
546 fn record_i64(&mut self, field: &Field, value: i64) {
547 if field.name() == RATE_LIMIT_SECS_FIELD {
548 self.limit = Some(true); self.limit_secs = Some(u64::try_from(value).unwrap_or_default()); }
551 }
552
553 fn record_u64(&mut self, field: &Field, value: u64) {
554 if field.name() == RATE_LIMIT_SECS_FIELD {
555 self.limit = Some(true); self.limit_secs = Some(value); }
558 }
559
560 fn record_debug(&mut self, _field: &Field, _value: &dyn fmt::Debug) {}
561}
562
563#[derive(Default)]
564struct MessageVisitor {
565 pub message: Option<String>,
566}
567
568impl Visit for MessageVisitor {
569 fn record_str(&mut self, field: &Field, value: &str) {
570 if self.message.is_none() && field.name() == MESSAGE_FIELD {
571 self.message = Some(value.to_string());
572 }
573 }
574
575 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
576 if self.message.is_none() && field.name() == MESSAGE_FIELD {
577 self.message = Some(format!("{value:?}"));
578 }
579 }
580}
581
582#[cfg(test)]
583mod test {
584 use std::{
585 collections::BTreeMap,
586 sync::{Arc, Mutex},
587 time::Duration,
588 };
589
590 use mock_instant::global::MockClock;
591 use serial_test::serial;
592 use tracing_subscriber::layer::SubscriberExt;
593
594 use super::*;
595
596 #[derive(Debug, Clone, PartialEq, Eq)]
597 struct RecordedEvent {
598 message: String,
599 fields: BTreeMap<String, String>,
600 }
601
602 impl RecordedEvent {
603 fn new(message: impl Into<String>) -> Self {
604 Self {
605 message: message.into(),
606 fields: BTreeMap::new(),
607 }
608 }
609
610 fn with_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
611 self.fields.insert(key.into(), value.into());
612 self
613 }
614 }
615
616 macro_rules! event {
622 ($msg:expr) => {
623 RecordedEvent::new($msg)
624 };
625 ($msg:expr, $($key:ident: $value:expr),+ $(,)?) => {
626 RecordedEvent::new($msg)
627 $(.with_field(stringify!($key), $value))+
628 };
629 }
630
631 #[derive(Default)]
632 struct AllFieldsVisitor {
633 fields: BTreeMap<String, String>,
634 }
635
636 impl Visit for AllFieldsVisitor {
637 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
638 self.fields
639 .insert(field.name().to_string(), format!("{value:?}"));
640 }
641
642 fn record_str(&mut self, field: &Field, value: &str) {
643 self.fields
644 .insert(field.name().to_string(), value.to_string());
645 }
646
647 fn record_i64(&mut self, field: &Field, value: i64) {
648 self.fields
649 .insert(field.name().to_string(), value.to_string());
650 }
651
652 fn record_u64(&mut self, field: &Field, value: u64) {
653 self.fields
654 .insert(field.name().to_string(), value.to_string());
655 }
656
657 fn record_bool(&mut self, field: &Field, value: bool) {
658 self.fields
659 .insert(field.name().to_string(), value.to_string());
660 }
661 }
662
663 impl AllFieldsVisitor {
664 fn into_event(self) -> RecordedEvent {
665 let message = self
666 .fields
667 .get("message")
668 .cloned()
669 .unwrap_or_else(|| String::from(""));
670
671 let mut fields = BTreeMap::new();
672 for (key, value) in self.fields {
673 if key != "message"
674 && key != "internal_log_rate_limit"
675 && key != "internal_log_rate_secs"
676 {
677 fields.insert(key, value);
678 }
679 }
680
681 RecordedEvent { message, fields }
682 }
683 }
684
685 #[derive(Default)]
686 struct RecordingLayer<S> {
687 events: Arc<Mutex<Vec<RecordedEvent>>>,
688
689 _subscriber: std::marker::PhantomData<S>,
690 }
691
692 impl<S> RecordingLayer<S> {
693 fn new(events: Arc<Mutex<Vec<RecordedEvent>>>) -> Self {
694 RecordingLayer {
695 events,
696
697 _subscriber: std::marker::PhantomData,
698 }
699 }
700 }
701
702 impl<S> Layer<S> for RecordingLayer<S>
703 where
704 S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
705 {
706 fn register_callsite(&self, _metadata: &'static Metadata<'static>) -> Interest {
707 Interest::always()
708 }
709
710 fn enabled(&self, _metadata: &Metadata<'_>, _ctx: Context<'_, S>) -> bool {
711 true
712 }
713
714 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
715 let mut visitor = AllFieldsVisitor::default();
716 event.record(&mut visitor);
717
718 if let Some(span) = ctx.lookup_current() {
720 for span_ref in span.scope().from_root() {
721 let extensions = span_ref.extensions();
722 if let Some(span_keys) = extensions.get::<RateLimitedSpanKeys>() {
723 if let Some(TraceValue::String(ref s)) = span_keys.component_id {
725 visitor.fields.insert("component_id".to_string(), s.clone());
726 }
727 }
728 }
729 }
730
731 let mut events = self.events.lock().unwrap();
732 events.push(visitor.into_event());
733 }
734 }
735
736 fn setup_test(
739 default_limit: u64,
740 ) -> (
741 Arc<Mutex<Vec<RecordedEvent>>>,
742 impl Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
743 ) {
744 let events: Arc<Mutex<Vec<RecordedEvent>>> = Default::default();
745 let recorder = RecordingLayer::new(Arc::clone(&events));
746 let sub = tracing_subscriber::registry::Registry::default()
747 .with(RateLimitedLayer::new(recorder).with_default_limit(default_limit));
748 (events, sub)
749 }
750
751 #[test]
752 #[serial]
753 fn rate_limits() {
754 let (events, sub) = setup_test(1);
755 tracing::subscriber::with_default(sub, || {
756 for _ in 0..21 {
757 info!(message = "Hello world!");
758 MockClock::advance(Duration::from_millis(100));
759 }
760 });
761
762 let events = events.lock().unwrap();
763
764 assert_eq!(
765 *events,
766 vec![
767 event!("Hello world!"),
768 event!("Internal log [Hello world!] is being suppressed to avoid flooding."),
769 event!("Internal log [Hello world!] has been suppressed 9 times."),
770 event!("Hello world!"),
771 event!("Internal log [Hello world!] is being suppressed to avoid flooding."),
772 event!("Internal log [Hello world!] has been suppressed 9 times."),
773 event!("Hello world!"),
774 ]
775 );
776 }
777
778 #[test]
779 #[serial]
780 fn override_rate_limit_at_callsite() {
781 let (events, sub) = setup_test(100);
782 tracing::subscriber::with_default(sub, || {
783 for _ in 0..31 {
784 info!(message = "Hello world!", internal_log_rate_secs = 2);
785 MockClock::advance(Duration::from_millis(100));
786 }
787 });
788
789 let events = events.lock().unwrap();
790
791 assert_eq!(
796 *events,
797 vec![
798 event!("Hello world!"),
799 event!("Internal log [Hello world!] is being suppressed to avoid flooding."),
800 event!("Internal log [Hello world!] has been suppressed 19 times."),
801 event!("Hello world!"),
802 event!("Internal log [Hello world!] is being suppressed to avoid flooding."),
803 ]
804 );
805 }
806
807 #[test]
808 #[serial]
809 fn rate_limit_by_event_key() {
810 let (events, sub) = setup_test(1);
811 tracing::subscriber::with_default(sub, || {
812 for _ in 0..21 {
813 for key in &["foo", "bar"] {
814 info!(
815 message = format!("Hello {key}!").as_str(),
816 component_id = &key
817 );
818 }
819 MockClock::advance(Duration::from_millis(100));
820 }
821 });
822
823 let events = events.lock().unwrap();
824
825 assert_eq!(
827 *events,
828 vec![
829 event!("Hello foo!", component_id: "foo"),
830 event!("Hello bar!", component_id: "bar"),
831 event!("Internal log [Hello foo!] is being suppressed to avoid flooding."),
832 event!("Internal log [Hello bar!] is being suppressed to avoid flooding."),
833 event!("Internal log [Hello foo!] has been suppressed 9 times."),
834 event!("Hello foo!", component_id: "foo"),
835 event!("Internal log [Hello bar!] has been suppressed 9 times."),
836 event!("Hello bar!", component_id: "bar"),
837 event!("Internal log [Hello foo!] is being suppressed to avoid flooding."),
838 event!("Internal log [Hello bar!] is being suppressed to avoid flooding."),
839 event!("Internal log [Hello foo!] has been suppressed 9 times."),
840 event!("Hello foo!", component_id: "foo"),
841 event!("Internal log [Hello bar!] has been suppressed 9 times."),
842 event!("Hello bar!", component_id: "bar"),
843 ]
844 );
845 }
846
847 #[test]
848 #[serial]
849 fn disabled_rate_limit() {
850 let (events, sub) = setup_test(1);
851 tracing::subscriber::with_default(sub, || {
852 for _ in 0..21 {
853 info!(message = "Hello world!", internal_log_rate_limit = false);
854 MockClock::advance(Duration::from_millis(100));
855 }
856 });
857
858 let events = events.lock().unwrap();
859
860 assert_eq!(events.len(), 21);
862 assert!(events.iter().all(|e| e == &event!("Hello world!")));
863 }
864
865 #[test]
866 #[serial]
867 fn rate_limit_ignores_non_special_fields() {
868 let (events, sub) = setup_test(1);
869 tracing::subscriber::with_default(sub, || {
870 for i in 0..21 {
871 for _ in 0..3 {
874 let fanout = if i % 2 == 0 { "output_1" } else { "output_2" };
875 info!(
876 message = "Routing event",
877 component_id = "router",
878 fanout_id = fanout
879 );
880 }
881 MockClock::advance(Duration::from_millis(100));
882 }
883 });
884
885 let events = events.lock().unwrap();
886
887 assert_eq!(
891 *events,
892 vec![
893 event!("Routing event", component_id: "router", fanout_id: "output_1"),
895 event!("Internal log [Routing event] is being suppressed to avoid flooding."),
896 event!("Internal log [Routing event] has been suppressed 29 times."),
898 event!("Routing event", component_id: "router", fanout_id: "output_1"),
899 event!("Internal log [Routing event] is being suppressed to avoid flooding."),
900 event!("Internal log [Routing event] has been suppressed 29 times."),
901 event!("Routing event", component_id: "router", fanout_id: "output_1"),
902 event!("Internal log [Routing event] is being suppressed to avoid flooding."),
903 ]
904 );
905 }
906
907 #[test]
908 #[serial]
909 fn nested_spans_child_takes_precedence() {
910 let (events, sub) = setup_test(1);
911 tracing::subscriber::with_default(sub, || {
912 let outer = info_span!("outer", component_id = "parent");
914 let _outer_guard = outer.enter();
915
916 for _ in 0..21 {
917 let inner = info_span!("inner", component_id = "child");
919 let _inner_guard = inner.enter();
920 info!(message = "Nested event");
921 drop(_inner_guard);
922
923 MockClock::advance(Duration::from_millis(100));
924 }
925 });
926
927 let events = events.lock().unwrap();
928
929 assert_eq!(
932 *events,
933 vec![
934 event!("Nested event", component_id: "child"),
935 event!("Internal log [Nested event] is being suppressed to avoid flooding.", component_id: "child"),
936 event!("Internal log [Nested event] has been suppressed 9 times.", component_id: "child"),
937 event!("Nested event", component_id: "child"),
938 event!("Internal log [Nested event] is being suppressed to avoid flooding.", component_id: "child"),
939 event!("Internal log [Nested event] has been suppressed 9 times.", component_id: "child"),
940 event!("Nested event", component_id: "child"),
941 ]
942 );
943 }
944
945 #[test]
946 #[serial]
947 fn nested_spans_ignores_untracked_fields() {
948 let (events, sub) = setup_test(1);
949 tracing::subscriber::with_default(sub, || {
950 let outer = info_span!("outer", component_id = "transform");
952 let _outer_guard = outer.enter();
953
954 for _ in 0..21 {
955 let inner = info_span!("inner", some_field = "value");
956 let _inner_guard = inner.enter();
957 info!(message = "Event message");
958 drop(_inner_guard);
959
960 MockClock::advance(Duration::from_millis(100));
961 }
962 });
963
964 let events = events.lock().unwrap();
965
966 assert_eq!(
969 *events,
970 vec![
971 event!("Event message", component_id: "transform"),
972 event!(
973 "Internal log [Event message] is being suppressed to avoid flooding.",
974 component_id: "transform"
975 ),
976 event!(
977 "Internal log [Event message] has been suppressed 9 times.",
978 component_id: "transform"
979 ),
980 event!("Event message", component_id: "transform"),
981 event!(
982 "Internal log [Event message] is being suppressed to avoid flooding.",
983 component_id: "transform"
984 ),
985 event!(
986 "Internal log [Event message] has been suppressed 9 times.",
987 component_id: "transform"
988 ),
989 event!("Event message", component_id: "transform"),
990 ]
991 );
992 }
993
994 #[test]
995 #[serial]
996 fn rate_limit_same_message_different_component() {
997 let (events, sub) = setup_test(1);
998 tracing::subscriber::with_default(sub, || {
999 for component in &["foo", "foo", "bar"] {
1002 info!(message = "Hello!", component_id = component);
1003 MockClock::advance(Duration::from_millis(100));
1004 }
1005 });
1006
1007 let events = events.lock().unwrap();
1008
1009 assert_eq!(
1015 *events,
1016 vec![
1017 event!("Hello!", component_id: "foo"),
1018 event!("Internal log [Hello!] is being suppressed to avoid flooding."),
1019 event!("Hello!", component_id: "bar"),
1020 ]
1021 );
1022 }
1023
1024 #[test]
1025 #[serial]
1026 fn events_with_custom_fields_no_message_dont_panic() {
1027 let (events, sub) = setup_test(1);
1030 tracing::subscriber::with_default(sub, || {
1031 let emit_event = || {
1033 debug!(component_id = "test_component", utilization = 0.85);
1034 };
1035
1036 for _ in 0..5 {
1038 emit_event();
1039 MockClock::advance(Duration::from_millis(100));
1040 }
1041
1042 MockClock::advance(Duration::from_millis(1000));
1044
1045 emit_event();
1047 });
1048
1049 let events = events.lock().unwrap();
1050
1051 assert_eq!(
1054 *events,
1055 vec![
1056 event!("", component_id: "test_component", utilization: "0.85"),
1057 event!("", component_id: "test_component", utilization: "0.85"),
1058 ]
1059 );
1060 }
1061}