1pub use parking_lot;
58pub use tracing;
59
60pub use fastrand;
61
62use parking_lot::Mutex;
63use std::collections::HashSet;
64use std::hash::{Hash, Hasher};
65use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
66use std::time::{Duration, Instant};
67
68#[doc(hidden)]
70pub fn compute_hash<T: Hash>(value: &T) -> u64 {
71 use std::collections::hash_map::DefaultHasher;
72 let mut hasher = DefaultHasher::new();
73 value.hash(&mut hasher);
74 hasher.finish()
75}
76
77pub struct ThrottleState {
79 last_log: Mutex<Option<Instant>>,
80}
81
82impl Default for ThrottleState {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88impl ThrottleState {
89 pub const fn new() -> Self {
90 Self {
91 last_log: Mutex::new(None),
92 }
93 }
94
95 pub fn should_log(&self, interval: Duration) -> bool {
97 let mut last = self.last_log.lock();
98 let now = Instant::now();
99
100 match *last {
101 None => {
102 *last = Some(now);
103 true
104 }
105 Some(prev) if now.duration_since(prev) >= interval => {
106 *last = Some(now);
107 true
108 }
109 _ => false,
110 }
111 }
112}
113
114pub struct CountState {
116 count: AtomicUsize,
117}
118
119impl Default for CountState {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125impl CountState {
126 pub const fn new() -> Self {
127 Self {
128 count: AtomicUsize::new(0),
129 }
130 }
131
132 pub fn should_log(&self, n: usize) -> bool {
134 let count = self.count.fetch_add(1, Ordering::Relaxed);
135 count.is_multiple_of(n)
136 }
137}
138
139pub struct OnceState {
141 logged: AtomicBool,
142}
143
144impl Default for OnceState {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150impl OnceState {
151 pub const fn new() -> Self {
152 Self {
153 logged: AtomicBool::new(false),
154 }
155 }
156
157 pub fn should_log(&self) -> bool {
159 !self.logged.swap(true, Ordering::Relaxed)
160 }
161}
162
163pub struct FirstNState {
165 count: AtomicUsize,
166}
167
168impl Default for FirstNState {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174impl FirstNState {
175 pub const fn new() -> Self {
176 Self {
177 count: AtomicUsize::new(0),
178 }
179 }
180
181 pub fn should_log(&self, n: usize) -> bool {
183 let count = self.count.fetch_add(1, Ordering::Relaxed);
184 count < n
185 }
186}
187
188pub struct BackoffState {
190 state: Mutex<Option<BackoffInner>>,
191}
192
193struct BackoffInner {
194 last_log: Instant,
195 current_interval: Duration,
196}
197
198impl Default for BackoffState {
199 fn default() -> Self {
200 Self::new()
201 }
202}
203
204impl BackoffState {
205 pub const fn new() -> Self {
206 Self {
207 state: Mutex::new(None),
208 }
209 }
210
211 pub fn should_log(&self, initial: Duration, max: Duration) -> bool {
214 let mut state = self.state.lock();
215 let now = Instant::now();
216
217 match state.as_mut() {
218 None => {
219 *state = Some(BackoffInner {
220 last_log: now,
221 current_interval: initial,
222 });
223 true
224 }
225 Some(inner) if now.duration_since(inner.last_log) >= inner.current_interval => {
226 inner.last_log = now;
227 inner.current_interval = (inner.current_interval * 2).min(max);
228 true
229 }
230 _ => false,
231 }
232 }
233}
234
235pub struct OnChangeState {
238 prev_hash: AtomicU64,
239 initialized: AtomicBool,
240}
241
242impl Default for OnChangeState {
243 fn default() -> Self {
244 Self::new()
245 }
246}
247
248impl OnChangeState {
249 pub const fn new() -> Self {
250 Self {
251 prev_hash: AtomicU64::new(0),
252 initialized: AtomicBool::new(false),
253 }
254 }
255
256 pub fn should_log<T: Hash>(&self, value: &T) -> bool {
259 let new_hash = compute_hash(value);
260
261 if !self.initialized.swap(true, Ordering::AcqRel) {
263 self.prev_hash.store(new_hash, Ordering::Release);
264 return true;
265 }
266
267 let prev = self.prev_hash.swap(new_hash, Ordering::AcqRel);
269 prev != new_hash
270 }
271}
272
273pub struct OncePerValueState {
277 seen: Mutex<HashSet<u64>>,
278}
279
280impl Default for OncePerValueState {
281 fn default() -> Self {
282 Self::new()
283 }
284}
285
286impl OncePerValueState {
287 pub fn new() -> Self {
288 Self {
289 seen: Mutex::new(HashSet::new()),
290 }
291 }
292
293 pub fn should_log<T: Hash>(&self, value: &T) -> bool {
295 let hash = compute_hash(value);
296 let mut seen = self.seen.lock();
297 seen.insert(hash)
298 }
299}
300
301pub struct SampleState {
303 _private: (),
304}
305
306impl Default for SampleState {
307 fn default() -> Self {
308 Self::new()
309 }
310}
311
312impl SampleState {
313 pub const fn new() -> Self {
314 Self { _private: () }
315 }
316
317 pub fn should_log(&self, probability: f64) -> bool {
319 fastrand::f64() < probability
320 }
321}
322
323#[macro_export]
329macro_rules! trace_once {
330 ($($arg:tt)*) => {{
331 static STATE: $crate::OnceState = $crate::OnceState::new();
332 if STATE.should_log() {
333 $crate::tracing::trace!($($arg)*);
334 }
335 }};
336}
337
338#[macro_export]
340macro_rules! debug_once {
341 ($($arg:tt)*) => {{
342 static STATE: $crate::OnceState = $crate::OnceState::new();
343 if STATE.should_log() {
344 $crate::tracing::debug!($($arg)*);
345 }
346 }};
347}
348
349#[macro_export]
351macro_rules! info_once {
352 ($($arg:tt)*) => {{
353 static STATE: $crate::OnceState = $crate::OnceState::new();
354 if STATE.should_log() {
355 $crate::tracing::info!($($arg)*);
356 }
357 }};
358}
359
360#[macro_export]
362macro_rules! warn_once {
363 ($($arg:tt)*) => {{
364 static STATE: $crate::OnceState = $crate::OnceState::new();
365 if STATE.should_log() {
366 $crate::tracing::warn!($($arg)*);
367 }
368 }};
369}
370
371#[macro_export]
373macro_rules! error_once {
374 ($($arg:tt)*) => {{
375 static STATE: $crate::OnceState = $crate::OnceState::new();
376 if STATE.should_log() {
377 $crate::tracing::error!($($arg)*);
378 }
379 }};
380}
381
382#[macro_export]
388macro_rules! trace_every {
389 ($duration:expr, $($arg:tt)*) => {{
390 static STATE: $crate::ThrottleState = $crate::ThrottleState::new();
391 if STATE.should_log($duration) {
392 $crate::tracing::trace!($($arg)*);
393 }
394 }};
395}
396
397#[macro_export]
399macro_rules! debug_every {
400 ($duration:expr, $($arg:tt)*) => {{
401 static STATE: $crate::ThrottleState = $crate::ThrottleState::new();
402 if STATE.should_log($duration) {
403 $crate::tracing::debug!($($arg)*);
404 }
405 }};
406}
407
408#[macro_export]
410macro_rules! info_every {
411 ($duration:expr, $($arg:tt)*) => {{
412 static STATE: $crate::ThrottleState = $crate::ThrottleState::new();
413 if STATE.should_log($duration) {
414 $crate::tracing::info!($($arg)*);
415 }
416 }};
417}
418
419#[macro_export]
421macro_rules! warn_every {
422 ($duration:expr, $($arg:tt)*) => {{
423 static STATE: $crate::ThrottleState = $crate::ThrottleState::new();
424 if STATE.should_log($duration) {
425 $crate::tracing::warn!($($arg)*);
426 }
427 }};
428}
429
430#[macro_export]
432macro_rules! error_every {
433 ($duration:expr, $($arg:tt)*) => {{
434 static STATE: $crate::ThrottleState = $crate::ThrottleState::new();
435 if STATE.should_log($duration) {
436 $crate::tracing::error!($($arg)*);
437 }
438 }};
439}
440
441#[macro_export]
447macro_rules! trace_every_n {
448 ($n:expr, $($arg:tt)*) => {{
449 static STATE: $crate::CountState = $crate::CountState::new();
450 if STATE.should_log($n) {
451 $crate::tracing::trace!($($arg)*);
452 }
453 }};
454}
455
456#[macro_export]
458macro_rules! debug_every_n {
459 ($n:expr, $($arg:tt)*) => {{
460 static STATE: $crate::CountState = $crate::CountState::new();
461 if STATE.should_log($n) {
462 $crate::tracing::debug!($($arg)*);
463 }
464 }};
465}
466
467#[macro_export]
469macro_rules! info_every_n {
470 ($n:expr, $($arg:tt)*) => {{
471 static STATE: $crate::CountState = $crate::CountState::new();
472 if STATE.should_log($n) {
473 $crate::tracing::info!($($arg)*);
474 }
475 }};
476}
477
478#[macro_export]
480macro_rules! warn_every_n {
481 ($n:expr, $($arg:tt)*) => {{
482 static STATE: $crate::CountState = $crate::CountState::new();
483 if STATE.should_log($n) {
484 $crate::tracing::warn!($($arg)*);
485 }
486 }};
487}
488
489#[macro_export]
491macro_rules! error_every_n {
492 ($n:expr, $($arg:tt)*) => {{
493 static STATE: $crate::CountState = $crate::CountState::new();
494 if STATE.should_log($n) {
495 $crate::tracing::error!($($arg)*);
496 }
497 }};
498}
499
500#[macro_export]
506macro_rules! trace_first_n {
507 ($n:expr, $($arg:tt)*) => {{
508 static STATE: $crate::FirstNState = $crate::FirstNState::new();
509 if STATE.should_log($n) {
510 $crate::tracing::trace!($($arg)*);
511 }
512 }};
513}
514
515#[macro_export]
517macro_rules! debug_first_n {
518 ($n:expr, $($arg:tt)*) => {{
519 static STATE: $crate::FirstNState = $crate::FirstNState::new();
520 if STATE.should_log($n) {
521 $crate::tracing::debug!($($arg)*);
522 }
523 }};
524}
525
526#[macro_export]
528macro_rules! info_first_n {
529 ($n:expr, $($arg:tt)*) => {{
530 static STATE: $crate::FirstNState = $crate::FirstNState::new();
531 if STATE.should_log($n) {
532 $crate::tracing::info!($($arg)*);
533 }
534 }};
535}
536
537#[macro_export]
539macro_rules! warn_first_n {
540 ($n:expr, $($arg:tt)*) => {{
541 static STATE: $crate::FirstNState = $crate::FirstNState::new();
542 if STATE.should_log($n) {
543 $crate::tracing::warn!($($arg)*);
544 }
545 }};
546}
547
548#[macro_export]
550macro_rules! error_first_n {
551 ($n:expr, $($arg:tt)*) => {{
552 static STATE: $crate::FirstNState = $crate::FirstNState::new();
553 if STATE.should_log($n) {
554 $crate::tracing::error!($($arg)*);
555 }
556 }};
557}
558
559#[macro_export]
566macro_rules! trace_backoff {
567 ($initial:expr, $max:expr, $($arg:tt)*) => {{
568 static STATE: $crate::BackoffState = $crate::BackoffState::new();
569 if STATE.should_log($initial, $max) {
570 $crate::tracing::trace!($($arg)*);
571 }
572 }};
573}
574
575#[macro_export]
578macro_rules! debug_backoff {
579 ($initial:expr, $max:expr, $($arg:tt)*) => {{
580 static STATE: $crate::BackoffState = $crate::BackoffState::new();
581 if STATE.should_log($initial, $max) {
582 $crate::tracing::debug!($($arg)*);
583 }
584 }};
585}
586
587#[macro_export]
590macro_rules! info_backoff {
591 ($initial:expr, $max:expr, $($arg:tt)*) => {{
592 static STATE: $crate::BackoffState = $crate::BackoffState::new();
593 if STATE.should_log($initial, $max) {
594 $crate::tracing::info!($($arg)*);
595 }
596 }};
597}
598
599#[macro_export]
602macro_rules! warn_backoff {
603 ($initial:expr, $max:expr, $($arg:tt)*) => {{
604 static STATE: $crate::BackoffState = $crate::BackoffState::new();
605 if STATE.should_log($initial, $max) {
606 $crate::tracing::warn!($($arg)*);
607 }
608 }};
609}
610
611#[macro_export]
614macro_rules! error_backoff {
615 ($initial:expr, $max:expr, $($arg:tt)*) => {{
616 static STATE: $crate::BackoffState = $crate::BackoffState::new();
617 if STATE.should_log($initial, $max) {
618 $crate::tracing::error!($($arg)*);
619 }
620 }};
621}
622
623#[macro_export]
629macro_rules! trace_on_change {
630 ($value:expr, $($arg:tt)*) => {{
631 static STATE: $crate::OnChangeState = $crate::OnChangeState::new();
632 if STATE.should_log(&$value) {
633 $crate::tracing::trace!($($arg)*);
634 }
635 }};
636}
637
638#[macro_export]
640macro_rules! debug_on_change {
641 ($value:expr, $($arg:tt)*) => {{
642 static STATE: $crate::OnChangeState = $crate::OnChangeState::new();
643 if STATE.should_log(&$value) {
644 $crate::tracing::debug!($($arg)*);
645 }
646 }};
647}
648
649#[macro_export]
651macro_rules! info_on_change {
652 ($value:expr, $($arg:tt)*) => {{
653 static STATE: $crate::OnChangeState = $crate::OnChangeState::new();
654 if STATE.should_log(&$value) {
655 $crate::tracing::info!($($arg)*);
656 }
657 }};
658}
659
660#[macro_export]
662macro_rules! warn_on_change {
663 ($value:expr, $($arg:tt)*) => {{
664 static STATE: $crate::OnChangeState = $crate::OnChangeState::new();
665 if STATE.should_log(&$value) {
666 $crate::tracing::warn!($($arg)*);
667 }
668 }};
669}
670
671#[macro_export]
673macro_rules! error_on_change {
674 ($value:expr, $($arg:tt)*) => {{
675 static STATE: $crate::OnChangeState = $crate::OnChangeState::new();
676 if STATE.should_log(&$value) {
677 $crate::tracing::error!($($arg)*);
678 }
679 }};
680}
681
682#[macro_export]
688macro_rules! trace_once_per_value {
689 ($value:expr, $($arg:tt)*) => {{
690 static STATE: std::sync::OnceLock<$crate::OncePerValueState> = std::sync::OnceLock::new();
691 if STATE.get_or_init($crate::OncePerValueState::new).should_log(&$value) {
692 $crate::tracing::trace!($($arg)*);
693 }
694 }};
695}
696
697#[macro_export]
699macro_rules! debug_once_per_value {
700 ($value:expr, $($arg:tt)*) => {{
701 static STATE: std::sync::OnceLock<$crate::OncePerValueState> = std::sync::OnceLock::new();
702 if STATE.get_or_init($crate::OncePerValueState::new).should_log(&$value) {
703 $crate::tracing::debug!($($arg)*);
704 }
705 }};
706}
707
708#[macro_export]
710macro_rules! info_once_per_value {
711 ($value:expr, $($arg:tt)*) => {{
712 static STATE: std::sync::OnceLock<$crate::OncePerValueState> = std::sync::OnceLock::new();
713 if STATE.get_or_init($crate::OncePerValueState::new).should_log(&$value) {
714 $crate::tracing::info!($($arg)*);
715 }
716 }};
717}
718
719#[macro_export]
721macro_rules! warn_once_per_value {
722 ($value:expr, $($arg:tt)*) => {{
723 static STATE: std::sync::OnceLock<$crate::OncePerValueState> = std::sync::OnceLock::new();
724 if STATE.get_or_init($crate::OncePerValueState::new).should_log(&$value) {
725 $crate::tracing::warn!($($arg)*);
726 }
727 }};
728}
729
730#[macro_export]
732macro_rules! error_once_per_value {
733 ($value:expr, $($arg:tt)*) => {{
734 static STATE: std::sync::OnceLock<$crate::OncePerValueState> = std::sync::OnceLock::new();
735 if STATE.get_or_init($crate::OncePerValueState::new).should_log(&$value) {
736 $crate::tracing::error!($($arg)*);
737 }
738 }};
739}
740
741#[macro_export]
747macro_rules! trace_sample {
748 ($probability:expr, $($arg:tt)*) => {{
749 static STATE: $crate::SampleState = $crate::SampleState::new();
750 if STATE.should_log($probability) {
751 $crate::tracing::trace!($($arg)*);
752 }
753 }};
754}
755
756#[macro_export]
758macro_rules! debug_sample {
759 ($probability:expr, $($arg:tt)*) => {{
760 static STATE: $crate::SampleState = $crate::SampleState::new();
761 if STATE.should_log($probability) {
762 $crate::tracing::debug!($($arg)*);
763 }
764 }};
765}
766
767#[macro_export]
769macro_rules! info_sample {
770 ($probability:expr, $($arg:tt)*) => {{
771 static STATE: $crate::SampleState = $crate::SampleState::new();
772 if STATE.should_log($probability) {
773 $crate::tracing::info!($($arg)*);
774 }
775 }};
776}
777
778#[macro_export]
780macro_rules! warn_sample {
781 ($probability:expr, $($arg:tt)*) => {{
782 static STATE: $crate::SampleState = $crate::SampleState::new();
783 if STATE.should_log($probability) {
784 $crate::tracing::warn!($($arg)*);
785 }
786 }};
787}
788
789#[macro_export]
791macro_rules! error_sample {
792 ($probability:expr, $($arg:tt)*) => {{
793 static STATE: $crate::SampleState = $crate::SampleState::new();
794 if STATE.should_log($probability) {
795 $crate::tracing::error!($($arg)*);
796 }
797 }};
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803 use std::time::Duration;
804
805 #[test]
806 fn once_state_logs_only_first_time() {
807 let state = OnceState::new();
808 assert!(state.should_log());
809 assert!(!state.should_log());
810 assert!(!state.should_log());
811 }
812
813 #[test]
814 fn count_state_logs_every_n() {
815 let state = CountState::new();
816 assert!(state.should_log(3)); assert!(!state.should_log(3)); assert!(!state.should_log(3)); assert!(state.should_log(3)); assert!(!state.should_log(3)); assert!(!state.should_log(3)); assert!(state.should_log(3)); }
825
826 #[test]
827 fn throttle_state_respects_duration() {
828 let state = ThrottleState::new();
829 let short_duration = Duration::from_millis(50);
830
831 assert!(state.should_log(short_duration));
833
834 assert!(!state.should_log(short_duration));
836
837 std::thread::sleep(Duration::from_millis(60));
839
840 assert!(state.should_log(short_duration));
842 }
843
844 #[test]
845 fn once_macro_logs_once() {
846 for _ in 0..5 {
847 trace_once!("test message");
848 }
850 }
851
852 #[test]
853 fn every_macro_compiles() {
854 for i in 0..5 {
855 debug_every!(Duration::from_secs(1), "iteration {}", i);
856 }
857 }
858
859 #[test]
860 fn every_n_macro_compiles() {
861 for i in 0..10 {
862 info_every_n!(3, "iteration {}", i);
863 }
864 }
865
866 #[test]
867 fn first_n_state_logs_first_n_only() {
868 let state = FirstNState::new();
869 assert!(state.should_log(3)); assert!(state.should_log(3)); assert!(state.should_log(3)); assert!(!state.should_log(3)); assert!(!state.should_log(3)); }
876
877 #[test]
878 fn backoff_state_doubles_interval() {
879 let state = BackoffState::new();
880 let initial = Duration::from_millis(20);
881 let max = Duration::from_millis(100);
882
883 assert!(state.should_log(initial, max));
885
886 assert!(!state.should_log(initial, max));
888
889 std::thread::sleep(Duration::from_millis(25));
891 assert!(state.should_log(initial, max));
892
893 std::thread::sleep(Duration::from_millis(25));
895 assert!(!state.should_log(initial, max));
896
897 std::thread::sleep(Duration::from_millis(20));
899 assert!(state.should_log(initial, max));
900 }
901
902 #[test]
903 fn on_change_state_detects_changes() {
904 let state = OnChangeState::new();
905
906 assert!(state.should_log(&42));
908
909 assert!(!state.should_log(&42));
911
912 assert!(state.should_log(&100));
914
915 assert!(!state.should_log(&100));
917
918 assert!(state.should_log(&42));
920 }
921
922 #[test]
923 fn once_per_value_state_logs_unique_values() {
924 let state = OncePerValueState::new();
925
926 assert!(state.should_log(&"apple"));
928 assert!(state.should_log(&"banana"));
929 assert!(state.should_log(&"cherry"));
930
931 assert!(!state.should_log(&"apple"));
933 assert!(!state.should_log(&"banana"));
934
935 assert!(state.should_log(&"date"));
937 }
938
939 #[test]
940 fn sample_state_respects_probability() {
941 let state = SampleState::new();
942
943 let mut logged = false;
945 for _ in 0..100 {
946 if state.should_log(0.0) {
947 logged = true;
948 }
949 }
950 assert!(!logged);
951
952 for _ in 0..10 {
954 assert!(state.should_log(1.0));
955 }
956 }
957
958 #[test]
959 fn first_n_macro_compiles() {
960 for i in 0..10 {
961 info_first_n!(3, "iteration {}", i);
962 }
963 }
964
965 #[test]
966 fn backoff_macro_compiles() {
967 for i in 0..5 {
968 warn_backoff!(Duration::from_secs(1), Duration::from_secs(60), "iteration {}", i);
969 }
970 }
971
972 #[test]
973 fn on_change_macro_compiles() {
974 for i in 0..5 {
975 let value = i % 2;
976 debug_on_change!(value, "value changed to {}", value);
977 }
978 }
979
980 #[test]
981 fn once_per_value_macro_compiles() {
982 for i in 0..10 {
983 let key = i % 3;
984 info_once_per_value!(key, "first time seeing {}", key);
985 }
986 }
987
988 #[test]
989 fn sample_macro_compiles() {
990 for i in 0..10 {
991 trace_sample!(0.5, "sampled iteration {}", i);
992 }
993 }
994}