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
#![doc(
    html_root_url = "https://docs.rs/log-reroute/0.1.2/log-reroute/",
    test(attr(deny(warnings)))
)]
#![deny(missing_docs)]
#![forbid(unsafe_code)]

//! 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
//! extern crate fern;
//! #[macro_use]
//! extern crate log;
//! extern crate log_reroute;
//! extern crate tempfile;
//!
//! use fern::Dispatch;
//! use log::LevelFilter;
//!
//! fn main() {
//!     log::set_max_level(LevelFilter::Off);
//!     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);
//! }
//! ```

extern crate arc_swap;
extern crate log;
#[macro_use]
extern crate once_cell;

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 (unless there's a lot of contention and the slave logger also doesn't lock),
/// it makes it usable in some weird places (like a signal handler).
///
/// The rerouting (eg. changing the slave) is lock-less, but may have to wait for current logging
/// calls to end and concurrent reroutes will block each other.
///
/// # Note
///
/// When switching a logger, no care is taken to pair logging calls. In other words, it is possible
/// a message is written to the old logger and the new logger is flushed. This shouldn't matter in
/// practice, since a logger should flush itself once it is dropped.
pub struct Reroute {
    inner: ArcSwap<Box<Log>>,
}

impl Reroute {
    /// 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 payed here.
    pub fn reroute_boxed(&self, log: Box<Log>) {
        let old = self.inner.swap(Arc::new(log));
        old.flush();
    }

    /// Sets a new slave logger.
    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);
    }
}

impl Log for Reroute {
    fn enabled(&self, metadata: &Metadata) -> bool {
        self.inner.lease().enabled(metadata)
    }
    fn log(&self, record: &Record) {
        self.inner.lease().log(record)
    }
    fn flush(&self) {
        self.inner.lease().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<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> = sync_lazy!(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<Log>) {
    REROUTE.reroute_boxed(log)
}