stderrlog/lib.rs
1// Copyright 2016-2018 Doug Goldstein
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9#![allow(clippy::needless_doctest_main)]
10#![doc(html_root_url = "https://docs.rs/stderrlog/0.6.0")]
11
12//! A simple logger to provide semantics similar to what is expected
13//! of most UNIX utilities by logging to stderr and the higher the
14//! verbosity the higher the log level. It supports the
15//! ability to provide timestamps at different granularities. As
16//! well as colorizing the different log levels.
17//!
18//! ## Simple Use Case
19//!
20//! ```rust
21//! use log::*;
22//!
23//! fn main() {
24//! stderrlog::new().module(module_path!()).init().unwrap();
25//!
26//! error!("some failure");
27//!
28//! // ...
29//! }
30//! ```
31//!
32//! # StructOpt Example
33//!
34//! ```
35//! use log::*;
36//! use structopt::StructOpt;
37//!
38//! /// A StructOpt example
39//! #[derive(StructOpt, Debug)]
40//! #[structopt()]
41//! struct Opt {
42//! /// Silence all output
43//! #[structopt(short = "q", long = "quiet")]
44//! quiet: bool,
45//! /// Verbose mode (-v, -vv, -vvv, etc)
46//! #[structopt(short = "v", long = "verbose", parse(from_occurrences))]
47//! verbose: usize,
48//! /// Timestamp (sec, ms, ns, none)
49//! #[structopt(short = "t", long = "timestamp")]
50//! ts: Option<stderrlog::Timestamp>,
51//! }
52//!
53//! fn main() {
54//! let opt = Opt::from_args();
55//!
56//! stderrlog::new()
57//! .module(module_path!())
58//! .quiet(opt.quiet)
59//! .verbosity(opt.verbose)
60//! .timestamp(opt.ts.unwrap_or(stderrlog::Timestamp::Off))
61//! .init()
62//! .unwrap();
63//! trace!("trace message");
64//! debug!("debug message");
65//! info!("info message");
66//! warn!("warn message");
67//! error!("error message");
68//! }
69//! ```
70//!
71//! ## docopt Example
72//!
73//! ```rust
74//! use log::*;
75//! use docopt::Docopt;
76//! use serde::Deserialize;
77//!
78//! const USAGE: &'static str = "
79//! Usage: program [-q] [-v...]
80//! ";
81//!
82//! #[derive(Debug, Deserialize)]
83//! struct Args {
84//! flag_q: bool,
85//! flag_v: usize,
86//! }
87//!
88//! fn main() {
89//! let args: Args = Docopt::new(USAGE)
90//! .and_then(|d| d.deserialize())
91//! .unwrap_or_else(|e| e.exit());
92//!
93//! stderrlog::new()
94//! .module(module_path!())
95//! .quiet(args.flag_q)
96//! .timestamp(stderrlog::Timestamp::Second)
97//! .verbosity(args.flag_v)
98//! .init()
99//! .unwrap();
100//! trace!("trace message");
101//! debug!("debug message");
102//! info!("info message");
103//! warn!("warn message");
104//! error!("error message");
105//!
106//! // ...
107//! }
108//! ```
109//!
110//! # clap Example
111//!
112//! ```
113//! use clap::{Arg, App, crate_version};
114//! use log::*;
115//! use std::str::FromStr;
116//!
117//! fn main() {
118//! let m = App::new("stderrlog example")
119//! .version(crate_version!())
120//! .arg(Arg::with_name("verbosity")
121//! .short('v')
122//! .takes_value(true)
123//! .multiple(true)
124//! .help("Increase message verbosity"))
125//! .arg(Arg::with_name("quiet")
126//! .short('q')
127//! .help("Silence all output"))
128//! .arg(Arg::with_name("timestamp")
129//! .short('t')
130//! .help("prepend log lines with a timestamp")
131//! .takes_value(true)
132//! .possible_values(&["none", "sec", "ms", "ns"]))
133//! .get_matches();
134//!
135//! let verbose = m.occurrences_of("verbosity") as usize;
136//! let quiet = m.is_present("quiet");
137//! let ts = m.value_of("timestamp").map(|v| {
138//! stderrlog::Timestamp::from_str(v).unwrap_or_else(|_| {
139//! clap::Error::raw(clap::ErrorKind::InvalidValue, "invalid value for 'timestamp'").exit()
140//! })
141//! }).unwrap_or(stderrlog::Timestamp::Off);
142//!
143//! stderrlog::new()
144//! .module(module_path!())
145//! .quiet(quiet)
146//! .verbosity(verbose)
147//! .timestamp(ts)
148//! .init()
149//! .unwrap();
150//! trace!("trace message");
151//! debug!("debug message");
152//! info!("info message");
153//! warn!("warn message");
154//! error!("error message");
155//! }
156//! ```
157//!
158//! ### `log` Compatibility
159//!
160//! The 0.5.x versions of `stderrlog` aim to provide compatibility with
161//! applications using `log` >= 0.4.11.
162//!
163//! ### Rust Compatibility
164//!
165//! `stderrlog` is serious about backwards compat. `stderrlog`
166//! pins the minimum required version of Rust in the CI build.
167//! Bumping the minimum version of Rust is a minor breaking
168//! change and requires a minor version to be bumped.
169//!
170//! The minimum supported Rust version for this release is 1.48.0.
171//!
172//! ### Module Level Logging
173//!
174//! `stderrlog` has the ability to limit the components which can log.
175//! Many crates use [log](https://docs.rs/log/*/log/) but you may not
176//! want their output in your application. For example
177//! [hyper](https://docs.rs/hyper/*/hyper/) makes heavy use of log but
178//! when your application receives `-vvvvv` to enable the `trace!()`
179//! messages you don't want the output of `hyper`'s `trace!()` level.
180//!
181//! To support this `stderrlog` includes a `module()` method allowing
182//! you to specify the modules that are allowed to log. The examples
183//! above use the `module_path!()` macro to enable logging only for
184//! the binary itself but none of its dependencies. To enable logging
185//! from extra crates just add another call to `module()` with the
186//! name of the crate. To enable logging for only a module within
187//! that crate specify `crate::module` to `module()`. crates and
188//! modules will be named the same way would would include them in
189//! source code with `use` (e.g. `some-crate` would be `some_crate`).
190//!
191//! For a good example of how the module level logging works see the
192//! [large-example
193//! crate](https://github.com/cardoe/stderrlog-rs/tree/master/examples/large-example)
194//! under examples, you'll want to run the
195//! following binaries to see all the examples:
196//!
197//! - `cargo run --bin large-example --`
198//! - `cargo run --bin another --`
199//! - `cargo run --bin yet --`
200//!
201//! ### Features
202//!
203//! `stderrlog` has the following default crate features, which can be disabled
204//! to reduce the number of dependencies:
205//!
206//! - `timestamps`: Provides support for log timestamp prefixes (uses the `chrono` crate).
207
208#[cfg(feature = "timestamps")]
209use chrono::Local;
210use is_terminal::IsTerminal;
211use log::{Level, LevelFilter, Log, Metadata, Record};
212use std::cell::RefCell;
213use std::fmt;
214use std::io::{self, Write};
215#[cfg(feature = "timestamps")]
216use std::str::FromStr;
217use termcolor::{Color, ColorSpec, StandardStream, WriteColor};
218
219pub use termcolor::ColorChoice;
220use thread_local::ThreadLocal;
221
222/// State of the timestamping in the logger.
223#[cfg(feature = "timestamps")]
224#[derive(Clone, Copy, Debug)]
225pub enum Timestamp {
226 /// Disable timestamping of log messages
227 Off,
228 /// Timestamp with second granularity
229 Second,
230 /// Timestamp with millisecond granularity
231 Millisecond,
232 /// Timestamp with microsecond granularity
233 Microsecond,
234 /// Timestamp with nanosecond granularity
235 Nanosecond,
236}
237
238/// Provides a quick conversion of the following:
239///
240/// - "sec" -> `Timestamp::Second`
241/// - "ms" -> `Timestamp::Millisecond`
242/// - "us" -> `Timestamp::Microsecond`
243/// - "ns" -> `Timestamp::Nanosecond`
244/// - "none" | "off" -> `Timestamp::Off`
245///
246/// This is provided as a helper for argument parsers
247#[cfg(feature = "timestamps")]
248impl FromStr for Timestamp {
249 type Err = String;
250
251 fn from_str(s: &str) -> Result<Self, Self::Err> {
252 match s {
253 "ns" => Ok(Timestamp::Nanosecond),
254 "ms" => Ok(Timestamp::Millisecond),
255 "us" => Ok(Timestamp::Microsecond),
256 "sec" => Ok(Timestamp::Second),
257 "none" | "off" => Ok(Timestamp::Off),
258 _ => Err("invalid value".into()),
259 }
260 }
261}
262
263/// Data specific to this logger
264pub struct StdErrLog {
265 verbosity: LevelFilter,
266 quiet: bool,
267 show_level: bool,
268 #[cfg(feature = "timestamps")]
269 timestamp: Timestamp,
270 modules: Vec<String>,
271 writer: ThreadLocal<RefCell<StandardStream>>,
272 color_choice: ColorChoice,
273 show_module_names: bool,
274}
275
276impl fmt::Debug for StdErrLog {
277 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
278 let mut builder = f.debug_struct("StdErrLog");
279 builder
280 .field("verbosity", &self.verbosity)
281 .field("quiet", &self.quiet)
282 .field("show_level", &self.show_level);
283 #[cfg(feature = "timestamps")]
284 builder.field("timestamp", &self.timestamp);
285 builder
286 .field("modules", &self.modules)
287 .field("writer", &"stderr")
288 .field("color_choice", &self.color_choice)
289 .field("show_module_names", &self.show_module_names)
290 .finish()
291 }
292}
293
294impl Clone for StdErrLog {
295 fn clone(&self) -> StdErrLog {
296 StdErrLog {
297 modules: self.modules.clone(),
298 writer: ThreadLocal::new(),
299 ..*self
300 }
301 }
302}
303
304impl Log for StdErrLog {
305 fn enabled(&self, metadata: &Metadata) -> bool {
306 metadata.level() <= self.log_level_filter() && self.includes_module(metadata.target())
307 }
308
309 fn log(&self, record: &Record) {
310 // if logging isn't enabled for this level do a quick out
311 if !self.enabled(record.metadata()) {
312 return;
313 }
314
315 let writer = self
316 .writer
317 .get_or(|| RefCell::new(StandardStream::stderr(self.color_choice)));
318 let writer = writer.borrow_mut();
319 let mut writer = io::LineWriter::new(writer.lock());
320 let color = match record.metadata().level() {
321 Level::Error => Color::Red,
322 Level::Warn => Color::Yellow,
323 Level::Info => Color::Blue,
324 Level::Debug => Color::Cyan,
325 Level::Trace => Color::Magenta,
326 };
327 {
328 // A failure here indicates the stream closed. Do not panic.
329 writer
330 .get_mut()
331 .set_color(ColorSpec::new().set_fg(Some(color)))
332 .ok();
333 }
334
335 if self.show_module_names {
336 let _ = write!(writer, "{}: ", record.metadata().target());
337 }
338 #[cfg(feature = "timestamps")]
339 match self.timestamp {
340 Timestamp::Second => {
341 let fmt = "%Y-%m-%dT%H:%M:%S%:z";
342 let _ = write!(writer, "{} - ", Local::now().format(fmt));
343 }
344 Timestamp::Millisecond => {
345 let fmt = "%Y-%m-%dT%H:%M:%S%.3f%:z";
346 let _ = write!(writer, "{} - ", Local::now().format(fmt));
347 }
348 Timestamp::Microsecond => {
349 let fmt = "%Y-%m-%dT%H:%M:%S%.6f%:z";
350 let _ = write!(writer, "{} - ", Local::now().format(fmt));
351 }
352 Timestamp::Nanosecond => {
353 let fmt = "%Y-%m-%dT%H:%M:%S%.9f%:z";
354 let _ = write!(writer, "{} - ", Local::now().format(fmt));
355 }
356 Timestamp::Off => {}
357 }
358 if self.show_level {
359 let _ = write!(writer, "{} ", record.level());
360 }
361 writer.flush().ok();
362 {
363 // A failure here indicates the stream closed. Do not panic.
364 writer.get_mut().reset().ok();
365 }
366 let _ = writeln!(writer, "{}", record.args());
367 }
368
369 fn flush(&self) {
370 let writer = self
371 .writer
372 .get_or(|| RefCell::new(StandardStream::stderr(self.color_choice)));
373 let mut writer = writer.borrow_mut();
374 writer.flush().ok();
375 }
376}
377
378pub enum LogLevelNum {
379 Off,
380 Error,
381 Warn,
382 Info,
383 Debug,
384 Trace,
385}
386
387impl From<usize> for LogLevelNum {
388 fn from(verbosity: usize) -> Self {
389 match verbosity {
390 0 => LogLevelNum::Error,
391 1 => LogLevelNum::Warn,
392 2 => LogLevelNum::Info,
393 3 => LogLevelNum::Debug,
394 _ => LogLevelNum::Trace,
395 }
396 }
397}
398
399impl From<Level> for LogLevelNum {
400 fn from(l: Level) -> Self {
401 match l {
402 Level::Error => LogLevelNum::Error,
403 Level::Warn => LogLevelNum::Warn,
404 Level::Info => LogLevelNum::Info,
405 Level::Debug => LogLevelNum::Debug,
406 Level::Trace => LogLevelNum::Trace,
407 }
408 }
409}
410
411impl From<LevelFilter> for LogLevelNum {
412 fn from(l: LevelFilter) -> Self {
413 match l {
414 LevelFilter::Off => LogLevelNum::Off,
415 LevelFilter::Error => LogLevelNum::Error,
416 LevelFilter::Warn => LogLevelNum::Warn,
417 LevelFilter::Info => LogLevelNum::Info,
418 LevelFilter::Debug => LogLevelNum::Debug,
419 LevelFilter::Trace => LogLevelNum::Trace,
420 }
421 }
422}
423
424impl StdErrLog {
425 /// creates a new stderr logger
426 pub fn new() -> StdErrLog {
427 StdErrLog {
428 verbosity: LevelFilter::Error,
429 quiet: false,
430 show_level: true,
431 #[cfg(feature = "timestamps")]
432 timestamp: Timestamp::Off,
433 modules: Vec::new(),
434 writer: ThreadLocal::new(),
435 color_choice: ColorChoice::Auto,
436 show_module_names: false,
437 }
438 }
439
440 /// Sets the verbosity level of messages that will be displayed
441 ///
442 /// Values can be supplied as:
443 /// - usize
444 /// - log::Level
445 /// - log::LevelFilter
446 /// - LogLevelNum
447 ///
448 /// Values map as follows:
449 /// 0 -> Error
450 /// 1 -> Warn
451 /// 2 -> Info
452 /// 3 -> Debug
453 /// 4 or higher -> Trace
454 pub fn verbosity<V: Into<LogLevelNum>>(&mut self, verbosity: V) -> &mut StdErrLog {
455 self.verbosity = match verbosity.into() {
456 LogLevelNum::Off => LevelFilter::Off,
457 LogLevelNum::Error => LevelFilter::Error,
458 LogLevelNum::Warn => LevelFilter::Warn,
459 LogLevelNum::Info => LevelFilter::Info,
460 LogLevelNum::Debug => LevelFilter::Debug,
461 LogLevelNum::Trace => LevelFilter::Trace,
462 };
463 self
464 }
465
466 /// silence all output, no matter the value of verbosity
467 pub fn quiet(&mut self, quiet: bool) -> &mut StdErrLog {
468 self.quiet = quiet;
469 self
470 }
471
472 /// Enables or disables the use of levels in log messages (default is true)
473 pub fn show_level(&mut self, levels: bool) -> &mut StdErrLog {
474 self.show_level = levels;
475 self
476 }
477
478 /// Enables or disables the use of timestamps in log messages
479 #[cfg(feature = "timestamps")]
480 pub fn timestamp(&mut self, timestamp: Timestamp) -> &mut StdErrLog {
481 self.timestamp = timestamp;
482 self
483 }
484
485 /// Enables or disables the use of color in log messages
486 pub fn color(&mut self, choice: ColorChoice) -> &mut StdErrLog {
487 self.color_choice = choice;
488 self
489 }
490
491 /// specify a module to allow to log to stderr
492 pub fn module<T: Into<String>>(&mut self, module: T) -> &mut StdErrLog {
493 self._module(module.into())
494 }
495
496 /// Enables or disables the use of module names in log messages
497 pub fn show_module_names(&mut self, show_module_names: bool) -> &mut StdErrLog {
498 self.show_module_names = show_module_names;
499 self
500 }
501
502 fn _module(&mut self, module: String) -> &mut StdErrLog {
503 // If Ok, the module was already found
504 if let Err(i) = self.modules.binary_search(&module) {
505 // If a super-module of the current module already exists, don't insert this module
506 if i == 0 || !is_submodule(&self.modules[i - 1], &module) {
507 // Remove any submodules of the module we're inserting
508 let submodule_count = self.modules[i..]
509 .iter()
510 .take_while(|possible_submodule| is_submodule(&module, possible_submodule))
511 .count();
512 self.modules.drain(i..i + submodule_count);
513 self.modules.insert(i, module);
514 }
515 }
516 self
517 }
518
519 /// specify modules to allow to log to stderr
520 pub fn modules<T: Into<String>, I: IntoIterator<Item = T>>(
521 &mut self,
522 modules: I,
523 ) -> &mut StdErrLog {
524 for module in modules {
525 self.module(module);
526 }
527 self
528 }
529
530 fn log_level_filter(&self) -> LevelFilter {
531 if self.quiet {
532 LevelFilter::Off
533 } else {
534 self.verbosity
535 }
536 }
537
538 fn includes_module(&self, module_path: &str) -> bool {
539 // If modules is empty, include all module paths
540 if self.modules.is_empty() {
541 return true;
542 }
543 // if a prefix of module_path is in `self.modules`, it must
544 // be located at the first location before
545 // where module_path would be.
546 match self
547 .modules
548 .binary_search_by(|module| module.as_str().cmp(module_path))
549 {
550 Ok(_) => {
551 // Found exact module: return true
552 true
553 }
554 Err(0) => {
555 // if there's no item which would be located before module_path, no prefix is there
556 false
557 }
558 Err(i) => is_submodule(&self.modules[i - 1], module_path),
559 }
560 }
561
562 /// sets the the logger as active
563 pub fn init(&mut self) -> Result<(), log::SetLoggerError> {
564 /* if the user is using auto color choices then
565 * detect if stderr is a tty, if it is continue
566 * otherwise turn off colors by default
567 */
568 self.color_choice = match self.color_choice {
569 ColorChoice::Auto => {
570 if io::stderr().is_terminal() {
571 ColorChoice::Auto
572 } else {
573 ColorChoice::Never
574 }
575 }
576 other => other,
577 };
578 log::set_max_level(self.log_level_filter());
579 log::set_boxed_logger(Box::new(self.clone()))
580 }
581}
582
583impl Default for StdErrLog {
584 fn default() -> Self {
585 StdErrLog::new()
586 }
587}
588
589/// creates a new stderr logger
590pub fn new() -> StdErrLog {
591 StdErrLog::new()
592}
593
594fn is_submodule(parent: &str, possible_child: &str) -> bool {
595 // Treat as bytes, because we'll be doing slicing, and we only care about ':' chars
596 let parent = parent.as_bytes();
597 let possible_child = possible_child.as_bytes();
598
599 // a longer module path cannot be a parent of a shorter module path
600 if parent.len() > possible_child.len() {
601 return false;
602 }
603
604 // If the path up to the parent isn't the same as the child,
605 if parent != &possible_child[..parent.len()] {
606 return false;
607 }
608
609 // Either the path is exactly the same, or the sub module should have a "::" after
610 // the length of the parent path. This prevents things like 'a::bad' being considered
611 // a submodule of 'a::b'
612 parent.len() == possible_child.len()
613 || possible_child.get(parent.len()..parent.len() + 2) == Some(b"::")
614}
615
616#[cfg(test)]
617mod tests {
618 use super::{is_submodule, StdErrLog};
619
620 #[test]
621 fn submodule() {
622 assert!(is_submodule("a", "a::b::c::d"));
623 assert!(is_submodule("a::b::c", "a::b::c::d"));
624 assert!(is_submodule("a::b::c", "a::b::c"));
625 assert!(!is_submodule("a::b::c", "a::bad::c"));
626 assert!(!is_submodule("a::b::c", "a::b::cab"));
627 assert!(!is_submodule("a::b::c", "a::b::cab::d"));
628 assert!(!is_submodule("a::b::c", "a::b"));
629 assert!(!is_submodule("a::b::c", "a::bad"));
630 }
631
632 #[test]
633 fn test_default_level() {
634 super::new().module(module_path!()).init().unwrap();
635
636 assert_eq!(log::Level::Error, log::max_level())
637 }
638
639 #[test]
640 fn modules_display_all() {
641 let logger = StdErrLog::new();
642 assert!(logger.includes_module("good"));
643 }
644
645 #[test]
646 fn modules_display_exact_match() {
647 let mut logger = StdErrLog::new();
648 logger.module("good");
649 assert!(logger.includes_module("good"));
650 assert!(!logger.includes_module("bad"));
651 }
652
653 #[test]
654 fn modules_display_module() {
655 let mut logger = StdErrLog::new();
656 logger.module("good");
657 assert!(logger.includes_module("good::foo"));
658 assert!(logger.includes_module("good::bar"));
659 assert!(logger.includes_module("good"));
660 assert!(!logger.includes_module("bad"));
661 }
662
663 #[test]
664 fn modules_display_submodule() {
665 let mut logger = StdErrLog::new();
666 logger.module("good::foo");
667 assert!(logger.includes_module("good::foo"));
668 assert!(!logger.includes_module("good::bar"));
669 assert!(!logger.includes_module("good"));
670 assert!(!logger.includes_module("bad"));
671 }
672}