capture_logger/
lib.rs

1#![doc(html_root_url = "https://docs.rs/capture-logger/0.1.1")]
2//! [`log`](https://docs.rs/log/0.4.11/log/) implementation for testing.
3//!
4//! ## Dependencies
5//!
6//! ```toml
7//! [dev-dependencies]
8//! capture-logger = "0.1"
9//! ```
10//!
11//! ## Example
12//!
13//! ```
14//! use capture_logger::{begin_capture, pop_captured};
15//!
16//! # fn main() {
17//! begin_capture();
18//! log::debug!("LOG");
19//! assert_eq!(pop_captured().unwrap().message(), "LOG");
20//! # }
21//! ```
22//!
23//! ## License
24//!
25//! Licensed under either of
26//! * Apache License, Version 2.0
27//!   ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
28//! * MIT license
29//!   ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
30//! at your option.
31//!
32//! ## Contribution
33//!
34//! Unless you explicitly state otherwise, any contribution intentionally submitted
35//! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
36//! dual licensed as above, without any additional terms or conditions.!
37use std::cell::RefCell;
38use std::collections::VecDeque;
39use std::sync::Once;
40
41pub use log::Level;
42
43static INITIALIZED: Once = Once::new();
44
45/// Captured log Record.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Record {
48    level: Level,
49    message: String,
50}
51
52impl Record {
53    /// get logged level.
54    pub fn level(&self) -> Level {
55        self.level
56    }
57
58    /// get log message.
59    pub fn message(&self) -> &str {
60        &self.message
61    }
62}
63
64thread_local! {
65    static CAPTURED: RefCell<Option<VecDeque<Record>>> = RefCell::new(None);
66}
67
68static LOGGER: Logger = Logger;
69
70struct Logger;
71
72impl log::Log for Logger {
73    fn enabled(&self, _: &log::Metadata) -> bool {
74        true
75    }
76
77    fn log(&self, record: &log::Record) {
78        CAPTURED.with(|cap| {
79            if let Some(queue) = &mut *cap.borrow_mut() {
80                queue.push_back(Record {
81                    level: record.level(),
82                    message: format!("{}", record.args()),
83                });
84            }
85        });
86    }
87
88    fn flush(&self) {}
89}
90
91/// Begin capturing log.
92///
93/// # Panics
94///
95/// Panics if already begin captured.
96pub fn begin_capture() {
97    INITIALIZED.call_once(|| {
98        log::set_logger(&LOGGER).unwrap();
99        log::set_max_level(log::LevelFilter::Trace);
100    });
101
102    CAPTURED.with(|cap| {
103        let queue = &mut *cap.borrow_mut();
104        if queue.is_none() {
105            *queue = Some(VecDeque::new());
106        } else {
107            panic!("already captured.")
108        }
109    });
110}
111
112/// End capturing log.
113pub fn end_capture() {
114    CAPTURED.with(|cap| {
115        *cap.borrow_mut() = None;
116    });
117}
118
119/// Pop captured log record.
120///
121/// # Panics
122///
123/// Panics if no begin capture yet.
124pub fn pop_captured() -> Option<Record> {
125    CAPTURED.with(|cap| {
126        if let Some(queue) = &mut *cap.borrow_mut() {
127            queue.pop_front()
128        } else {
129            panic!("not captured.");
130        }
131    })
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use log::Log;
138
139    #[test]
140    fn test_logger() {
141        LOGGER.flush(); // NOP
142    }
143}