simplog/lib.rs
1#![deny(missing_docs)]
2
3//! `simplog` is as its name suggests a very simpler logging implementation for rust
4//! It provides three main features
5//! - Settable log level (or verbosity) (default is Log::Level::Error)
6//! - Optional prefix each log line with the Level it corresponds to (after timestamp if present)
7//! - Optional timestamp prefixed to each line
8
9use std::io;
10use std::io::{stderr, stdout, Write};
11use std::str::FromStr;
12
13use log::{Level, Log, Metadata, Record};
14use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
15use atty::Stream;
16use std::time::Instant;
17
18/// Use the `SimpleLogger` struct to initialize a logger. From then on, the rust `log` framework
19/// should be used to output log statements as usual.
20///
21/// # Example
22/// ```
23/// use log::{info, error};
24/// use simplog::SimpleLogger;
25///
26/// SimpleLogger::init(None); // Log level defaults to `Error`
27/// info!("Hello World!");
28/// // Produces nothing
29/// error!("Goodbye World!");
30/// // Produces "Goodbye World"
31/// ```
32#[derive(Clone)]
33pub struct SimpleLogger {
34 log_level: Level,
35 prefix: bool,
36 start: Instant,
37 timestamp: bool,
38}
39
40const DEFAULT_LOG_LEVEL: Level = Level::Error;
41
42impl SimpleLogger {
43 /// Initialize the logger, with an optionally provided log level (`verbosity`) in a `&str`
44 /// If `None` is provided -> The log level will be set to `Error`
45 /// If 'Some(`verbosity') is a &str with a valid log level, the string will be parsed and if
46 /// valid set as the log level.
47 ///
48 /// # Example
49 /// ```
50 /// use log::info;
51 /// use simplog::SimpleLogger;
52 ///
53 /// SimpleLogger::init(Some("info"));
54 /// info!("Hello World!");
55 /// // Produces "Hello World"
56 /// ```
57 pub fn init(verbosity: Option<&str>) {
58 Self::init_prefix(verbosity, true)
59 }
60
61 /// Initialize the logger, with an optionally provided log level (`verbosity`) in a &str
62 /// The default log level is Error if `None` is provided.
63 /// `prefix` determines whether each log line output is prefixed with the level that produced it
64 ///
65 /// # Example
66 /// ```
67 /// use log::info;
68 /// use simplog::SimpleLogger;
69 ///
70 /// SimpleLogger::init_prefix(Some("info"), true);
71 /// info!("Hello World!");
72 /// // Produces "INFO - Hello World"
73 /// ```
74 pub fn init_prefix(verbosity: Option<&str>, prefix: bool) {
75 Self::init_prefix_timestamp(verbosity, prefix, false);
76 }
77
78 /// Initialize the logger, with an optionally provided log level (`verbosity`) in a &str
79 /// The default log level is Error if `None` is provided.
80 /// `prefix` determines whether each log line output is prefixed with the level that produced it
81 /// if `timestamp` is true, each log line will be prefixed with the elapsed time since the
82 /// logger was initialized
83 ///
84 /// # Example
85 /// ```
86 /// use log::info;
87 /// use simplog::SimpleLogger;
88 ///
89 /// let mut logger = SimpleLogger::init_prefix_timestamp(Some("info"), false, true);
90 /// info!("Hello World!");
91 /// // Produces "1.246717ms Hello World"
92 /// ```
93 pub fn init_prefix_timestamp(verbosity: Option<&str>, prefix: bool, timestamp: bool) {
94 let log_level = parse_log_level(verbosity);
95 let simplogger = SimpleLogger {
96 log_level,
97 prefix,
98 start: Instant::now(),
99 timestamp,
100 };
101 let logger = Box::new(simplogger);
102 let _ = log::set_boxed_logger(logger);
103 log::set_max_level(log_level.to_level_filter());
104 }
105}
106
107/*
108 Parse an optional String argument ("debug", "info", "trace", "error") into a log level that
109 can be used to set verbosity of output. If none is supplied or there is an error parsing the
110 String, then the DEFAULT_LOG_LEVEL of "Error" is used.
111*/
112fn parse_log_level(arg: Option<&str>) -> Level {
113 match arg {
114 None => DEFAULT_LOG_LEVEL,
115 Some(arg) => match Level::from_str(arg) {
116 Ok(ll) => ll,
117 Err(_) => DEFAULT_LOG_LEVEL
118 }
119 }
120}
121
122/*
123 Implement the simpler logger.
124 - depending on the way Logger was created a prefix with the level of the output is printed or not
125 - "Error" level output is printed to STDERR, all other levels are printed to STDOUT
126*/
127impl Log for SimpleLogger {
128 fn enabled(&self, metadata: &Metadata) -> bool {
129 metadata.level() <= self.log_level
130 }
131
132 fn log(&self, record: &Record) {
133 if self.enabled(record.metadata()) {
134 let mut stdout = StandardStream::stdout(ColorChoice::Always);
135
136 let message = if self.prefix {
137 format!("{}\t- {}", record.level(), record.args())
138 } else {
139 format!("{}", record.args())
140 };
141
142 if atty::is(Stream::Stdout) {
143 match record.level() {
144 Level::Error => stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red))).unwrap(),
145 Level::Warn => stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow))).unwrap(),
146 Level::Info=> stdout.set_color(ColorSpec::new().set_fg(Some(Color::Magenta))).unwrap(),
147 Level::Debug=> stdout.set_color(ColorSpec::new().set_fg(Some(Color::Blue))).unwrap(),
148 Level::Trace=> stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green))).unwrap()
149 }
150 }
151
152 if self.timestamp {
153 let stdout = io::stdout();
154 let mut handle = stdout.lock();
155 let _ = handle.write_all(
156 format!("{:?} {}\n", self.start.elapsed(), message).as_bytes());
157 } else {
158 let stdout = io::stdout();
159 let mut handle = stdout.lock();
160 let _ = handle.write_all(
161 format!("{}\n", message).as_bytes());
162 }
163 }
164 }
165
166 fn flush(&self) {
167 stdout().flush().unwrap();
168 stderr().flush().unwrap();
169 }
170}
171
172#[cfg(test)]
173mod test {
174 use log::Level;
175
176 use super::SimpleLogger;
177
178 #[test]
179 fn no_log_level_arg() {
180 assert_eq!(super::parse_log_level(None), super::DEFAULT_LOG_LEVEL);
181 }
182
183 #[test]
184 fn invalid_log_level_arg() {
185 assert_eq!(super::parse_log_level(Some("garbage")), super::DEFAULT_LOG_LEVEL);
186 }
187
188 #[test]
189 fn info_log_level_arg() {
190 assert_eq!(super::parse_log_level(Some("INFO")), Level::Info);
191 }
192
193 #[test]
194 fn error_log_level_arg() {
195 assert_eq!(super::parse_log_level(Some("ERROR")), Level::Error);
196 }
197
198 #[test]
199 fn parse_debug_log_level_arg() {
200 assert_eq!(super::parse_log_level(Some("DEBUG")), Level::Debug);
201 assert_eq!(super::parse_log_level(Some("debug")), Level::Debug);
202 }
203
204 #[test]
205 fn init_legacy_no_levels() {
206 SimpleLogger::init(None);
207 }
208
209 #[test]
210 fn init_legacy_debug_level() {
211 SimpleLogger::init(Some("DEBUG"));
212 }
213
214 #[test]
215 fn init_no_level_no_prefix() {
216 SimpleLogger::init_prefix(None, false);
217 }
218}