Skip to content

Commit ba39b50

Browse files
committed
auto merge of #16553 : nick29581/rust/log, r=huon
When specifying RUST_LOG, the programmer may append `/regex` to the end of the spec. All results will then be filtered using that regex. r?
2 parents b516532 + cc9b2b0 commit ba39b50

File tree

3 files changed

+128
-23
lines changed

3 files changed

+128
-23
lines changed

mk/crates.mk

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ DEPS_test := std getopts serialize rbml term time regex native:rust_test_helpers
9696
DEPS_time := std serialize
9797
DEPS_rand := core
9898
DEPS_url := std
99-
DEPS_log := std
99+
DEPS_log := std regex
100100
DEPS_regex := std
101101
DEPS_regex_macros = rustc syntax std regex
102102
DEPS_fmt_macros = std

src/liblog/directive.rs

+74-11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
use regex::Regex;
1112
use std::ascii::AsciiExt;
1213
use std::cmp;
1314

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

31-
/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=1")
32+
/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=1/foo")
3233
/// and return a vector with log directives.
3334
///
3435
/// Valid log levels are 0-255, with the most likely ones being 1-4 (defined in
3536
/// std::). Also supports string log levels of error, warn, info, and debug
36-
pub fn parse_logging_spec(spec: &str) -> Vec<LogDirective> {
37+
pub fn parse_logging_spec(spec: &str) -> (Vec<LogDirective>, Option<Regex>) {
3738
let mut dirs = Vec::new();
38-
for s in spec.split(',') {
39+
40+
let mut parts = spec.split('/');
41+
let mods = parts.next();
42+
let filter = parts.next();
43+
if parts.next().is_some() {
44+
println!("warning: invalid logging spec '{}', \
45+
ignoring it (too many '/'s)", spec);
46+
return (dirs, None);
47+
}
48+
mods.map(|m| { for s in m.split(',') {
3949
if s.len() == 0 { continue }
4050
let mut parts = s.split('=');
4151
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> {
6878
name: name.map(|s| s.to_string()),
6979
level: log_level,
7080
});
71-
}
72-
return dirs;
81+
}});
82+
83+
let filter = filter.map_or(None, |filter| {
84+
match Regex::new(filter) {
85+
Ok(re) => Some(re),
86+
Err(e) => {
87+
println!("warning: invalid regex filter - {}", e);
88+
None
89+
}
90+
}
91+
});
92+
93+
return (dirs, filter);
7394
}
7495

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

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

90111
assert_eq!(dirs[2].name, Some("crate2".to_string()));
91112
assert_eq!(dirs[2].level, 4);
113+
assert!(filter.is_none());
92114
}
93115

94116
#[test]
95117
fn parse_logging_spec_invalid_crate() {
96118
// test parse_logging_spec with multiple = in specification
97-
let dirs = parse_logging_spec("crate1::mod1=1=2,crate2=4");
119+
let (dirs, filter) = parse_logging_spec("crate1::mod1=1=2,crate2=4");
98120
let dirs = dirs.as_slice();
99121
assert_eq!(dirs.len(), 1);
100122
assert_eq!(dirs[0].name, Some("crate2".to_string()));
101123
assert_eq!(dirs[0].level, 4);
124+
assert!(filter.is_none());
102125
}
103126

104127
#[test]
105128
fn parse_logging_spec_invalid_log_level() {
106129
// test parse_logging_spec with 'noNumber' as log level
107-
let dirs = parse_logging_spec("crate1::mod1=noNumber,crate2=4");
130+
let (dirs, filter) = parse_logging_spec("crate1::mod1=noNumber,crate2=4");
108131
let dirs = dirs.as_slice();
109132
assert_eq!(dirs.len(), 1);
110133
assert_eq!(dirs[0].name, Some("crate2".to_string()));
111134
assert_eq!(dirs[0].level, 4);
135+
assert!(filter.is_none());
112136
}
113137

114138
#[test]
115139
fn parse_logging_spec_string_log_level() {
116140
// test parse_logging_spec with 'warn' as log level
117-
let dirs = parse_logging_spec("crate1::mod1=wrong,crate2=warn");
141+
let (dirs, filter) = parse_logging_spec("crate1::mod1=wrong,crate2=warn");
118142
let dirs = dirs.as_slice();
119143
assert_eq!(dirs.len(), 1);
120144
assert_eq!(dirs[0].name, Some("crate2".to_string()));
121145
assert_eq!(dirs[0].level, ::WARN);
146+
assert!(filter.is_none());
122147
}
123148

124149
#[test]
125150
fn parse_logging_spec_empty_log_level() {
126151
// test parse_logging_spec with '' as log level
127-
let dirs = parse_logging_spec("crate1::mod1=wrong,crate2=");
152+
let (dirs, filter) = parse_logging_spec("crate1::mod1=wrong,crate2=");
128153
let dirs = dirs.as_slice();
129154
assert_eq!(dirs.len(), 1);
130155
assert_eq!(dirs[0].name, Some("crate2".to_string()));
131156
assert_eq!(dirs[0].level, ::MAX_LOG_LEVEL);
157+
assert!(filter.is_none());
132158
}
133159

134160
#[test]
135161
fn parse_logging_spec_global() {
136162
// test parse_logging_spec with no crate
137-
let dirs = parse_logging_spec("warn,crate2=4");
163+
let (dirs, filter) = parse_logging_spec("warn,crate2=4");
138164
let dirs = dirs.as_slice();
139165
assert_eq!(dirs.len(), 2);
140166
assert_eq!(dirs[0].name, None);
141167
assert_eq!(dirs[0].level, 2);
142168
assert_eq!(dirs[1].name, Some("crate2".to_string()));
143169
assert_eq!(dirs[1].level, 4);
170+
assert!(filter.is_none());
171+
}
172+
173+
#[test]
174+
fn parse_logging_spec_valid_filter() {
175+
let (dirs, filter) = parse_logging_spec("crate1::mod1=1,crate1::mod2,crate2=4/abc");
176+
let dirs = dirs.as_slice();
177+
assert_eq!(dirs.len(), 3);
178+
assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
179+
assert_eq!(dirs[0].level, 1);
180+
181+
assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
182+
assert_eq!(dirs[1].level, ::MAX_LOG_LEVEL);
183+
184+
assert_eq!(dirs[2].name, Some("crate2".to_string()));
185+
assert_eq!(dirs[2].level, 4);
186+
assert!(filter.is_some() && filter.unwrap().to_string().as_slice() == "abc");
187+
}
188+
189+
#[test]
190+
fn parse_logging_spec_invalid_crate_filter() {
191+
let (dirs, filter) = parse_logging_spec("crate1::mod1=1=2,crate2=4/a.c");
192+
let dirs = dirs.as_slice();
193+
assert_eq!(dirs.len(), 1);
194+
assert_eq!(dirs[0].name, Some("crate2".to_string()));
195+
assert_eq!(dirs[0].level, 4);
196+
assert!(filter.is_some() && filter.unwrap().to_string().as_slice() == "a.c");
197+
}
198+
199+
#[test]
200+
fn parse_logging_spec_empty_with_filter() {
201+
let (dirs, filter) = parse_logging_spec("crate1/a*c");
202+
let dirs = dirs.as_slice();
203+
assert_eq!(dirs.len(), 1);
204+
assert_eq!(dirs[0].name, Some("crate1".to_string()));
205+
assert_eq!(dirs[0].level, ::MAX_LOG_LEVEL);
206+
assert!(filter.is_some() && filter.unwrap().to_string().as_slice() == "a*c");
144207
}
145208
}

src/liblog/lib.rs

+53-11
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,32 @@ all modules is set to this value.
8080
8181
Some examples of valid values of `RUST_LOG` are:
8282
83-
```text
84-
hello // turns on all logging for the 'hello' module
85-
info // turns on all info logging
86-
hello=debug // turns on debug logging for 'hello'
87-
hello=3 // turns on info logging for 'hello'
88-
hello,std::option // turns on hello, and std's option logging
89-
error,hello=warn // turn on global error logging and also warn for hello
90-
```
83+
* `hello` turns on all logging for the 'hello' module
84+
* `info` turns on all info logging
85+
* `hello=debug` turns on debug logging for 'hello'
86+
* `hello=3` turns on info logging for 'hello'
87+
* `hello,std::option` turns on hello, and std's option logging
88+
* `error,hello=warn` turn on global error logging and also warn for hello
89+
90+
## Filtering results
91+
92+
A RUST_LOG directive may include a regex filter. The syntax is to append `/`
93+
followed by a regex. Each message is checked against the regex, and is only
94+
logged if it matches. Note that the matching is done after formatting the log
95+
string but before adding any logging meta-data. There is a single filter for all
96+
modules.
97+
98+
Some examples:
99+
100+
* `hello/foo` turns on all logging for the 'hello' module where the log message
101+
includes 'foo'.
102+
* `info/f.o` turns on all info logging where the log message includes 'foo',
103+
'f1o', 'fao', etc.
104+
* `hello=debug/foo*foo` turns on debug logging for 'hello' where the the log
105+
message includes 'foofoo' or 'fofoo' or 'fooooooofoo', etc.
106+
* `error,hello=warn/[0-9] scopes` turn on global error logging and also warn for
107+
hello. In both cases the log message must include a single digit number
108+
followed by 'scopes'
91109
92110
## Performance and Side Effects
93111
@@ -117,6 +135,9 @@ if logging is disabled, none of the components of the log will be executed.
117135
#![feature(macro_rules)]
118136
#![deny(missing_doc)]
119137

138+
extern crate regex;
139+
140+
use regex::Regex;
120141
use std::fmt;
121142
use std::io::LineBufferedWriter;
122143
use std::io;
@@ -146,6 +167,9 @@ static mut LOG_LEVEL: u32 = MAX_LOG_LEVEL;
146167
static mut DIRECTIVES: *const Vec<directive::LogDirective> =
147168
0 as *const Vec<directive::LogDirective>;
148169

170+
/// Optional regex filter.
171+
static mut FILTER: *const Regex = 0 as *const _;
172+
149173
/// Debug log level
150174
pub static DEBUG: u32 = 4;
151175
/// Info log level
@@ -222,6 +246,13 @@ impl Drop for DefaultLogger {
222246
/// invoked through the logging family of macros.
223247
#[doc(hidden)]
224248
pub fn log(level: u32, loc: &'static LogLocation, args: &fmt::Arguments) {
249+
// Test the literal string from args against the current filter, if there
250+
// is one.
251+
match unsafe { FILTER.to_option() } {
252+
Some(filter) if filter.is_match(args.to_string().as_slice()) => return,
253+
_ => {}
254+
}
255+
225256
// Completely remove the local logger from TLS in case anyone attempts to
226257
// frob the slot while we're doing the logging. This will destroy any logger
227258
// set during logging.
@@ -321,9 +352,9 @@ fn enabled(level: u32,
321352
/// This is not threadsafe at all, so initialization os performed through a
322353
/// `Once` primitive (and this function is called from that primitive).
323354
fn init() {
324-
let mut directives = match os::getenv("RUST_LOG") {
355+
let (mut directives, filter) = match os::getenv("RUST_LOG") {
325356
Some(spec) => directive::parse_logging_spec(spec.as_slice()),
326-
None => Vec::new(),
357+
None => (Vec::new(), None),
327358
};
328359

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

376+
assert!(FILTER.is_null());
377+
match filter {
378+
Some(f) => FILTER = mem::transmute(box f),
379+
None => {}
380+
}
381+
345382
assert!(DIRECTIVES.is_null());
346383
DIRECTIVES = mem::transmute(box directives);
347384

348-
// Schedule the cleanup for this global for when the runtime exits.
385+
// Schedule the cleanup for the globals for when the runtime exits.
349386
rt::at_exit(proc() {
350387
assert!(!DIRECTIVES.is_null());
351388
let _directives: Box<Vec<directive::LogDirective>> =
352389
mem::transmute(DIRECTIVES);
353390
DIRECTIVES = 0 as *const Vec<directive::LogDirective>;
391+
392+
if !FILTER.is_null() {
393+
let _filter: Box<Regex> = mem::transmute(FILTER);
394+
FILTER = 0 as *const _;
395+
}
354396
});
355397
}
356398
}

0 commit comments

Comments
 (0)