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}