log_reload/
lib.rs

1//! A [`log::Log`] implementation which dynamically reloads inner loggers.
2//!
3//! [`ReloadLog`] wraps an inner logger and provides a [`ReloadHandle`] to
4//! dynamically replace or modify the inner logger.
5//!
6//! This allows programs to dynamically change the log level or log target at
7//! runtime.
8
9#![deny(warnings, clippy::all, clippy::pedantic, missing_docs)]
10#![forbid(unsafe_code)]
11
12use std::sync::{Arc, RwLock, Weak};
13
14use log::Log;
15
16/// Filter an underlying logger by a given max level.
17///
18/// Only forward log events whose log level is smaller or equal than the
19/// configured level to the underlying logger.
20#[derive(Debug)]
21pub struct LevelFilter<T> {
22    level: log::Level,
23    logger: T,
24}
25
26impl<T> LevelFilter<T> {
27    /// Create a new level filter with the given max `level` around the given `logger`.
28    pub fn new(level: log::Level, logger: T) -> Self {
29        Self { level, logger }
30    }
31
32    /// Get the current log level.
33    pub fn level(&self) -> log::Level {
34        self.level
35    }
36
37    /// Change the maximum log level.
38    pub fn set_level(&mut self, level: log::Level) {
39        self.level = level;
40    }
41
42    fn level_passes(&self, metadata: &log::Metadata) -> bool {
43        metadata.level() <= self.level
44    }
45
46    /// Get a reference to the inner unfiltered logger.
47    pub fn inner(&self) -> &T {
48        &self.logger
49    }
50
51    /// Replace the inner logger.
52    pub fn set_inner(&mut self, logger: T) {
53        self.logger = logger;
54    }
55}
56
57impl<T: Log> log::Log for LevelFilter<T> {
58    /// Wether this logger is enabled.
59    ///
60    /// Return `true` if the log level in `metadata` is less then the level of
61    /// the given `metadata`, and the underlying logger is enabled.
62    fn enabled(&self, metadata: &log::Metadata) -> bool {
63        self.level_passes(metadata) && self.logger.enabled(metadata)
64    }
65
66    /// Forward a log `record` to the underlying logger if it passes the level filter.
67    fn log(&self, record: &log::Record) {
68        if self.level_passes(record.metadata()) {
69            self.logger.log(record);
70        }
71    }
72
73    /// Flush the underlying logger.
74    fn flush(&self) {
75        self.logger.flush();
76    }
77}
78
79/// A logger which can dynamically reload an inner logger.
80///
81/// This enables applications to dyanmically change e.g. the log output or
82/// log level.
83#[derive(Debug)]
84pub struct ReloadLog<T> {
85    underlying: Arc<RwLock<T>>,
86}
87
88impl<T> ReloadLog<T> {
89    /// Create a new reloadable logger over the given `logger`.
90    pub fn new(logger: T) -> Self {
91        Self {
92            underlying: Arc::new(RwLock::new(logger)),
93        }
94    }
95
96    /// Obtain a handle to reload or modify the inner logger.
97    #[must_use]
98    pub fn handle(&self) -> ReloadHandle<T> {
99        ReloadHandle {
100            underlying: Arc::downgrade(&self.underlying),
101        }
102    }
103}
104
105impl<T: Log> Log for ReloadLog<T> {
106    /// Whether the underlying logger is enabled.
107    ///
108    /// Always return `false` if the [`RwLock`] protecting the inner logger is poisoned,
109    /// because we can't trust that the inner logger is valid if a panic occurred
110    /// while it was modified, so we indicate that this logger shouldn't be used at all.
111    fn enabled(&self, metadata: &log::Metadata) -> bool {
112        self.underlying.read().is_ok_and(|l| l.enabled(metadata))
113    }
114
115    /// Log the given `record` with the inner logger.
116    ///
117    /// If the [`RwLock`] protecting the inner logger is poisoned do nothing,
118    /// because we can't trust that the inner logger is valid if a panic occurred
119    /// while it was modified.  The `record` is likely lost in this case.
120    fn log(&self, record: &log::Record) {
121        // We can't reasonably do anything if the lock is poisoned so we ignore the result
122        let _ = self.underlying.read().map(|l| l.log(record));
123    }
124
125    /// Flush the inner logger
126    ///
127    /// If the [`RwLock`] protecting the inner logger is poisoned do nothing,
128    /// because we can't trust that the inner logger is valid if a panic occurred
129    /// while it was modified.
130    fn flush(&self) {
131        // We can't reasonably do anything if the lock is poisoned so we ignore the result
132        let _ = self.underlying.read().map(|l| l.flush());
133    }
134}
135
136/// An error which occurred while reloading the logger.
137#[derive(Debug, Clone, Copy)]
138pub enum ReloadError {
139    /// The logger referenced by the reload handle was dropped meanwhile.
140    Gone,
141    /// The lock protecting the inner logger referenced by the reload is poisoned.
142    ///
143    /// [`ReloadHandle::modify`] will return this error if the lock is poisoned;
144    /// use [`ReloadHandle::replace`] to overwrite the logger and clear the
145    /// poison.
146    Poisoned,
147}
148
149impl std::fmt::Display for ReloadError {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        match self {
152            ReloadError::Gone => write!(f, "Referenced logger was dropped"),
153            ReloadError::Poisoned => write!(f, "Lock poisoned"),
154        }
155    }
156}
157
158impl std::error::Error for ReloadError {}
159
160/// A handle to reload a logger inside a [`ReloadLog`].
161#[derive(Debug, Clone)]
162pub struct ReloadHandle<T> {
163    underlying: Weak<RwLock<T>>,
164}
165
166impl<T> ReloadHandle<T> {
167    /// Replace the inner logger.
168    ///
169    /// This replaces the inner logger of the referenced [`ReloadLog`] with the
170    /// given `logger`, and clears any existing poison from the lock for the
171    /// inner logger.
172    ///
173    /// # Errors
174    ///
175    /// Return [`ReloadError::Gone`] if the logger referenced by this reload
176    /// handle was dropped, and can no longer be reloaded.
177    pub fn replace(&self, logger: T) -> Result<(), ReloadError> {
178        let lock = self.underlying.upgrade().ok_or(ReloadError::Gone)?;
179        let mut guard = lock.write().unwrap_or_else(|error| {
180            // We'll overwrite the logger immediately, so we can safely clear the poison flag.
181            lock.clear_poison();
182            error.into_inner()
183        });
184        *guard = logger;
185        Ok(())
186    }
187
188    /// Modify the inner logger.
189    ///
190    /// Call the given function with a mutable reference to the logger.  Note that
191    /// a lock is held while invoking `f`, so no log messages will be processed
192    /// until `f` returns.
193    ///
194    /// If `f` panics this lock gets poisoned which effectively disables the logger.
195    ///
196    /// # Errors
197    ///
198    /// Return [`ReloadError::Gone`] if the logger referenced by this reload
199    /// handle was dropped, and can no longer be reloaded.
200    ///
201    /// Return [`ReloadError::Poisoned`] if the reload lock is poisoned.
202    pub fn modify<F>(&self, f: F) -> Result<(), ReloadError>
203    where
204        F: FnOnce(&mut T),
205    {
206        let lock = self.underlying.upgrade().ok_or(ReloadError::Gone)?;
207        // We can't clear poison here has we don't know whether `f` will force
208        // the logger into a consistent state after a previous panic.
209        let mut guard = lock.write().map_err(|_| ReloadError::Poisoned)?;
210        f(&mut *guard);
211        Ok(())
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use crate::{LevelFilter, ReloadLog};
218    use log::{Log, Record};
219    use similar_asserts::assert_eq;
220    use std::sync::{Arc, Mutex};
221
222    struct CollectMessages {
223        messages: Mutex<Vec<String>>,
224    }
225
226    impl CollectMessages {
227        fn new() -> Self {
228            Self {
229                messages: Mutex::new(Vec::new()),
230            }
231        }
232    }
233
234    impl Log for CollectMessages {
235        fn enabled(&self, _metadata: &log::Metadata) -> bool {
236            true
237        }
238
239        fn log(&self, record: &log::Record) {
240            let mut guard = self.messages.try_lock().unwrap();
241            guard.push(format!("{}", record.args()));
242        }
243
244        fn flush(&self) {}
245    }
246
247    #[test]
248    fn sanity_check_log_level_ordering() {
249        use log::Level;
250
251        assert!(Level::Error <= Level::Warn);
252        assert!(Level::Warn <= Level::Warn);
253        assert!(Level::Debug >= Level::Warn);
254    }
255
256    #[test]
257    fn level_filter() {
258        let collect_logs = Arc::new(CollectMessages::new());
259        let mut filter = LevelFilter::new(log::Level::Warn, collect_logs.clone());
260
261        for level in log::Level::iter() {
262            filter.log(
263                &Record::builder()
264                    .level(level)
265                    .args(format_args!("{level}"))
266                    .build(),
267            );
268        }
269        let mut messages = collect_logs.messages.try_lock().unwrap();
270        assert_eq!(*messages, vec!["ERROR", "WARN"]);
271        messages.clear();
272        drop(messages);
273
274        filter.set_level(log::Level::Debug);
275
276        for level in log::Level::iter() {
277            filter.log(
278                &Record::builder()
279                    .level(level)
280                    .args(format_args!("{level}"))
281                    .build(),
282            );
283        }
284        let messages = collect_logs.messages.try_lock().unwrap();
285        assert_eq!(*messages, &["ERROR", "WARN", "INFO", "DEBUG"]);
286    }
287
288    #[test]
289    fn reloadlog_replace() {
290        let collect_logs_1 = Arc::new(CollectMessages::new());
291        let collect_logs_2 = Arc::new(CollectMessages::new());
292
293        let reload_log = ReloadLog::new(collect_logs_1.clone());
294        let reload_handle = reload_log.handle();
295
296        reload_log.log(&Record::builder().args(format_args!("Message 1")).build());
297
298        reload_handle.replace(collect_logs_2.clone()).unwrap();
299
300        reload_log.log(&Record::builder().args(format_args!("Message 2")).build());
301
302        let messages_1 = collect_logs_1.messages.try_lock().unwrap();
303        let messages_2 = collect_logs_2.messages.try_lock().unwrap();
304        assert_eq!(*messages_1, &["Message 1"]);
305        assert_eq!(*messages_2, &["Message 2"]);
306    }
307
308    #[test]
309    fn reloadlog_modify() {
310        let collect_logs = Arc::new(CollectMessages::new());
311
312        let reload_log = ReloadLog::new(collect_logs.clone());
313        let reload_handle = reload_log.handle();
314
315        reload_log.log(&Record::builder().args(format_args!("Message 1")).build());
316        let messages = collect_logs.messages.try_lock().unwrap();
317        assert_eq!(*messages, &["Message 1"]);
318        drop(messages);
319
320        // Clear the message store through the reload handle.
321        reload_handle
322            .modify(|l| l.messages.try_lock().unwrap().clear())
323            .unwrap();
324
325        // At this point the first message doesn't appear anymore.
326        reload_log.log(&Record::builder().args(format_args!("Message 2")).build());
327        let messages = collect_logs.messages.try_lock().unwrap();
328        assert_eq!(*messages, &["Message 2"]);
329    }
330}