commit b18443e6eb27e522551d5e38192e80669d35f412 Author: Alex Crichton Date: Sat Dec 13 13:46:26 2014 -0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d97a34a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: rust +script: + - cargo build --verbose + - cargo test --verbose + - cargo doc +after_success: | + [ $TRAVIS_BRANCH = master ] && + [ $TRAVIS_PULL_REQUEST = false ] && + echo '' > target/doc/index.html && + sudo pip install ghp-import && + ghp-import -n target/doc && + git push -fq https://${TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages + +env: + global: + secure: bNUxFmA5uEdKKdVDtgb+5WQuMRDZ6QxK5152sIZWSimxyr/x5erNZ1uacPh02Ng92dbUQ66wz90WPmqTwoL0EeJ/FmZDd29hRHXe82MNpz47QX3cGMgFNldQ2GmJmCHBXKu373B80o/ZB+BTVcYcl1ksTXuIhQg2wZtXh8bZ9F4= diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..30961cf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] + +name = "log" +version = "0.1.0" +authors = ["The Rust Project Developers"] +license = "MIT/Apache-2.0" +readme = "README.md" +repository = "https://github.com/rust-lang/log" +documentation = "http://doc.rust-lang.org/log" +homepage = "https://github.com/rust-lang/log" +description = """ +Logging macros and infrastructure for Rust +""" + +[dependencies.regex] +git = "https://github.com/rust-lang/regex" +version = "0.1.0" diff --git a/src/directive.rs b/src/directive.rs new file mode 100644 index 0000000..d1db0ec --- /dev/null +++ b/src/directive.rs @@ -0,0 +1,199 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// 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; + +#[deriving(Show, Clone)] +pub struct LogDirective { + pub name: Option, + pub level: u32, +} + +pub static LOG_LEVEL_NAMES: [&'static str, ..4] = ["ERROR", "WARN", "INFO", + "DEBUG"]; + +/// Parse an individual log level that is either a number or a symbolic log level +fn parse_log_level(level: &str) -> Option { + from_str::(level).or_else(|| { + let pos = LOG_LEVEL_NAMES.iter().position(|&name| name.eq_ignore_ascii_case(level)); + pos.map(|p| p as u32 + 1) + }).map(|p| cmp::min(p, ::MAX_LOG_LEVEL)) +} + +/// 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, Option) { + let mut dirs = Vec::new(); + + 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()) { + (Some(part0), None, None) => { + // if the single argument is a log-level string or number, + // treat that as a global fallback + match parse_log_level(part0) { + Some(num) => (num, None), + None => (::MAX_LOG_LEVEL, Some(part0)), + } + } + (Some(part0), Some(""), None) => (::MAX_LOG_LEVEL, Some(part0)), + (Some(part0), Some(part1), None) => { + match parse_log_level(part1) { + Some(num) => (num, Some(part0)), + _ => { + println!("warning: invalid logging spec '{}', \ + ignoring it", part1); + continue + } + } + }, + _ => { + println!("warning: invalid logging spec '{}', \ + ignoring it", s); + continue + } + }; + dirs.push(LogDirective { + name: name.map(|s| s.to_string()), + level: log_level, + }); + }}); + + 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)] +mod tests { + use super::parse_logging_spec; + + #[test] + fn parse_logging_spec_valid() { + let (dirs, filter) = parse_logging_spec("crate1::mod1=1,crate1::mod2,crate2=4"); + 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_none()); + } + + #[test] + fn parse_logging_spec_invalid_crate() { + // test parse_logging_spec with multiple = in specification + let (dirs, filter) = parse_logging_spec("crate1::mod1=1=2,crate2=4"); + 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, filter) = parse_logging_spec("crate1::mod1=noNumber,crate2=4"); + 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, filter) = parse_logging_spec("crate1::mod1=wrong,crate2=warn"); + 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, filter) = parse_logging_spec("crate1::mod1=wrong,crate2="); + 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, filter) = parse_logging_spec("warn,crate2=4"); + 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"); + 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() == "abc"); + } + + #[test] + fn parse_logging_spec_invalid_crate_filter() { + let (dirs, filter) = parse_logging_spec("crate1::mod1=1=2,crate2=4/a.c"); + 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() == "a.c"); + } + + #[test] + fn parse_logging_spec_empty_with_filter() { + let (dirs, filter) = parse_logging_spec("crate1/a*c"); + 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() == "a*c"); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8b79078 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,510 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Utilities for program-wide and customizable logging +//! +//! ## Example +//! +//! ``` +//! #![feature(phase)] +//! #[phase(plugin, link)] extern crate log; +//! +//! fn main() { +//! debug!("this is a debug {}", "message"); +//! error!("this is printed by default"); +//! +//! if log_enabled!(log::INFO) { +//! let x = 3i * 4i; // expensive computation +//! info!("the answer was: {}", x); +//! } +//! } +//! ``` +//! +//! Assumes the binary is `main`: +//! +//! ```{.bash} +//! $ RUST_LOG=error ./main +//! ERROR:main: this is printed by default +//! ``` +//! +//! ```{.bash} +//! $ RUST_LOG=info ./main +//! ERROR:main: this is printed by default +//! INFO:main: the answer was: 12 +//! ``` +//! +//! ```{.bash} +//! $ RUST_LOG=debug ./main +//! DEBUG:main: this is a debug message +//! ERROR:main: this is printed by default +//! INFO:main: the answer was: 12 +//! ``` +//! +//! You can also set the log level on a per module basis: +//! +//! ```{.bash} +//! $ RUST_LOG=main=info ./main +//! ERROR:main: this is printed by default +//! INFO:main: the answer was: 12 +//! ``` +//! +//! And enable all logging: +//! +//! ```{.bash} +//! $ RUST_LOG=main ./main +//! DEBUG:main: this is a debug message +//! ERROR:main: this is printed by default +//! INFO:main: the answer was: 12 +//! ``` +//! +//! +//! ## Logging Macros +//! +//! There are five macros that the logging subsystem uses: +//! +//! * `log!(level, ...)` - the generic logging macro, takes a level as a u32 and any +//! related `format!` arguments +//! * `debug!(...)` - a macro hard-wired to the log level of `DEBUG` +//! * `info!(...)` - a macro hard-wired to the log level of `INFO` +//! * `warn!(...)` - a macro hard-wired to the log level of `WARN` +//! * `error!(...)` - a macro hard-wired to the log level of `ERROR` +//! +//! All of these macros use the same style of syntax as the `format!` syntax +//! extension. Details about the syntax can be found in the documentation of +//! `std::fmt` along with the Rust tutorial/manual. +//! +//! If you want to check at runtime if a given logging level is enabled (e.g. if the +//! information you would want to log is expensive to produce), you can use the +//! following macro: +//! +//! * `log_enabled!(level)` - returns true if logging of the given level is enabled +//! +//! ## Enabling logging +//! +//! Log levels are controlled on a per-module basis, and by default all logging is +//! disabled except for `error!` (a log level of 1). Logging is controlled via the +//! `RUST_LOG` environment variable. The value of this environment variable is a +//! comma-separated list of logging directives. A logging directive is of the form: +//! +//! ```text +//! path::to::module=log_level +//! ``` +//! +//! The path to the module is rooted in the name of the crate it was compiled for, +//! so if your program is contained in a file `hello.rs`, for example, to turn on +//! logging for this file you would use a value of `RUST_LOG=hello`. +//! Furthermore, this path is a prefix-search, so all modules nested in the +//! specified module will also have logging enabled. +//! +//! The actual `log_level` is optional to specify. If omitted, all logging will be +//! enabled. If specified, the it must be either a numeric in the range of 1-255, or +//! it must be one of the strings `debug`, `error`, `info`, or `warn`. If a numeric +//! is specified, then all logging less than or equal to that numeral is enabled. +//! For example, if logging level 3 is active, error, warn, and info logs will be +//! printed, but debug will be omitted. +//! +//! As the log level for a module is optional, the module to enable logging for is +//! also optional. If only a `log_level` is provided, then the global log level for +//! all modules is set to this value. +//! +//! Some examples of valid values of `RUST_LOG` are: +//! +//! * `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 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 +//! +//! Each of these macros will expand to code similar to: +//! +//! ```rust,ignore +//! if log_level <= my_module_log_level() { +//! ::log::log(log_level, format!(...)); +//! } +//! ``` +//! +//! What this means is that each of these macros are very cheap at runtime if +//! they're turned off (just a load and an integer comparison). This also means that +//! if logging is disabled, none of the components of the log will be executed. + +#![crate_name = "log"] +#![experimental] +#![crate_type = "rlib"] +#![crate_type = "dylib"] +#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "http://www.rust-lang.org/favicon.ico", + html_root_url = "http://doc.rust-lang.org/nightly/", + html_playground_url = "http://play.rust-lang.org/")] +#![feature(macro_rules)] +#![deny(missing_docs)] + +extern crate regex; + +use std::cell::RefCell; +use std::fmt; +use std::io::LineBufferedWriter; +use std::io; +use std::mem; +use std::os; +use std::rt; +use std::slice; +use std::sync::{Once, ONCE_INIT}; + +use regex::Regex; + +use directive::LOG_LEVEL_NAMES; + +pub mod macros; +mod directive; + +/// Maximum logging level of a module that can be specified. Common logging +/// levels are found in the DEBUG/INFO/WARN/ERROR constants. +pub const MAX_LOG_LEVEL: u32 = 255; + +/// The default logging level of a crate if no other is specified. +const DEFAULT_LOG_LEVEL: u32 = 1; + +/// An unsafe constant that is the maximum logging level of any module +/// specified. This is the first line of defense to determining whether a +/// logging statement should be run. +static mut LOG_LEVEL: u32 = MAX_LOG_LEVEL; + +static mut DIRECTIVES: *const Vec = + 0 as *const Vec; + +/// Optional regex filter. +static mut FILTER: *const Regex = 0 as *const _; + +/// Debug log level +pub const DEBUG: u32 = 4; +/// Info log level +pub const INFO: u32 = 3; +/// Warn log level +pub const WARN: u32 = 2; +/// Error log level +pub const ERROR: u32 = 1; + +thread_local!(static LOCAL_LOGGER: RefCell>> = { + RefCell::new(None) +}) + +/// A trait used to represent an interface to a task-local logger. Each task +/// can have its own custom logger which can respond to logging messages +/// however it likes. +pub trait Logger { + /// Logs a single message described by the `record`. + fn log(&mut self, record: &LogRecord); +} + +struct DefaultLogger { + handle: LineBufferedWriter, +} + +/// Wraps the log level with fmt implementations. +#[deriving(PartialEq, PartialOrd)] +pub struct LogLevel(pub u32); + +impl Copy for LogLevel {} + +impl fmt::Show for LogLevel { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let LogLevel(level) = *self; + match LOG_LEVEL_NAMES.get(level as uint - 1) { + Some(name) => name.fmt(fmt), + None => level.fmt(fmt) + } + } +} + +impl Logger for DefaultLogger { + fn log(&mut self, record: &LogRecord) { + match writeln!(&mut self.handle, + "{}:{}: {}", + record.level, + record.module_path, + record.args) { + Err(e) => panic!("failed to log: {}", e), + Ok(()) => {} + } + } +} + +impl Drop for DefaultLogger { + fn drop(&mut self) { + // FIXME(#12628): is panicking the right thing to do? + match self.handle.flush() { + Err(e) => panic!("failed to flush a logger: {}", e), + Ok(()) => {} + } + } +} + +/// This function is called directly by the compiler when using the logging +/// macros. This function does not take into account whether the log level +/// specified is active or not, it will always log something if this method is +/// called. +/// +/// It is not recommended to call this function directly, rather it should be +/// 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.as_ref() } { + 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. + let mut logger = LOCAL_LOGGER.with(|s| { + s.borrow_mut().take() + }).unwrap_or_else(|| { + box DefaultLogger { handle: io::stderr() } as Box + }); + logger.log(&LogRecord { + level: LogLevel(level), + args: args, + file: loc.file, + module_path: loc.module_path, + line: loc.line, + }); + set_logger(logger); +} + +/// Getter for the global log level. This is a function so that it can be called +/// safely +#[doc(hidden)] +#[inline(always)] +pub fn log_level() -> u32 { unsafe { LOG_LEVEL } } + +/// Replaces the task-local logger with the specified logger, returning the old +/// logger. +pub fn set_logger(logger: Box) -> Option> { + let mut l = Some(logger); + LOCAL_LOGGER.with(|slot| { + mem::replace(&mut *slot.borrow_mut(), l.take()) + }) +} + +/// A LogRecord is created by the logging macros, and passed as the only +/// argument to Loggers. +#[deriving(Show)] +pub struct LogRecord<'a> { + + /// The module path of where the LogRecord originated. + pub module_path: &'a str, + + /// The LogLevel of this record. + pub level: LogLevel, + + /// The arguments from the log line. + pub args: &'a fmt::Arguments<'a>, + + /// The file of where the LogRecord originated. + pub file: &'a str, + + /// The line number of where the LogRecord originated. + pub line: uint, +} + +#[doc(hidden)] +pub struct LogLocation { + pub module_path: &'static str, + pub file: &'static str, + pub line: uint, +} + +impl Copy for LogLocation {} + +/// Tests whether a given module's name is enabled for a particular level of +/// logging. This is the second layer of defense about determining whether a +/// module's log statement should be emitted or not. +#[doc(hidden)] +pub fn mod_enabled(level: u32, module: &str) -> bool { + static INIT: Once = ONCE_INIT; + INIT.doit(init); + + // It's possible for many threads are in this function, only one of them + // will perform the global initialization, but all of them will need to check + // again to whether they should really be here or not. Hence, despite this + // check being expanded manually in the logging macro, this function checks + // the log level again. + if level > unsafe { LOG_LEVEL } { return false } + + // This assertion should never get tripped unless we're in an at_exit + // handler after logging has been torn down and a logging attempt was made. + assert!(unsafe { !DIRECTIVES.is_null() }); + + enabled(level, module, unsafe { (*DIRECTIVES).iter() }) +} + +fn enabled(level: u32, + module: &str, + iter: slice::Items) + -> bool { + // Search for the longest match, the vector is assumed to be pre-sorted. + for directive in iter.rev() { + match directive.name { + Some(ref name) if !module.starts_with(name.as_slice()) => {}, + Some(..) | None => { + return level <= directive.level + } + } + } + level <= DEFAULT_LOG_LEVEL +} + +/// Initialize logging for the current process. +/// +/// This is not threadsafe at all, so initialization is performed through a +/// `Once` primitive (and this function is called from that primitive). +fn init() { + let (mut directives, filter) = match os::getenv("RUST_LOG") { + Some(spec) => directive::parse_logging_spec(spec.as_slice()), + None => (Vec::new(), None), + }; + + // Sort the provided directives by length of their name, this allows a + // little more efficient lookup at runtime. + directives.sort_by(|a, b| { + let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0); + let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0); + alen.cmp(&blen) + }); + + let max_level = { + let max = directives.iter().max_by(|d| d.level); + max.map(|d| d.level).unwrap_or(DEFAULT_LOG_LEVEL) + }; + + 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 the globals for when the runtime exits. + rt::at_exit(proc() { + assert!(!DIRECTIVES.is_null()); + let _directives: Box> = + mem::transmute(DIRECTIVES); + DIRECTIVES = 0 as *const Vec; + + if !FILTER.is_null() { + let _filter: Box = mem::transmute(FILTER); + FILTER = 0 as *const _; + } + }); + } +} + +#[cfg(test)] +mod tests { + use super::enabled; + use directive::LogDirective; + + #[test] + fn match_full_path() { + let dirs = [ + LogDirective { + name: Some("crate2".to_string()), + level: 3 + }, + LogDirective { + name: Some("crate1::mod1".to_string()), + level: 2 + } + ]; + assert!(enabled(2, "crate1::mod1", dirs.iter())); + assert!(!enabled(3, "crate1::mod1", dirs.iter())); + assert!(enabled(3, "crate2", dirs.iter())); + assert!(!enabled(4, "crate2", dirs.iter())); + } + + #[test] + fn no_match() { + let dirs = [ + LogDirective { name: Some("crate2".to_string()), level: 3 }, + LogDirective { name: Some("crate1::mod1".to_string()), level: 2 } + ]; + assert!(!enabled(2, "crate3", dirs.iter())); + } + + #[test] + fn match_beginning() { + let dirs = [ + LogDirective { name: Some("crate2".to_string()), level: 3 }, + LogDirective { name: Some("crate1::mod1".to_string()), level: 2 } + ]; + assert!(enabled(3, "crate2::mod1", dirs.iter())); + } + + #[test] + fn match_beginning_longest_match() { + let dirs = [ + LogDirective { name: Some("crate2".to_string()), level: 3 }, + LogDirective { name: Some("crate2::mod".to_string()), level: 4 }, + LogDirective { name: Some("crate1::mod1".to_string()), level: 2 } + ]; + assert!(enabled(4, "crate2::mod1", dirs.iter())); + assert!(!enabled(4, "crate2", dirs.iter())); + } + + #[test] + fn match_default() { + let dirs = [ + LogDirective { name: None, level: 3 }, + LogDirective { name: Some("crate1::mod1".to_string()), level: 2 } + ]; + assert!(enabled(2, "crate1::mod1", dirs.iter())); + assert!(enabled(3, "crate2::mod2", dirs.iter())); + } + + #[test] + fn zero_level() { + let dirs = [ + LogDirective { name: None, level: 3 }, + LogDirective { name: Some("crate1::mod1".to_string()), level: 0 } + ]; + assert!(!enabled(1, "crate1::mod1", dirs.iter())); + assert!(enabled(3, "crate2::mod2", dirs.iter())); + } +} diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..4f88370 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,207 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Logging macros + +#![macro_escape] + +/// The standard logging macro +/// +/// This macro will generically log over a provided level (of type u32) with a +/// format!-based argument list. See documentation in `std::fmt` for details on +/// how to use the syntax. +/// +/// # Example +/// +/// ``` +/// #![feature(phase)] +/// #[phase(plugin, link)] extern crate log; +/// +/// fn main() { +/// log!(log::WARN, "this is a warning {}", "message"); +/// log!(log::DEBUG, "this is a debug message"); +/// log!(6, "this is a custom logging level: {level}", level=6u); +/// } +/// ``` +/// +/// Assumes the binary is `main`: +/// +/// ```{.bash} +/// $ RUST_LOG=warn ./main +/// WARN:main: this is a warning message +/// ``` +/// +/// ```{.bash} +/// $ RUST_LOG=debug ./main +/// DEBUG:main: this is a debug message +/// WARN:main: this is a warning message +/// ``` +/// +/// ```{.bash} +/// $ RUST_LOG=6 ./main +/// DEBUG:main: this is a debug message +/// WARN:main: this is a warning message +/// 6:main: this is a custom logging level: 6 +/// ``` +#[macro_export] +macro_rules! log( + ($lvl:expr, $($arg:tt)+) => ({ + static LOC: ::log::LogLocation = ::log::LogLocation { + line: line!(), + file: file!(), + module_path: module_path!(), + }; + let lvl = $lvl; + if log_enabled!(lvl) { + format_args!(|args| { ::log::log(lvl, &LOC, args) }, $($arg)+) + } + }) +) + +/// A convenience macro for logging at the error log level. +/// +/// # Example +/// +/// ``` +/// #![feature(phase)] +/// #[phase(plugin, link)] extern crate log; +/// +/// fn main() { +/// let error = 3u; +/// error!("the build has failed with error code: {}", error); +/// } +/// ``` +/// +/// Assumes the binary is `main`: +/// +/// ```{.bash} +/// $ RUST_LOG=error ./main +/// ERROR:main: the build has failed with error code: 3 +/// ``` +/// +#[macro_export] +macro_rules! error( + ($($arg:tt)*) => (log!(::log::ERROR, $($arg)*)) +) + +/// A convenience macro for logging at the warning log level. +/// +/// # Example +/// +/// ``` +/// #![feature(phase)] +/// #[phase(plugin, link)] extern crate log; +/// +/// fn main() { +/// let code = 3u; +/// warn!("you may like to know that a process exited with: {}", code); +/// } +/// ``` +/// +/// Assumes the binary is `main`: +/// +/// ```{.bash} +/// $ RUST_LOG=warn ./main +/// WARN:main: you may like to know that a process exited with: 3 +/// ``` +#[macro_export] +macro_rules! warn( + ($($arg:tt)*) => (log!(::log::WARN, $($arg)*)) +) + +/// A convenience macro for logging at the info log level. +/// +/// # Example +/// +/// ``` +/// #![feature(phase)] +/// #[phase(plugin, link)] extern crate log; +/// +/// fn main() { +/// let ret = 3i; +/// info!("this function is about to return: {}", ret); +/// } +/// ``` +/// +/// Assumes the binary is `main`: +/// +/// ```{.bash} +/// $ RUST_LOG=info ./main +/// INFO:main: this function is about to return: 3 +/// ``` +#[macro_export] +macro_rules! info( + ($($arg:tt)*) => (log!(::log::INFO, $($arg)*)) +) + +/// A convenience macro for logging at the debug log level. This macro can also +/// be omitted at compile time by passing `--cfg ndebug` to the compiler. If +/// this option is not passed, then debug statements will be compiled. +/// +/// # Example +/// +/// ``` +/// #![feature(phase)] +/// #[phase(plugin, link)] extern crate log; +/// +/// fn main() { +/// debug!("x = {x}, y = {y}", x=10i, y=20i); +/// } +/// ``` +/// +/// Assumes the binary is `main`: +/// +/// ```{.bash} +/// $ RUST_LOG=debug ./main +/// DEBUG:main: x = 10, y = 20 +/// ``` +#[macro_export] +macro_rules! debug( + ($($arg:tt)*) => (if cfg!(not(ndebug)) { log!(::log::DEBUG, $($arg)*) }) +) + +/// A macro to test whether a log level is enabled for the current module. +/// +/// # Example +/// +/// ``` +/// #![feature(phase)] +/// #[phase(plugin, link)] extern crate log; +/// +/// struct Point { x: int, y: int } +/// fn some_expensive_computation() -> Point { Point { x: 1, y: 2 } } +/// +/// fn main() { +/// if log_enabled!(log::DEBUG) { +/// let x = some_expensive_computation(); +/// debug!("x.x = {}, x.y = {}", x.x, x.y); +/// } +/// } +/// ``` +/// +/// Assumes the binary is `main`: +/// +/// ```{.bash} +/// $ RUST_LOG=error ./main +/// ``` +/// +/// ```{.bash} +/// $ RUST_LOG=debug ./main +/// DEBUG:main: x.x = 1, x.y = 2 +/// ``` +#[macro_export] +macro_rules! log_enabled( + ($lvl:expr) => ({ + let lvl = $lvl; + (lvl != ::log::DEBUG || cfg!(not(ndebug))) && + lvl <= ::log::log_level() && + ::log::mod_enabled(lvl, module_path!()) + }) +)