cursive_core/
logger.rs

1//! Logging utilities.
2
3use lazy_static::lazy_static;
4use std::cmp::Ord;
5use std::collections::VecDeque;
6use std::str::FromStr;
7use std::sync::{Mutex, RwLock};
8
9/// Saves all log records in a global deque.
10///
11/// Uses a `DebugView` to access it.
12///
13/// # Examples
14///
15/// Set log levels from env vars
16///
17/// ```
18/// # use cursive_core::*;
19/// logger::set_filter_levels_from_env();
20/// logger::init();
21/// ```
22///
23/// Set log levels explicitly.
24///
25/// ```
26/// # use cursive_core::*;
27/// # use log::LevelFilter;
28/// logger::set_internal_filter_level(LevelFilter::Warn);
29/// logger::set_external_filter_level(LevelFilter::Debug);
30/// logger::init();
31/// ```
32
33pub struct CursiveLogger;
34
35lazy_static! {
36    /// Circular buffer for logs. Use it to implement [`DebugView`].
37    ///
38    /// [`DebugView`]: ../views/struct.DebugView.html
39    pub static ref LOGS: Mutex<VecDeque<Record>> =
40        Mutex::new(VecDeque::with_capacity(1_000));
41
42    // Log filter level for log messages from within cursive
43    static ref INT_FILTER_LEVEL: RwLock<log::LevelFilter> = RwLock::new(log::LevelFilter::Trace);
44    // Log filter level for log messages from sources outside of cursive
45    static ref EXT_FILTER_LEVEL: RwLock<log::LevelFilter> = RwLock::new(log::LevelFilter::Trace);
46}
47
48/// Sets the internal log filter level.
49pub fn set_internal_filter_level(level: log::LevelFilter) {
50    *INT_FILTER_LEVEL.write().unwrap() = level;
51}
52
53/// Sets the external log filter level.
54pub fn set_external_filter_level(level: log::LevelFilter) {
55    *EXT_FILTER_LEVEL.write().unwrap() = level;
56}
57
58/// Sets log filter levels based on environment variables `RUST_LOG` and `CURSIVE_LOG`.
59/// If `RUST_LOG` is set, then both internal and external log levels are set to match.
60/// If `CURSIVE_LOG` is set, then the internal log level is set to match with precedence over
61/// `RUST_LOG`.
62pub fn set_filter_levels_from_env() {
63    if let Ok(rust_log) = std::env::var("RUST_LOG") {
64        match log::LevelFilter::from_str(&rust_log) {
65            Ok(filter_level) => {
66                set_internal_filter_level(filter_level);
67                set_external_filter_level(filter_level);
68            }
69            Err(e) => log::warn!("Could not parse RUST_LOG: {}", e),
70        }
71    }
72    if let Ok(cursive_log) = std::env::var("CURSIVE_LOG") {
73        match log::LevelFilter::from_str(&cursive_log) {
74            Ok(filter_level) => {
75                set_internal_filter_level(filter_level);
76            }
77            Err(e) => log::warn!("Could not parse CURSIVE_LOG: {}", e),
78        }
79    }
80}
81
82/// A log record.
83pub struct Record {
84    /// Log level used for this record
85    pub level: log::Level,
86    /// Time this message was logged
87    pub time: time::OffsetDateTime,
88    /// Message content
89    pub message: String,
90}
91
92/// Log a record in cursive's log queue.
93pub fn log(record: &log::Record) {
94    let mut logs = LOGS.lock().unwrap();
95    // TODO: customize the format? Use colors? Save more info?
96    if logs.len() == logs.capacity() {
97        logs.pop_front();
98    }
99    logs.push_back(Record {
100        level: record.level(),
101        message: format!("{}", record.args()),
102        time: time::OffsetDateTime::now_local().unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
103    });
104}
105
106impl log::Log for CursiveLogger {
107    fn enabled(&self, metadata: &log::Metadata) -> bool {
108        if metadata.target().starts_with("cursive_core::") {
109            metadata.level() <= *INT_FILTER_LEVEL.read().unwrap()
110        } else {
111            metadata.level() <= *EXT_FILTER_LEVEL.read().unwrap()
112        }
113    }
114
115    fn log(&self, record: &log::Record) {
116        if self.enabled(record.metadata()) {
117            log(record);
118        }
119    }
120
121    fn flush(&self) {}
122}
123
124/// Initialize the Cursive logger.
125///
126/// Make sure this is the only logger your are using.
127///
128/// Use a [`DebugView`](crate::views::DebugView) to see the logs, or use
129/// [`Cursive::toggle_debug_console()`](crate::Cursive::toggle_debug_console()).
130pub fn init() {
131    log::set_max_level((*INT_FILTER_LEVEL.read().unwrap()).max(*EXT_FILTER_LEVEL.read().unwrap()));
132    // This will panic if `set_logger` was already called.
133    log::set_logger(&CursiveLogger).unwrap();
134}
135
136/// Return a logger that stores records in cursive's log queue.
137///
138/// These logs can then be read by a [`DebugView`](crate::views::DebugView).
139///
140/// An easier alternative might be to use [`init()`].
141pub fn get_logger() -> CursiveLogger {
142    CursiveLogger
143}
144
145/// Adds `n` more entries to cursive's log queue.
146///
147/// Most of the time you don't need to use this directly.
148pub fn reserve_logs(n: usize) {
149    LOGS.lock().unwrap().reserve(n);
150}