Skip to content

Allow a regex filter for RUST_LOG #16553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 28, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mk/crates.mk
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ DEPS_test := std getopts serialize rbml term time regex native:rust_test_helpers
DEPS_time := std serialize
DEPS_rand := core
DEPS_url := std
DEPS_log := std
DEPS_log := std regex
DEPS_regex := std
DEPS_regex_macros = rustc syntax std regex
DEPS_fmt_macros = std
Expand Down
85 changes: 74 additions & 11 deletions src/liblog/directive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use regex::Regex;
use std::ascii::AsciiExt;
use std::cmp;

Expand All @@ -28,14 +29,23 @@ fn parse_log_level(level: &str) -> Option<u32> {
}).map(|p| cmp::min(p, ::MAX_LOG_LEVEL))
}

/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=1")
/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=1/foo")
/// and return a vector with log directives.
///
/// Valid log levels are 0-255, with the most likely ones being 1-4 (defined in
/// std::). Also supports string log levels of error, warn, info, and debug
pub fn parse_logging_spec(spec: &str) -> Vec<LogDirective> {
pub fn parse_logging_spec(spec: &str) -> (Vec<LogDirective>, Option<Regex>) {
let mut dirs = Vec::new();
for s in spec.split(',') {

let mut parts = spec.split('/');
let mods = parts.next();
let filter = parts.next();
if parts.next().is_some() {
println!("warning: invalid logging spec '{}', \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two println!s should go to stderr.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following convention in the file. Do you prefer to change all uses to stderr?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huonw ping on this question

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it probably should; but I'm happy with just leaving as is and filing a bug.

ignoring it (too many '/'s)", spec);
return (dirs, None);
}
mods.map(|m| { for s in m.split(',') {
if s.len() == 0 { continue }
let mut parts = s.split('=');
let (log_level, name) = match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
Expand Down Expand Up @@ -68,8 +78,19 @@ pub fn parse_logging_spec(spec: &str) -> Vec<LogDirective> {
name: name.map(|s| s.to_string()),
level: log_level,
});
}
return dirs;
}});

let filter = filter.map_or(None, |filter| {
match Regex::new(filter) {
Ok(re) => Some(re),
Err(e) => {
println!("warning: invalid regex filter - {}", e);
None
}
}
});

return (dirs, filter);
}

#[cfg(test)]
Expand All @@ -78,7 +99,7 @@ mod tests {

#[test]
fn parse_logging_spec_valid() {
let dirs = parse_logging_spec("crate1::mod1=1,crate1::mod2,crate2=4");
let (dirs, filter) = parse_logging_spec("crate1::mod1=1,crate1::mod2,crate2=4");
let dirs = dirs.as_slice();
assert_eq!(dirs.len(), 3);
assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
Expand All @@ -89,57 +110,99 @@ mod tests {

assert_eq!(dirs[2].name, Some("crate2".to_string()));
assert_eq!(dirs[2].level, 4);
assert!(filter.is_none());
}

#[test]
fn parse_logging_spec_invalid_crate() {
// test parse_logging_spec with multiple = in specification
let dirs = parse_logging_spec("crate1::mod1=1=2,crate2=4");
let (dirs, filter) = parse_logging_spec("crate1::mod1=1=2,crate2=4");
let dirs = dirs.as_slice();
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_string()));
assert_eq!(dirs[0].level, 4);
assert!(filter.is_none());
}

#[test]
fn parse_logging_spec_invalid_log_level() {
// test parse_logging_spec with 'noNumber' as log level
let dirs = parse_logging_spec("crate1::mod1=noNumber,crate2=4");
let (dirs, filter) = parse_logging_spec("crate1::mod1=noNumber,crate2=4");
let dirs = dirs.as_slice();
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_string()));
assert_eq!(dirs[0].level, 4);
assert!(filter.is_none());
}

#[test]
fn parse_logging_spec_string_log_level() {
// test parse_logging_spec with 'warn' as log level
let dirs = parse_logging_spec("crate1::mod1=wrong,crate2=warn");
let (dirs, filter) = parse_logging_spec("crate1::mod1=wrong,crate2=warn");
let dirs = dirs.as_slice();
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_string()));
assert_eq!(dirs[0].level, ::WARN);
assert!(filter.is_none());
}

#[test]
fn parse_logging_spec_empty_log_level() {
// test parse_logging_spec with '' as log level
let dirs = parse_logging_spec("crate1::mod1=wrong,crate2=");
let (dirs, filter) = parse_logging_spec("crate1::mod1=wrong,crate2=");
let dirs = dirs.as_slice();
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_string()));
assert_eq!(dirs[0].level, ::MAX_LOG_LEVEL);
assert!(filter.is_none());
}

#[test]
fn parse_logging_spec_global() {
// test parse_logging_spec with no crate
let dirs = parse_logging_spec("warn,crate2=4");
let (dirs, filter) = parse_logging_spec("warn,crate2=4");
let dirs = dirs.as_slice();
assert_eq!(dirs.len(), 2);
assert_eq!(dirs[0].name, None);
assert_eq!(dirs[0].level, 2);
assert_eq!(dirs[1].name, Some("crate2".to_string()));
assert_eq!(dirs[1].level, 4);
assert!(filter.is_none());
}

#[test]
fn parse_logging_spec_valid_filter() {
let (dirs, filter) = parse_logging_spec("crate1::mod1=1,crate1::mod2,crate2=4/abc");
let dirs = dirs.as_slice();
assert_eq!(dirs.len(), 3);
assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
assert_eq!(dirs[0].level, 1);

assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
assert_eq!(dirs[1].level, ::MAX_LOG_LEVEL);

assert_eq!(dirs[2].name, Some("crate2".to_string()));
assert_eq!(dirs[2].level, 4);
assert!(filter.is_some() && filter.unwrap().to_string().as_slice() == "abc");
}

#[test]
fn parse_logging_spec_invalid_crate_filter() {
let (dirs, filter) = parse_logging_spec("crate1::mod1=1=2,crate2=4/a.c");
let dirs = dirs.as_slice();
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_string()));
assert_eq!(dirs[0].level, 4);
assert!(filter.is_some() && filter.unwrap().to_string().as_slice() == "a.c");
}

#[test]
fn parse_logging_spec_empty_with_filter() {
let (dirs, filter) = parse_logging_spec("crate1/a*c");
let dirs = dirs.as_slice();
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate1".to_string()));
assert_eq!(dirs[0].level, ::MAX_LOG_LEVEL);
assert!(filter.is_some() && filter.unwrap().to_string().as_slice() == "a*c");
}
}
64 changes: 53 additions & 11 deletions src/liblog/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,32 @@ all modules is set to this value.

Some examples of valid values of `RUST_LOG` are:

```text
hello // turns on all logging for the 'hello' module
info // turns on all info logging
hello=debug // turns on debug logging for 'hello'
hello=3 // turns on info logging for 'hello'
hello,std::option // turns on hello, and std's option logging
error,hello=warn // turn on global error logging and also warn for hello
```
* `hello` turns on all logging for the 'hello' module
* `info` turns on all info logging
* `hello=debug` turns on debug logging for 'hello'
* `hello=3` turns on info logging for 'hello'
* `hello,std::option` turns on hello, and std's option logging
* `error,hello=warn` turn on global error logging and also warn for hello

## Filtering results

A RUST_LOG directive may include a regex filter. The syntax is to append `/`
followed by a regex. Each message is checked against the regex, and is only
logged if it matches. Note that the matching is done after formatting the log
string but before adding any logging meta-data. There is a single filter for all
modules.

Some examples:

* `hello/foo` turns on all logging for the 'hello' module where the log message
includes 'foo'.
* `info/f.o` turns on all info logging where the log message includes 'foo',
'f1o', 'fao', etc.
* `hello=debug/foo*foo` turns on debug logging for 'hello' where the the log
message includes 'foofoo' or 'fofoo' or 'fooooooofoo', etc.
* `error,hello=warn/[0-9] scopes` turn on global error logging and also warn for
hello. In both cases the log message must include a single digit number
followed by 'scopes'

## Performance and Side Effects

Expand Down Expand Up @@ -117,6 +135,9 @@ if logging is disabled, none of the components of the log will be executed.
#![feature(macro_rules)]
#![deny(missing_doc)]

extern crate regex;

use regex::Regex;
use std::fmt;
use std::io::LineBufferedWriter;
use std::io;
Expand Down Expand Up @@ -146,6 +167,9 @@ static mut LOG_LEVEL: u32 = MAX_LOG_LEVEL;
static mut DIRECTIVES: *const Vec<directive::LogDirective> =
0 as *const Vec<directive::LogDirective>;

/// Optional regex filter.
static mut FILTER: *const Regex = 0 as *const _;

/// Debug log level
pub static DEBUG: u32 = 4;
/// Info log level
Expand Down Expand Up @@ -222,6 +246,13 @@ impl Drop for DefaultLogger {
/// invoked through the logging family of macros.
#[doc(hidden)]
pub fn log(level: u32, loc: &'static LogLocation, args: &fmt::Arguments) {
// Test the literal string from args against the current filter, if there
// is one.
match unsafe { FILTER.to_option() } {
Some(filter) if filter.is_match(args.to_string().as_slice()) => return,
_ => {}
}

// Completely remove the local logger from TLS in case anyone attempts to
// frob the slot while we're doing the logging. This will destroy any logger
// set during logging.
Expand Down Expand Up @@ -321,9 +352,9 @@ fn enabled(level: u32,
/// This is not threadsafe at all, so initialization os performed through a
/// `Once` primitive (and this function is called from that primitive).
fn init() {
let mut directives = match os::getenv("RUST_LOG") {
let (mut directives, filter) = match os::getenv("RUST_LOG") {
Some(spec) => directive::parse_logging_spec(spec.as_slice()),
None => Vec::new(),
None => (Vec::new(), None),
};

// Sort the provided directives by length of their name, this allows a
Expand All @@ -342,15 +373,26 @@ fn init() {
unsafe {
LOG_LEVEL = max_level;

assert!(FILTER.is_null());
match filter {
Some(f) => FILTER = mem::transmute(box f),
None => {}
}

assert!(DIRECTIVES.is_null());
DIRECTIVES = mem::transmute(box directives);

// Schedule the cleanup for this global for when the runtime exits.
// Schedule the cleanup for the globals for when the runtime exits.
rt::at_exit(proc() {
assert!(!DIRECTIVES.is_null());
let _directives: Box<Vec<directive::LogDirective>> =
mem::transmute(DIRECTIVES);
DIRECTIVES = 0 as *const Vec<directive::LogDirective>;

if !FILTER.is_null() {
let _filter: Box<Regex> = mem::transmute(FILTER);
FILTER = 0 as *const _;
}
});
}
}
Expand Down