]> git.lizzy.rs Git - rust.git/commitdiff
Allow a regex filter for RUST_LOG
authorNick Cameron <ncameron@mozilla.com>
Wed, 13 Aug 2014 09:38:52 +0000 (10:38 +0100)
committerNick Cameron <ncameron@mozilla.com>
Wed, 27 Aug 2014 22:14:57 +0000 (10:14 +1200)
When specifying RUST_LOG, the programmer may append `/regex` to the end of the spec. All results will then be filtered using that regex.

mk/crates.mk
src/liblog/directive.rs
src/liblog/lib.rs

index 5bc4505fb05c742bf92f22bf21d65320820112d2..f830e1a9d958519f1de323e4096dff5fe6fb2efb 100644 (file)
@@ -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
index d692c99e8c261cedb8eecdc12689f8f6e4d5b1e9..a93cf4c15682fd83ef0bfc8cb4ace3c0b8bde85a 100644 (file)
@@ -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;
 
@@ -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 '{}', \
+                 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()) {
@@ -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)]
@@ -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()));
@@ -89,57 +110,99 @@ fn parse_logging_spec_valid() {
 
         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");
     }
 }
index 554f27b881b7269700937bdac03eb50f7fae7b69..0bad742933b1c2739d20fb175011a1f8090d5b85 100644 (file)
@@ -80,14 +80,32 @@ fn main() {
 
 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
 
@@ -117,6 +135,9 @@ fn main() {
 #![feature(macro_rules)]
 #![deny(missing_doc)]
 
+extern crate regex;
+
+use regex::Regex;
 use std::fmt;
 use std::io::LineBufferedWriter;
 use std::io;
@@ -146,6 +167,9 @@ fn main() {
 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
@@ -222,6 +246,13 @@ fn drop(&mut self) {
 /// 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.
@@ -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
@@ -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 _;
+            }
         });
     }
 }