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}