simple_logging/
lib.rs

1//! A simple logger for the [`log`](https://crates.io/crates/log) facade. One
2//! log message is written per line. Each line also includes the time it was
3//! logged, the logging level and the ID of the thread.
4//!
5//! # Examples
6//!
7//! Most users will simply need to call [`log_to_file()`](fn.log_to_file.html)
8//! with the path to the log file and minimum log level:
9//!
10//! ```rust
11//! # extern crate log;
12//! # extern crate simple_logging;
13//! use log::LevelFilter;
14//!
15//! # fn main() {
16//! simple_logging::log_to_file("test.log", LevelFilter::Info);
17//! # }
18//! ```
19//!
20//! Or use [`log_to_stderr()`](fn.log_to_stderr.html) if simply logging to
21//! `stderr`:
22//!
23//! ```rust
24//! # extern crate log;
25//! # extern crate simple_logging;
26//! use log::LevelFilter;
27//!
28//! # fn main() {
29//! simple_logging::log_to_stderr(LevelFilter::Info);
30//! # }
31//! ```
32//!
33//! For more control, [`log_to()`](fn.log_to.html) can be used with an
34//! arbitrary sink implementing
35//! [`Write`](https://doc.rust-lang.org/std/io/trait.Write.html) +
36//! [`Send`](https://doc.rust-lang.org/std/marker/trait.Send.html) + `'static`:
37//!
38//! ```rust
39//! # extern crate log;
40//! # extern crate simple_logging;
41//! use log::LevelFilter;
42//! use std::io;
43//!
44//! # fn main() {
45//! simple_logging::log_to(io::sink(), LevelFilter::Info);
46//! # }
47//! ```
48//!
49//! # Log format
50//!
51//! Each and every log message obeys the following fixed and easily-parsable
52//! format:
53//!
54//! ```text
55//! [<hh>:<mm>:<ss>.<SSS>] (<thread-id>) <level> <message>\n
56//! ```
57//!
58//! Where `<hh>` denotes hours zero-padded to at least two digits, `<mm>`
59//! denotes minutes zero-padded to two digits, `<ss>` denotes seconds
60//! zero-padded to two digits and `<SSS>` denotes miliseconds zero-padded to
61//! three digits. `<thread-id>` is an implementation-specific alphanumeric ID.
62//! `<level>` is the log level as defined by `log::LogLevel` and padded right
63//! with spaces. `<message>` is the log message. Note that `<message>` is
64//! written to the log as-is, including any embedded newlines.
65//!
66//! # Errors
67//!
68//! Any errors returned by the sink when writing are ignored.
69//!
70//! # Performance
71//!
72//! The logger relies on a global `Mutex` to serialize access to the user
73//! supplied sink.
74
75#[macro_use]
76extern crate lazy_static;
77#[cfg(not(test))]
78extern crate log;
79extern crate thread_id;
80
81#[cfg(test)]
82#[macro_use]
83extern crate log;
84#[cfg(test)]
85extern crate regex;
86
87// TODO: include the changelog as a module when
88// https://github.com/rust-lang/rust/issues/44732 stabilises
89
90use log::{LevelFilter, Log, Metadata, Record};
91use std::fs::File;
92use std::io;
93use std::io::Write;
94use std::path::Path;
95use std::sync::Mutex;
96use std::time::Instant;
97
98lazy_static! {
99    static ref LOGGER: SimpleLogger = SimpleLogger {
100        inner: Mutex::new(None),
101    };
102}
103
104struct SimpleLogger {
105    inner: Mutex<Option<SimpleLoggerInner>>,
106}
107
108impl SimpleLogger {
109    // Set this `SimpleLogger`'s sink and reset the start time.
110    fn renew<T: Write + Send + 'static>(&self, sink: T) {
111        *self.inner.lock().unwrap() = Some(SimpleLoggerInner {
112            start: Instant::now(),
113            sink: Box::new(sink),
114        });
115    }
116}
117
118impl Log for SimpleLogger {
119    fn enabled(&self, _: &Metadata) -> bool {
120        true
121    }
122
123    fn log(&self, record: &Record) {
124        if !self.enabled(record.metadata()) {
125            return;
126        }
127
128        if let Some(ref mut inner) = *self.inner.lock().unwrap() {
129            inner.log(record);
130        }
131    }
132
133    fn flush(&self) {}
134}
135
136struct SimpleLoggerInner {
137    start: Instant,
138    sink: Box<Write + Send>,
139}
140
141impl SimpleLoggerInner {
142    fn log(&mut self, record: &Record) {
143        let now = self.start.elapsed();
144        let seconds = now.as_secs();
145        let hours = seconds / 3600;
146        let minutes = (seconds / 60) % 60;
147        let seconds = seconds % 60;
148        let miliseconds = now.subsec_nanos() / 1_000_000;
149
150        let _ = write!(
151            self.sink,
152            "[{:02}:{:02}:{:02}.{:03}] ({:x}) {:6} {}\n",
153            hours,
154            minutes,
155            seconds,
156            miliseconds,
157            thread_id::get(),
158            record.level(),
159            record.args()
160        );
161    }
162}
163
164/// Configure the [`log`](https://crates.io/crates/log) facade to log to a file.
165///
166/// # Examples
167///
168/// ```rust
169/// # extern crate log;
170/// # extern crate simple_logging;
171/// use log::LevelFilter;
172///
173/// # fn main() {
174/// simple_logging::log_to_file("test.log", LevelFilter::Info);
175/// # }
176/// ```
177pub fn log_to_file<T: AsRef<Path>>(
178    path: T,
179    max_log_level: LevelFilter,
180) -> io::Result<()> {
181    let file = File::create(path)?;
182    log_to(file, max_log_level);
183
184    Ok(())
185}
186
187/// Configure the [`log`](https://crates.io/crates/log) facade to log to
188/// `stderr`.
189///
190/// # Examples
191///
192/// ```rust
193/// # extern crate log;
194/// # extern crate simple_logging;
195/// use log::LevelFilter;
196///
197/// # fn main() {
198/// simple_logging::log_to_stderr(LevelFilter::Info);
199/// # }
200/// ```
201pub fn log_to_stderr(max_log_level: LevelFilter) {
202    log_to(io::stderr(), max_log_level);
203}
204
205/// Configure the [`log`](https://crates.io/crates/log) facade to log to a
206/// custom sink.
207///
208/// # Examples
209///
210/// ```rust
211/// # extern crate log;
212/// # extern crate simple_logging;
213/// use log::LevelFilter;
214/// use std::io;
215///
216/// # fn main() {
217/// simple_logging::log_to(io::sink(), LevelFilter::Info);
218/// # }
219/// ```
220pub fn log_to<T: Write + Send + 'static>(sink: T, max_log_level: LevelFilter) {
221    LOGGER.renew(sink);
222    log::set_max_level(max_log_level);
223    // The only possible error is if this has been called before
224    let _ = log::set_logger(&*LOGGER);
225    // TODO: too much?
226    assert_eq!(log::logger() as *const Log, &*LOGGER as *const Log);
227}
228
229#[cfg(test)]
230mod tests {
231    use log_to;
232
233    use log::LevelFilter::Info;
234    use regex::Regex;
235    use std::io;
236    use std::io::Write;
237    use std::str;
238    use std::sync::{Arc, Mutex};
239
240    struct VecProxy(Arc<Mutex<Vec<u8>>>);
241
242    impl Write for VecProxy {
243        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
244            self.0.lock().unwrap().write(buf)
245        }
246
247        fn flush(&mut self) -> io::Result<()> {
248            Ok(())
249        }
250    }
251
252    // The `log` API forbids calling `set_logger()` more than once in the
253    // lifetime of a single program (even after a `shutdown_logger()`), so
254    // we stash all tests in a single function.
255    // TODO: increase coverage by making `log_to*()` tests integration tests.
256    #[test]
257    fn test() {
258        let buf = Arc::new(Mutex::new(Vec::new()));
259        let proxy = VecProxy(buf.clone());
260        log_to(proxy, Info);
261
262        // Test filtering
263        debug!("filtered");
264        assert!(buf.lock().unwrap().is_empty());
265
266        // Test message format
267        let pat = Regex::new(
268            r"^\[\d\d:\d\d:\d\d.\d\d\d] \([0-9a-zA-Z]+\) INFO   test\n$",
269        )
270        .unwrap();
271        info!("test");
272        let line = str::from_utf8(&buf.lock().unwrap()).unwrap().to_owned();
273        assert!(pat.is_match(&line));
274    }
275}