slog_envlogger/
lib.rs

1// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! A logger configured via an environment variable.
12//!
13//! See the documentation for the log crate for more information about its API.
14//!
15//! ## Enabling logging
16//!
17//! Log levels are controlled on a per-module basis, and by default all logging
18//! is disabled except for `error!`. Logging is controlled via the `RUST_LOG`
19//! environment variable. The value of this environment variable is a
20//! comma-separated list of logging directives. A logging directive is of the
21//! form:
22//!
23//! ```text
24//! path::to::module=log_level
25//! ```
26//!
27//! The path to the module is rooted in the name of the crate it was compiled
28//! for, so if your program is contained in a file `hello.rs`, for example, to
29//! turn on logging for this file you would use a value of `RUST_LOG=hello`.
30//! Furthermore, this path is a prefix-search, so all modules nested in the
31//! specified module will also have logging enabled.
32//!
33//! The actual `log_level` is optional to specify. If omitted, all logging will
34//! be enabled. If specified, it must be one of the strings `debug`, `error`,
35//! `info`, `warn`, or `trace`.
36//!
37//! As the log level for a module is optional, the module to enable logging for
38//! is also optional. If only a `log_level` is provided, then the global log
39//! level for all modules is set to this value.
40//!
41//! Some examples of valid values of `RUST_LOG` are:
42//!
43//! * `hello` turns on all logging for the 'hello' module
44//! * `info` turns on all info logging
45//! * `hello=debug` turns on debug logging for 'hello'
46//! * `hello,std::option` turns on hello, and std's option logging
47//! * `error,hello=warn` turn on global error logging and also warn for hello
48//!
49//! ## Filtering results
50//!
51//! A RUST_LOG directive may include a regex filter. The syntax is to append `/`
52//! followed by a regex. Each message is checked against the regex, and is only
53//! logged if it matches. Note that the matching is done after formatting the
54//! log string but before adding any logging meta-data. There is a single filter
55//! for all modules.
56//!
57//! Some examples:
58//!
59//! * `hello/foo` turns on all logging for the 'hello' module where the log
60//!   message includes 'foo'.
61//! * `info/f.o` turns on all info logging where the log message includes 'foo',
62//!   'f1o', 'fao', etc.
63//! * `hello=debug/foo*foo` turns on debug logging for 'hello' where the log
64//!   message includes 'foofoo' or 'fofoo' or 'fooooooofoo', etc.
65//! * `error,hello=warn/[0-9] scopes` turn on global error logging and also
66//!   warn for hello. In both cases the log message must include a single digit
67//!   number followed by 'scopes'.
68
69#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
70       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
71       html_root_url = "http://doc.rust-lang.org/env_logger/")]
72#![cfg_attr(test, deny(warnings))]
73
74extern crate slog;
75extern crate slog_term;
76extern crate slog_stdlog;
77extern crate slog_scope;
78extern crate log;
79
80use std::{env, result, sync};
81use std::cell::RefCell;
82use slog::*;
83
84#[cfg(feature = "regex")]
85#[path = "regex.rs"]
86mod filter;
87
88#[cfg(not(feature = "regex"))]
89#[path = "string.rs"]
90mod filter;
91
92thread_local! {
93    static TL_BUF: RefCell<String> = RefCell::new(String::new())
94}
95
96/// `EnvLogger` drain.
97pub struct EnvLogger<T : Drain> {
98    drain : T,
99    directives: Vec<LogDirective>,
100    filter: Option<filter::Filter>,
101}
102
103/// LogBuilder acts as builder for initializing the EnvLogger.
104/// It can be used change the enviromental variable used
105/// to provide the logging directives and also set the default log level filter.
106pub struct LogBuilder<T : Drain> {
107    drain : T,
108    directives: Vec<LogDirective>,
109    filter: Option<filter::Filter>,
110}
111
112impl<T : Drain> LogBuilder<T> {
113    /// Initializes the log builder with defaults
114    pub fn new(d : T) -> Self {
115        LogBuilder {
116            drain : d,
117            directives: Vec::new(),
118            filter: None,
119        }
120    }
121
122    /// Adds filters to the logger
123    ///
124    /// The given module (if any) will log at most the specified level provided.
125    /// If no module is provided then the filter will apply to all log messages.
126    pub fn filter(mut self,
127                  module: Option<&str>,
128                  level: FilterLevel) -> Self {
129        self.directives.push(LogDirective {
130            name: module.map(|s| s.to_string()),
131            level: level,
132        });
133        self
134    }
135
136    /// Parses the directives string in the same form as the RUST_LOG
137    /// environment variable.
138    ///
139    /// See the module documentation for more details.
140    pub fn parse(mut self, filters: &str) -> Self {
141        let (directives, filter) = parse_logging_spec(filters);
142
143        self.filter = filter;
144
145        for directive in directives {
146            self.directives.push(directive);
147        }
148        self
149    }
150
151    /// Build an env logger.
152    pub fn build(mut self) -> EnvLogger<T> {
153        if self.directives.is_empty() {
154            // Adds the default filter if none exist
155            self.directives.push(LogDirective {
156                name: None,
157                level: FilterLevel::Error,
158            });
159        } else {
160            // Sort the directives by length of their name, this allows a
161            // little more efficient lookup at runtime.
162            self.directives.sort_by(|a, b| {
163                let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0);
164                let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0);
165                alen.cmp(&blen)
166            });
167        }
168
169        let LogBuilder {
170            drain,
171            directives,
172            filter,
173        } = self;
174
175        EnvLogger {
176            drain: drain,
177            directives: directives,
178            filter: filter,
179        }
180    }
181}
182
183impl<T : Drain> EnvLogger<T> {
184    pub fn new(d : T) -> Self {
185        let mut builder = LogBuilder::new(d);
186
187        if let Ok(s) = env::var("RUST_LOG") {
188            builder = builder.parse(&s);
189        }
190
191        builder.build()
192    }
193
194    pub fn filter(&self) -> FilterLevel {
195        self.directives.iter()
196            .map(|d| d.level).max()
197            .unwrap_or(FilterLevel::Off)
198    }
199
200    fn enabled(&self, level: Level, module: &str) -> bool {
201        // Search for the longest match, the vector is assumed to be pre-sorted.
202        for directive in self.directives.iter().rev() {
203            match directive.name {
204                Some(ref name) if !module.starts_with(&**name) => {},
205                Some(..) | None => {
206                    return level.as_usize() <= directive.level.as_usize()
207                }
208            }
209        }
210        false
211    }
212}
213
214impl<T : Drain> Drain for EnvLogger<T>
215where T : Drain<Ok=()> {
216    type Err = T::Err;
217    type Ok = ();
218    fn log(&self, info: &Record, val : &OwnedKVList) -> result::Result<(), T::Err> {
219        if !self.enabled(info.level(), info.module()) {
220            return Ok(());
221        }
222
223        if let Some(filter) = self.filter.as_ref() {
224            if !filter.is_match(&format!("{}", info.msg())) {
225                return Ok(())
226            }
227        }
228
229        TL_BUF.with(|buf| {
230            let mut buf = buf.borrow_mut();
231            let res = self.drain.log(info, val);
232            buf.clear();
233            res
234        })
235    }
236}
237
238struct LogDirective {
239    name: Option<String>,
240    level: FilterLevel,
241}
242
243/// Create a `EnvLogger` using `RUST_LOG` environment variable
244pub fn new<T : Drain>(d : T) -> EnvLogger<T> {
245    let mut builder = LogBuilder::new(d);
246
247    if let Ok(s) = env::var("RUST_LOG") {
248        builder = builder.parse(&s);
249    }
250
251    builder.build()
252}
253
254/// Use a default `EnvLogger` as global logging drain
255///
256/// This is for lazy devs that with minimal amount of work want to convert
257/// software that used standard Rust `env_logger` crate to
258/// `slog-env_logger`.
259///
260/// It's an easy first step, but using `init()` you're not gaining almost
261/// anything that `slog` has to offer, so I highly encourage to use `new()`
262/// instead and explicitly configure your loggers.
263pub fn init() -> std::result::Result<slog_scope::GlobalLoggerGuard, log::SetLoggerError> {
264    let drain = slog_term::CompactFormat::new(
265        slog_term::TermDecorator::new().stderr().build()
266        ).build();
267    let drain = new(drain);
268    let drain = sync::Mutex::new(drain.fuse());
269
270    let guard = slog_scope::set_global_logger(Logger::root(drain.fuse(), o!()).into_erased());
271    slog_stdlog::init()?;
272
273    Ok(guard)
274}
275
276/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo")
277/// and return a vector with log directives.
278fn parse_logging_spec(spec: &str) -> (Vec<LogDirective>, Option<filter::Filter>) {
279    let mut dirs = Vec::new();
280
281    let mut parts = spec.split('/');
282    let mods = parts.next();
283    let filter = parts.next();
284    if parts.next().is_some() {
285        println!("warning: invalid logging spec '{}', \
286                 ignoring it (too many '/'s)", spec);
287        return (dirs, None);
288    }
289    mods.map(|m| { for s in m.split(',') {
290        if s.len() == 0 { continue }
291        let mut parts = s.split('=');
292        let (log_level, name) = match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
293            (Some(part0), None, None) => {
294                // if the single argument is a log-level string or number,
295                // treat that as a global fallback
296                match part0.parse() {
297                    Ok(num) => (num, None),
298                    Err(_) => (FilterLevel::max(), Some(part0)),
299                }
300            }
301            (Some(part0), Some(""), None) => (FilterLevel::max(), Some(part0)),
302            (Some(part0), Some(part1), None) => {
303                match part1.parse() {
304                    Ok(num) => (num, Some(part0)),
305                    _ => {
306                        println!("warning: invalid logging spec '{}', \
307                                 ignoring it", part1);
308                        continue
309                    }
310                }
311            },
312            _ => {
313                println!("warning: invalid logging spec '{}', \
314                         ignoring it", s);
315                continue
316            }
317        };
318        dirs.push(LogDirective {
319            name: name.map(|s| s.to_string()),
320            level: log_level,
321        });
322    }});
323
324    let filter = filter.map_or(None, |filter| {
325        match filter::Filter::new(filter) {
326            Ok(re) => Some(re),
327            Err(e) => {
328                println!("warning: invalid regex filter - {}", e);
329                None
330            }
331        }
332    });
333
334    return (dirs, filter);
335}
336
337#[cfg(test)]
338mod tests {
339    use slog::{Level, FilterLevel};
340    use super::slog;
341
342    use super::{LogBuilder, EnvLogger, LogDirective, parse_logging_spec};
343
344    fn make_logger(dirs: Vec<LogDirective>) -> EnvLogger<slog::Discard> {
345        let mut logger = LogBuilder::new(slog::Discard).build();
346        logger.directives = dirs;
347        logger
348    }
349
350    #[test]
351    fn filter_info() {
352        let logger = LogBuilder::new(slog::Discard).filter(None, FilterLevel::Info).build();
353        assert!(logger.enabled(Level::Info, "crate1"));
354        assert!(!logger.enabled(Level::Debug, "crate1"));
355    }
356
357    #[test]
358    fn filter_beginning_longest_match() {
359        let logger = LogBuilder::new(slog::Discard)
360                        .filter(Some("crate2"), FilterLevel::Info)
361                        .filter(Some("crate2::mod"), FilterLevel::Debug)
362                        .filter(Some("crate1::mod1"), FilterLevel::Warning)
363                        .build();
364        assert!(logger.enabled(Level::Debug, "crate2::mod1"));
365        assert!(!logger.enabled(Level::Debug, "crate2"));
366    }
367
368    #[test]
369    fn parse_default() {
370        let logger = LogBuilder::new(slog::Discard).parse("info,crate1::mod1=warn").build();
371        assert!(logger.enabled(Level::Warning, "crate1::mod1"));
372        assert!(logger.enabled(Level::Info, "crate2::mod2"));
373    }
374
375    #[test]
376    fn match_full_path() {
377        let logger = make_logger(vec![
378            LogDirective {
379                name: Some("crate2".to_string()),
380                level: FilterLevel::Info
381            },
382            LogDirective {
383                name: Some("crate1::mod1".to_string()),
384                level: FilterLevel::Warning
385            }
386        ]);
387        assert!(logger.enabled(Level::Warning, "crate1::mod1"));
388        assert!(!logger.enabled(Level::Info, "crate1::mod1"));
389        assert!(logger.enabled(Level::Info, "crate2"));
390        assert!(!logger.enabled(Level::Debug, "crate2"));
391    }
392
393    #[test]
394    fn no_match() {
395        let logger = make_logger(vec![
396            LogDirective { name: Some("crate2".to_string()), level: FilterLevel::Info },
397            LogDirective { name: Some("crate1::mod1".to_string()), level: FilterLevel::Warning }
398        ]);
399        assert!(!logger.enabled(Level::Warning, "crate3"));
400    }
401
402    #[test]
403    fn match_beginning() {
404        let logger = make_logger(vec![
405            LogDirective { name: Some("crate2".to_string()), level: FilterLevel::Info },
406            LogDirective { name: Some("crate1::mod1".to_string()), level: FilterLevel::Warning }
407        ]);
408        assert!(logger.enabled(Level::Info, "crate2::mod1"));
409    }
410
411    #[test]
412    fn match_beginning_longest_match() {
413        let logger = make_logger(vec![
414            LogDirective { name: Some("crate2".to_string()), level: FilterLevel::Info },
415            LogDirective { name: Some("crate2::mod".to_string()), level: FilterLevel::Debug },
416            LogDirective { name: Some("crate1::mod1".to_string()), level: FilterLevel::Warning }
417        ]);
418        assert!(logger.enabled(Level::Debug, "crate2::mod1"));
419        assert!(!logger.enabled(Level::Debug, "crate2"));
420    }
421
422    #[test]
423    fn match_default() {
424        let logger = make_logger(vec![
425            LogDirective { name: None, level: FilterLevel::Info },
426            LogDirective { name: Some("crate1::mod1".to_string()), level: FilterLevel::Warning }
427        ]);
428        assert!(logger.enabled(Level::Warning, "crate1::mod1"));
429        assert!(logger.enabled(Level::Info, "crate2::mod2"));
430    }
431
432    #[test]
433    fn zero_level() {
434        let logger = make_logger(vec![
435            LogDirective { name: None, level: FilterLevel::Info },
436            LogDirective { name: Some("crate1::mod1".to_string()), level: FilterLevel::Off }
437        ]);
438        assert!(!logger.enabled(Level::Error, "crate1::mod1"));
439        assert!(logger.enabled(Level::Info, "crate2::mod2"));
440    }
441
442    #[test]
443    fn parse_logging_spec_valid() {
444        let (dirs, filter) = parse_logging_spec("crate1::mod1=error,crate1::mod2,crate2=debug");
445        assert_eq!(dirs.len(), 3);
446        assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
447        assert_eq!(dirs[0].level, FilterLevel::Error);
448
449        assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
450        assert_eq!(dirs[1].level, FilterLevel::max());
451
452        assert_eq!(dirs[2].name, Some("crate2".to_string()));
453        assert_eq!(dirs[2].level, FilterLevel::Debug);
454        assert!(filter.is_none());
455    }
456
457    #[test]
458    fn parse_logging_spec_invalid_crate() {
459        // test parse_logging_spec with multiple = in specification
460        let (dirs, filter) = parse_logging_spec("crate1::mod1=warn=info,crate2=debug");
461        assert_eq!(dirs.len(), 1);
462        assert_eq!(dirs[0].name, Some("crate2".to_string()));
463        assert_eq!(dirs[0].level, FilterLevel::Debug);
464        assert!(filter.is_none());
465    }
466
467    #[test]
468    fn parse_logging_spec_invalid_log_level() {
469        // test parse_logging_spec with 'noNumber' as log level
470        let (dirs, filter) = parse_logging_spec("crate1::mod1=noNumber,crate2=debug");
471        assert_eq!(dirs.len(), 1);
472        assert_eq!(dirs[0].name, Some("crate2".to_string()));
473        assert_eq!(dirs[0].level, FilterLevel::Debug);
474        assert!(filter.is_none());
475    }
476
477    #[test]
478    fn parse_logging_spec_string_log_level() {
479        // test parse_logging_spec with 'warn' as log level
480        let (dirs, filter) = parse_logging_spec("crate1::mod1=wrong,crate2=warn");
481        assert_eq!(dirs.len(), 1);
482        assert_eq!(dirs[0].name, Some("crate2".to_string()));
483        assert_eq!(dirs[0].level, FilterLevel::Warning);
484        assert!(filter.is_none());
485    }
486
487    #[test]
488    fn parse_logging_spec_empty_log_level() {
489        // test parse_logging_spec with '' as log level
490        let (dirs, filter) = parse_logging_spec("crate1::mod1=wrong,crate2=");
491        assert_eq!(dirs.len(), 1);
492        assert_eq!(dirs[0].name, Some("crate2".to_string()));
493        assert_eq!(dirs[0].level, FilterLevel::max());
494        assert!(filter.is_none());
495    }
496
497    #[test]
498    fn parse_logging_spec_global() {
499        // test parse_logging_spec with no crate
500        let (dirs, filter) = parse_logging_spec("warn,crate2=debug");
501        assert_eq!(dirs.len(), 2);
502        assert_eq!(dirs[0].name, None);
503        assert_eq!(dirs[0].level, FilterLevel::Warning);
504        assert_eq!(dirs[1].name, Some("crate2".to_string()));
505        assert_eq!(dirs[1].level, FilterLevel::Debug);
506        assert!(filter.is_none());
507    }
508
509    #[test]
510    fn parse_logging_spec_valid_filter() {
511        let (dirs, filter) = parse_logging_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc");
512        assert_eq!(dirs.len(), 3);
513        assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
514        assert_eq!(dirs[0].level, FilterLevel::Error);
515
516        assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
517        assert_eq!(dirs[1].level, FilterLevel::max());
518
519        assert_eq!(dirs[2].name, Some("crate2".to_string()));
520        assert_eq!(dirs[2].level, FilterLevel::Debug);
521        assert!(filter.is_some() && filter.unwrap().to_string() == "abc");
522    }
523
524    #[test]
525    fn parse_logging_spec_invalid_crate_filter() {
526        let (dirs, filter) = parse_logging_spec("crate1::mod1=error=warn,crate2=debug/a.c");
527        assert_eq!(dirs.len(), 1);
528        assert_eq!(dirs[0].name, Some("crate2".to_string()));
529        assert_eq!(dirs[0].level, FilterLevel::Debug);
530        assert!(filter.is_some() && filter.unwrap().to_string() == "a.c");
531    }
532
533    #[test]
534    fn parse_logging_spec_empty_with_filter() {
535        let (dirs, filter) = parse_logging_spec("crate1/a*c");
536        assert_eq!(dirs.len(), 1);
537        assert_eq!(dirs[0].name, Some("crate1".to_string()));
538        assert_eq!(dirs[0].level, FilterLevel::max());
539        assert!(filter.is_some() && filter.unwrap().to_string() == "a*c");
540    }
541}