log_reroute/
lib.rs

1#![doc(test(attr(deny(warnings))))]
2#![warn(missing_docs)]
3#![forbid(unsafe_code)]
4// We have Arc<Box<dyn ...>>. It is redundant allocation from a PoV, but arc-swap needs
5// Arc<S: Sized>, so we don't have much choice in that matter.
6#![allow(clippy::redundant_allocation)]
7
8//! Crate to reroute logging messages at runtime.
9//!
10//! The [`log`](https://crates.io/crates/log) logging facade allows to set only a single
11//! destination during the whole lifetime of program. If you want to change the logging destination
12//! multiple times, you can use [`Reroute`](struct.Reroute.html) (either directly, or through the
13//! [`init`](fn.init.html) and [`reroute`](fn.reroute.html) functions).
14//!
15//! This may be useful if you want to log to `stderr` before you know where the main logs will go.
16//!
17//! ```rust
18//! use fern::Dispatch;
19//! use log::{info, LevelFilter};
20//!
21//! fn main() {
22//!     // Enable logging of Debug and more severe messages.
23//!     log::set_max_level(LevelFilter::Debug);
24//!     info!("This log message goes nowhere");
25//!     log_reroute::init().unwrap();
26//!     info!("Still goes nowhere");
27//!     // Log to stderr
28//!     let early_logger = Dispatch::new().chain(std::io::stderr()).into_log().1;
29//!     log_reroute::reroute_boxed(early_logger);
30//!     info!("This one goes to stderr");
31//!     // Load file name from config and log to that file
32//!     let file = tempfile::tempfile().unwrap();
33//!     let logger = Dispatch::new().chain(file).into_log().1;
34//!     log_reroute::reroute_boxed(logger);
35//!     info!("And this one to the file");
36//!     // Stop logging
37//!     log_reroute::reroute(log_reroute::Dummy);
38//! }
39//! ```
40
41use std::sync::Arc;
42
43use arc_swap::ArcSwap;
44use log::{Log, Metadata, Record, SetLoggerError};
45use once_cell::sync::Lazy;
46
47/// A logger that doesn't log.
48///
49/// This is used to stub out the reroute in case no other log is set.
50pub struct Dummy;
51
52impl Log for Dummy {
53    fn enabled(&self, _metadata: &Metadata) -> bool {
54        false
55    }
56    fn log(&self, _record: &Record) {}
57    fn flush(&self) {}
58}
59
60/// A logging proxy.
61///
62/// This logger forwards all calls to currently configured slave logger.
63///
64/// The log routing is implemented in a lock-less and wait-less manner. While not necessarily faster
65/// than using a mutex, the performance should be more predictable and stable in face of contention
66/// from multiple threads. This assumes the slave logger also doesn't lock.
67pub struct Reroute {
68    inner: ArcSwap<Box<dyn Log>>,
69}
70
71impl Reroute {
72    /// Creates a new [`Reroute`] logger.
73    ///
74    /// No destination is set yet (it's sent to the [`Dummy`] instance), therefore all log messages
75    /// are thrown away.
76    pub fn new() -> Self {
77        Default::default()
78    }
79
80    /// Sets a new slave logger.
81    ///
82    /// In case it is already in a box, you should prefer this method over
83    /// [`reroute`](#fn.reroute), since there'll be less indirection.
84    ///
85    /// The old logger (if any) is flushed before dropping. In general, loggers should flush
86    /// themselves on drop, but that may take time. This way we (mostly) ensure the cost of
87    /// flushing is paid here.
88    pub fn reroute_boxed(&self, log: Box<dyn Log>) {
89        self.reroute_arc(Arc::new(log))
90    }
91
92    /// Sets a slave logger.
93    ///
94    /// Another variant of [`reroute_boxed`][Reroute::reroute_boxed], accepting the inner
95    /// representation. This can be combined with a previous [`get`][Reroute::get].
96    ///
97    /// Note that the `Arc<Box<dyn Log>>` (double indirection) is necessary evil, since arc-swap
98    /// can't accept `!Sized` types.
99    pub fn reroute_arc(&self, log: Arc<Box<dyn Log>>) {
100        let old = self.inner.swap(log);
101        old.flush();
102    }
103
104    /// Sets a new slave logger.
105    ///
106    /// See [`reroute_boxed`][Reroute::reroute_boxed] for more details.
107    pub fn reroute<L: Log + 'static>(&self, log: L) {
108        self.reroute_boxed(Box::new(log));
109    }
110
111    /// Stubs out the logger.
112    ///
113    /// Sets the slave logger to one that does nothing (eg. [`Dummy`](struct.Dummy.html)).
114    pub fn clear(&self) {
115        self.reroute(Dummy);
116    }
117
118    /// Gives access to the inner logger.
119    ///
120    /// # Notes
121    ///
122    /// The logger may be still in use by other threads, etc. It may be in use even after the
123    /// current thread called [`clear`][Reroute::clear] or [`reroute`][Reroute::reroute], at least
124    /// for a while.
125    pub fn get(&self) -> Arc<Box<dyn Log>> {
126        self.inner.load_full()
127    }
128}
129
130impl Log for Reroute {
131    fn enabled(&self, metadata: &Metadata) -> bool {
132        self.inner.load().enabled(metadata)
133    }
134    fn log(&self, record: &Record) {
135        self.inner.load().log(record)
136    }
137    fn flush(&self) {
138        self.inner.load().flush()
139    }
140}
141
142impl Default for Reroute {
143    /// Creates a reroute with a [`Dummy`](struct.Dummy.html) slave logger.
144    fn default() -> Self {
145        Self {
146            inner: ArcSwap::from(Arc::new(Box::new(Dummy) as Box<dyn Log>)),
147        }
148    }
149}
150
151/// A global [`Reroute`](struct.Reroute.html) object.
152///
153/// This one is manipulated by the global functions:
154///
155/// * [`init`](fn.init.html)
156/// * [`reroute`](fn.reroute.html)
157/// * [`reroute_boxed`](fn.reroute_boxed.html)
158pub static REROUTE: Lazy<Reroute> = Lazy::new(Reroute::default);
159
160/// Installs the global [`Reroute`](struct.Reroute.html) instance into the
161/// [`log`](https://crates.io/crates/log) facade.
162///
163/// Note that the default slave is [`Dummy`](struct.Dummy.html) and you need to call
164/// [`reroute`](fn.reroute.html) or [`reroute_boxed`](fn.reroute_boxed.html).
165pub fn init() -> Result<(), SetLoggerError> {
166    log::set_logger(&*REROUTE)
167}
168
169/// Changes the slave of the global [`Reroute`](struct.Reroute.html) instance.
170///
171/// If you have a boxed logger, use [`reroute_boxed`](fn.reroute_boxed.html).
172pub fn reroute<L: Log + 'static>(log: L) {
173    REROUTE.reroute(log);
174}
175
176/// Changes the slave of the global [`Reroute`](struct.Reroute.html) instance.
177pub fn reroute_boxed(log: Box<dyn Log>) {
178    REROUTE.reroute_boxed(log)
179}