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