satlog/
lib.rs

1//! `satlog.rs` is a simple and minimalistic logger designed for SAT solvers and the likes,
2//! following the `DIMACS` syntax of comments starting with `c`.
3//!
4//! The main interaction and setup is done through the [SatLogger] struct.
5//!
6//! This plugs into the generic [`log`](https://crates.io/crates/log) crate and simply displays
7//! the messages to `stdout`.
8//!
9//! Some neat features:
10//! - Messages with level [Level::Info] are displayed without prefix. This allows to show "default"
11//!   logging messages and toggle them with the [LevelFilter::Off].
12//! - Opt-out colors with the `color` feature.
13//!
14//! # Example
15//!
16//! ```
17//! use satlog::SatLogger;
18//! use log::LevelFilter;
19//!
20//! fn main() {
21//!     SatLogger::init(LevelFilter::Info);
22//!
23//!     log::info!("Hello");
24//!     log::trace!("Will not display");
25//! }
26//! ```
27use std::fmt::Display;
28
29use log::LevelFilter;
30use log::Log;
31use log::SetLoggerError;
32
33/// The main logger struct
34pub struct SatLogger {
35    /// All messages below or equal to the [LevelFilter] will be ignored.
36    level: LevelFilter,
37}
38
39fn write_record<S: Display>(levelstr: &S, record: &log::Record) {
40    for line in record.args().to_string().lines() {
41        println!("c {} {}", levelstr, line);
42    }
43}
44
45impl Log for SatLogger {
46    fn enabled(&self, metadata: &log::Metadata) -> bool {
47        metadata.level() <= self.level
48    }
49
50    fn log(&self, record: &log::Record) {
51        if self.enabled(record.metadata()) {
52            let level = record.level();
53            if matches!(level, log::Level::Info) {
54                for line in record.args().to_string().lines() {
55                    println!("c {}", line);
56                }
57            } else {
58                #[cfg(feature = "color")]
59                {
60                    use log::Level;
61                    use colored::Colorize;
62
63                    let levelstring = level.to_string();
64                    let levelstr = levelstring.as_str();
65                    let levelstr = match level {
66                        Level::Info => unreachable!(),
67                        Level::Warn => levelstr.yellow(),
68                        Level::Error => levelstr.red(),
69                        Level::Debug => levelstr.blue(),
70                        Level::Trace => levelstr.purple(),
71                    };
72                    write_record(&levelstr, record);
73                }
74
75                #[cfg(not(feature = "color"))]
76                {
77                    write_record(&record.level(), record);
78                }
79            }
80        }
81    }
82
83    fn flush(&self) {}
84}
85
86impl SatLogger {
87    /// Initializes and sets up a new SatLogger instance.
88    ///
89    /// # Arguments
90    ///
91    /// - `level`: Maximum level of messages to display. Any message with this or below level will
92    ///            be ignored.
93    ///
94    /// # Example
95    ///
96    /// ```
97    /// use satlog::SatLogger;
98    /// use log::LevelFilter;
99    ///
100    /// fn main() {
101    ///     SatLogger::init(LevelFilter::Info);
102    ///
103    ///     log::info!("Hello");
104    ///     log::trace!("Will not display");
105    /// }
106    /// ```
107    pub fn init(level: LevelFilter) -> Result<(), SetLoggerError> {
108        let logger = SatLogger { level };
109        log::set_boxed_logger(Box::new(logger)).map(|()| log::set_max_level(level))
110    }
111}