log_once/
lib.rs

1#![warn(clippy::all, clippy::pedantic)]
2#![allow(clippy::new_without_default, clippy::new_without_default)]
3#![allow(clippy::useless_attribute, clippy::missing_docs_in_private_items)]
4#![allow(clippy::use_self)]
5
6//! Collection of helper macros for logging some events only once.
7//!
8//! This crate provide macro in the `log_once` family (`warn_once!`,
9//! `trace_once!`, ...); that only send a logging event once for every message.
10//! It rely and uses the logging infrastructure in the [log][log] crate; and
11//! is fully compatible with any logger implementation.
12//!
13//! These macro will store the already seen messages in a static `BTreeSet`, and
14//! check if a message is in the set before sending the log event.
15//!
16//! [log]: https://crates.io/crates/log
17//!
18//! # Examples
19//!
20//! ```rust
21//! use log::info;
22//! use log_once::{info_once, warn_once};
23//!
24//! # #[derive(Debug)] pub struct Yak(String);
25//! # impl Yak { fn shave(&self, _: u32) {} }
26//! # fn find_a_razor() -> Result<u32, u32> { Ok(1) }
27//! pub fn shave_the_yak(yaks: &[Yak]) {
28//!     for yak in yaks {
29//!         info!(target: "yak_events", "Commencing yak shaving for {yak:?}");
30//!
31//!         loop {
32//!             match find_a_razor() {
33//!                 Ok(razor) => {
34//!                     // This will only appear once in the logger output for each razor
35//!                     info_once!("Razor located: {razor}");
36//!                     yak.shave(razor);
37//!                     break;
38//!                 }
39//!                 Err(err) => {
40//!                     // This will only appear once in the logger output for each error
41//!                     warn_once!("Unable to locate a razor: {err}, retrying");
42//!                 }
43//!             }
44//!         }
45//!     }
46//! }
47//!
48//! # fn main() {}
49//! ```
50
51// We re-export the log crate so that the log_once macros can use it directly.
52// That way users don't need to depend on `log` explicitly.
53// This is especially nice for people who use `tracing` for logging, but still use `log_once`.
54pub use log;
55
56pub use log::Level;
57
58use std::collections::BTreeSet;
59use std::sync::{Mutex, MutexGuard, PoisonError};
60
61#[doc(hidden)]
62pub struct MessagesSet {
63    inner: Mutex<BTreeSet<String>>,
64}
65
66impl MessagesSet {
67    #[must_use]
68    pub fn new() -> Self {
69        Self {
70            inner: Mutex::new(BTreeSet::new()),
71        }
72    }
73
74    /// # Errors
75    /// Mutex poisoning.
76    pub fn lock(
77        &self,
78    ) -> Result<MutexGuard<BTreeSet<String>>, PoisonError<MutexGuard<BTreeSet<String>>>> {
79        self.inner.lock()
80    }
81}
82
83/// Standard logging macro, logging events once for each arguments.
84///
85/// The log event will only be emitted once for each combinaison of target/arguments.
86///
87/// This macro will generically log with the specified `Level` and `format!`
88/// based argument list.
89///
90/// The `max_level_*` features can be used to statically disable logging at
91/// various levels.
92#[macro_export]
93macro_rules! log_once {
94    (@CREATE STATIC) => ({
95        use ::std::sync::Once;
96        static mut SEEN_MESSAGES: *const $crate::MessagesSet = 0 as *const _;
97        static ONCE: Once = Once::new();
98        unsafe {
99            ONCE.call_once(|| {
100                let singleton = $crate::MessagesSet::new();
101                SEEN_MESSAGES = ::std::mem::transmute(Box::new(singleton));
102            });
103            &(*SEEN_MESSAGES)
104        }
105    });
106
107    // log_once!(target: "my_target", Level::Info, "Some {}", "logging")
108    (target: $target:expr, $lvl:expr, $($arg:tt)+) => ({
109        let message = format!($($arg)+);
110        let seen_messages_mutex = $crate::log_once!(@CREATE STATIC);
111        let mut seen_messages_lock = seen_messages_mutex.lock().expect("Mutex was poisoned");
112        let event = String::from(stringify!($target)) + stringify!($lvl) + message.as_ref();
113        if seen_messages_lock.insert(event) {
114            $crate::log::log!(target: $target, $lvl, "{}", message);
115        }
116    });
117
118    // log_once!(Level::Info, "Some {}", "logging")
119    ($lvl:expr, $($arg:tt)+) => ($crate::log_once!(target: module_path!(), $lvl,  $($arg)+));
120}
121
122/// Logs a message once at the error level.
123///
124/// The log event will only be emitted once for each combinaison of target/arguments.
125///
126/// Logging at this level is disabled if the `max_level_off` feature is present.
127#[macro_export]
128macro_rules! error_once {
129    (target: $target:expr, $($arg:tt)*) => (
130        $crate::log_once!(target: $target, $crate::Level::Error, $($arg)*);
131    );
132    ($($arg:tt)*) => (
133        $crate::log_once!($crate::Level::Error, $($arg)*);
134    )
135}
136
137/// Logs a message once at the warn level.
138///
139/// The log event will only be emitted once for each combinaison of target/arguments.
140///
141/// Logging at this level is disabled if any of the following features are
142/// present: `max_level_off` or `max_level_error`.
143///
144/// When building in release mode (i.e., without the `debug_assertions` option),
145/// logging at this level is also disabled if any of the following features are
146/// present: `release_max_level_off` or `max_level_error`.
147#[macro_export]
148macro_rules! warn_once {
149    (target: $target:expr, $($arg:tt)*) => (
150        $crate::log_once!(target: $target, $crate::Level::Warn, $($arg)*);
151    );
152    ($($arg:tt)*) => (
153        $crate::log_once!($crate::Level::Warn, $($arg)*);
154    )
155}
156
157/// Logs a message once at the info level.
158///
159/// The log event will only be emitted once for each combinaison of target/arguments.
160///
161/// Logging at this level is disabled if any of the following features are
162/// present: `max_level_off`, `max_level_error`, or `max_level_warn`.
163///
164/// When building in release mode (i.e., without the `debug_assertions` option),
165/// logging at this level is also disabled if any of the following features are
166/// present: `release_max_level_off`, `release_max_level_error`, or
167/// `release_max_level_warn`.
168#[macro_export]
169macro_rules! info_once {
170    (target: $target:expr, $($arg:tt)*) => (
171        $crate::log_once!(target: $target, $crate::Level::Info, $($arg)*);
172    );
173    ($($arg:tt)*) => (
174        $crate::log_once!($crate::Level::Info, $($arg)*);
175    )
176}
177
178/// Logs a message once at the debug level.
179///
180/// The log event will only be emitted once for each combinaison of target/arguments.
181///
182/// Logging at this level is disabled if any of the following features are
183/// present: `max_level_off`, `max_level_error`, `max_level_warn`, or
184/// `max_level_info`.
185///
186/// When building in release mode (i.e., without the `debug_assertions` option),
187/// logging at this level is also disabled if any of the following features are
188/// present: `release_max_level_off`, `release_max_level_error`,
189/// `release_max_level_warn`, or `release_max_level_info`.
190#[macro_export]
191macro_rules! debug_once {
192    (target: $target:expr, $($arg:tt)*) => (
193        $crate::log_once!(target: $target, $crate::Level::Debug, $($arg)*);
194    );
195    ($($arg:tt)*) => (
196        $crate::log_once!($crate::Level::Debug, $($arg)*);
197    )
198}
199
200/// Logs a message once at the trace level.
201///
202/// The log event will only be emitted once for each combinaison of target/arguments.
203///
204/// Logging at this level is disabled if any of the following features are
205/// present: `max_level_off`, `max_level_error`, `max_level_warn`,
206/// `max_level_info`, or `max_level_debug`.
207///
208/// When building in release mode (i.e., without the `debug_assertions` option),
209/// logging at this level is also disabled if any of the following features are
210/// present: `release_max_level_off`, `release_max_level_error`,
211/// `release_max_level_warn`, `release_max_level_info`, or
212/// `release_max_level_debug`.
213#[macro_export]
214macro_rules! trace_once {
215    (target: $target:expr, $($arg:tt)*) => (
216        $crate::log_once!(target: $target, $crate::Level::Trace, $($arg)*);
217    );
218    ($($arg:tt)*) => (
219        $crate::log_once!($crate::Level::Trace, $($arg)*);
220    )
221}
222
223#[cfg(test)]
224mod tests {
225    use log::{LevelFilter, Log, Metadata, Record};
226    use std::cell::Cell;
227    use std::sync::Once;
228
229    struct SimpleLogger;
230    impl Log for SimpleLogger {
231        fn enabled(&self, _: &Metadata) -> bool {
232            true
233        }
234        fn log(&self, _: &Record) {}
235        fn flush(&self) {}
236    }
237
238    static LOGGER: SimpleLogger = SimpleLogger;
239
240    #[test]
241    fn called_once() {
242        static START: Once = Once::new();
243        START.call_once(|| {
244            log::set_logger(&LOGGER).expect("Could not set the logger");
245            log::set_max_level(LevelFilter::Trace);
246        });
247
248        let counter = Cell::new(0);
249        let function = || {
250            counter.set(counter.get() + 1);
251            counter.get()
252        };
253
254        info_once!("Counter is: {}", function());
255        assert_eq!(counter.get(), 1);
256    }
257}