diff --git a/src/liblog/directive.rs b/src/liblog/directive.rs index 4329f68b9aaee..afbcf8c6ded5a 100644 --- a/src/liblog/directive.rs +++ b/src/liblog/directive.rs @@ -8,6 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use std::ascii::StrAsciiExt; use std::cmp; #[deriving(Show, Clone)] @@ -16,13 +17,13 @@ pub struct LogDirective { pub level: u32, } -static LOG_LEVEL_NAMES: [&'static str, ..4] = ["error", "warn", "info", - "debug"]; +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 == level); + 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)) } diff --git a/src/liblog/lib.rs b/src/liblog/lib.rs index 87c82e1dd7d42..d0426b1e40733 100644 --- a/src/liblog/lib.rs +++ b/src/liblog/lib.rs @@ -129,6 +129,8 @@ use std::slice; use sync::one::{Once, ONCE_INIT}; +use directive::LOG_LEVEL_NAMES; + pub mod macros; mod directive; @@ -162,19 +164,42 @@ local_data_key!(local_logger: ~Logger:Send) /// 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 `args` structure. The level is - /// provided in case you want to do things like color the message, etc. - fn log(&mut self, level: u32, args: &fmt::Arguments); + /// 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(Eq, Ord)] +pub struct LogLevel(pub u32); + +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 fmt::Signed for LogLevel { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let LogLevel(level) = *self; + write!(fmt.buf, "{}", level) + } +} + impl Logger for DefaultLogger { - // by default, just ignore the level - fn log(&mut self, _level: u32, args: &fmt::Arguments) { - match fmt::writeln(&mut self.handle, args) { + fn log(&mut self, record: &LogRecord) { + match write!(&mut self.handle, + "{}:{}: {}", + record.level, + record.module_path, + record.args) { Err(e) => fail!("failed to log: {}", e), Ok(()) => {} } @@ -198,14 +223,21 @@ impl Drop for DefaultLogger { /// /// It is not recommended to call this function directly, rather it should be /// invoked through the logging family of macros. -pub fn log(level: u32, args: &fmt::Arguments) { +#[doc(hidden)] +pub fn log(level: u32, loc: &'static LogLocation, args: &fmt::Arguments) { // 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_data::pop(local_logger).unwrap_or_else(|| { ~DefaultLogger { handle: io::stderr() } as ~Logger:Send }); - logger.log(level, args); + logger.log(&LogRecord { + level: LogLevel(level), + args: args, + file: loc.file, + module_path: loc.module_path, + line: loc.line, + }); local_data::set(local_logger, logger); } @@ -223,6 +255,34 @@ pub fn set_logger(logger: ~Logger:Send) -> Option<~Logger:Send> { return prev; } +/// 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, +} + /// 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. diff --git a/src/liblog/macros.rs b/src/liblog/macros.rs index f1d4a318bf080..3bb49c237f146 100644 --- a/src/liblog/macros.rs +++ b/src/liblog/macros.rs @@ -33,9 +33,14 @@ #[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, args) }, $($arg)+) + format_args!(|args| { ::log::log(lvl, &LOC, args) }, $($arg)+) } }) ) diff --git a/src/libstd/fmt/mod.rs b/src/libstd/fmt/mod.rs index 8e4d8707cca7b..e225dc42e0570 100644 --- a/src/libstd/fmt/mod.rs +++ b/src/libstd/fmt/mod.rs @@ -565,6 +565,12 @@ pub struct Arguments<'a> { args: &'a [Argument<'a>], } +impl<'a> Show for Arguments<'a> { + fn fmt(&self, fmt: &mut Formatter) -> Result { + write(fmt.buf, self) + } +} + /// When a format is not otherwise specified, types are formatted by ascribing /// to this trait. There is not an explicit way of selecting this trait to be /// used for formatting, it is only if no other format is specified. diff --git a/src/test/run-pass/capturing-logging.rs b/src/test/run-pass/capturing-logging.rs index a6443dc5a1a9a..3859ed3a53d3e 100644 --- a/src/test/run-pass/capturing-logging.rs +++ b/src/test/run-pass/capturing-logging.rs @@ -19,14 +19,14 @@ extern crate native; use std::fmt; use std::io::{ChanReader, ChanWriter}; -use log::{set_logger, Logger}; +use log::{set_logger, Logger, LogRecord}; struct MyWriter(ChanWriter); impl Logger for MyWriter { - fn log(&mut self, _level: u32, args: &fmt::Arguments) { + fn log(&mut self, record: &LogRecord) { let MyWriter(ref mut inner) = *self; - fmt::writeln(inner as &mut Writer, args); + fmt::writeln(inner as &mut Writer, record.args); } } diff --git a/src/test/run-pass/ifmt.rs b/src/test/run-pass/ifmt.rs index d20c4ec83731c..16b21f5f53729 100644 --- a/src/test/run-pass/ifmt.rs +++ b/src/test/run-pass/ifmt.rs @@ -33,7 +33,7 @@ impl fmt::Signed for B { } } -macro_rules! t(($a:expr, $b:expr) => { assert_eq!($a, $b.to_owned()) }) +macro_rules! t(($a:expr, $b:expr) => { assert_eq!($a.as_slice(), $b) }) pub fn main() { // Make sure there's a poly formatter that takes anything @@ -202,6 +202,10 @@ fn test_format_args() { let s = format_args!(fmt::format, "hello {}", "world"); t!(s, "hello world"); + let s = format_args!(|args| { + format!("{}: {}", "args were", args) + }, "hello {}", "world"); + t!(s, "args were: hello world"); } fn test_order() {