]> git.lizzy.rs Git - rust.git/commitdiff
log: Logger receiveis a LogRecord
authorSean McArthur <sean.monstar@gmail.com>
Sat, 3 May 2014 02:32:47 +0000 (19:32 -0700)
committerSean McArthur <sean.monstar@gmail.com>
Mon, 5 May 2014 18:18:53 +0000 (11:18 -0700)
The logging macros now create a LogRecord, and pass that to the
Logger, instead of passing a `level` and `args`. The new signature is:

    trait Logger {
        fn log(&mut self, record: &LogRecord);
    }

The LogRecord includes additional values that may be useful to custom
loggers, and also allows for further expansion if not values are found
useful.

DefaultLogger's formatting was taken from Python's default formatting:
`LEVEL:from: message`

Also included: fmt::Arguments now implement Show, so they can be used to
extend format strings.

[breaking-change]

src/liblog/directive.rs
src/liblog/lib.rs
src/liblog/macros.rs
src/libstd/fmt/mod.rs
src/test/run-pass/capturing-logging.rs
src/test/run-pass/ifmt.rs

index 4329f68b9aaeede834e4ae3b61ddd9e52d7542a3..afbcf8c6ded5a7f35aadbb8614fd6d2995a401ed 100644 (file)
@@ -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<u32> {
     from_str::<u32>(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))
 }
index 87c82e1dd7d424d829ac77e6fae6ef2b2625235a..d0426b1e407330a036ae4a11f53ffa61ed3cb659 100644 (file)
@@ -129,6 +129,8 @@ fn main() {
 
 use sync::one::{Once, ONCE_INIT};
 
+use directive::LOG_LEVEL_NAMES;
+
 pub mod macros;
 mod directive;
 
@@ -162,19 +164,42 @@ fn main() {
 /// 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<io::stdio::StdWriter>,
 }
 
+/// 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 @@ fn drop(&mut self) {
 ///
 /// 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.
index f1d4a318bf080ff1bfd0a4b2babbcc939a00a520..3bb49c237f14686651e21ca731aa0156bd81fc42 100644 (file)
 #[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)+)
         }
     })
 )
index 8e4d8707cca7b788a34c112dc9bc97d89a5267e7..e225dc42e057046333be8eaa73106c593a708e90 100644 (file)
@@ -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.
index a6443dc5a1a9adeae75133266c0545df4e308bd0..3859ed3a53d3ed21f2c570c4e2627683988516b2 100644 (file)
 
 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);
     }
 }
 
index d20c4ec83731ce104db6ad0a093f88e786f3f7e9..16b21f5f53729313b995393b981dfc1f361fd202 100644 (file)
@@ -33,7 +33,7 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     }
 }
 
-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() {