Skip to content

log: LogRecord #13912

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 5, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/liblog/directive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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))
}
Expand Down
76 changes: 68 additions & 8 deletions src/liblog/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ use std::slice;

use sync::one::{Once, ONCE_INIT};

use directive::LOG_LEVEL_NAMES;

pub mod macros;
mod directive;

Expand Down Expand Up @@ -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<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)
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I'm entirely convinced of this new LogLevel structure. The inner field is private, so it doesn't allow inspecting the value itself, and if it were public, why wrap it in a structure? If it's just used for formatting, it seems like it should be used locally in the implementation of DefaultLogger

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My goals are that in other languages, such as Python, a log record contains the level and the levelname. The level is more useful for filtering, such as if record.level >= log::WARN. And then the levelname is more useful in formatting. However, since you can specify custom levels that don't have a name, you might need to output just the level number.

So, I need:

  • easy Ord. Probably against a u32, but maybe it'd be fine to ask people to do if record.level >= LogLevel(20)
  • Easy outputting of the name or number. I figured I could implement fmt::Show and fmt::Signed for LogLevel. Show would try to show the name if possible, and Signed would always just show the number.
  • No need to have both level and levelname as fields on the LogRecord, since they can be derived when needed.

I also wanted to add fmt::Signed to LogLevel.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, in that case, you may also want to make sure the field is public via pub LogLevel(pub u32)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fixing up the missing pieces of LogLevel right now... as a special representation of a u32, does it make since to also include FromPrimitive and ToPrimitive? I don't fully grasp the use of those traits.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd stick with the simpler implementation for now and just implement things like Ord


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(()) => {}
}
Expand All @@ -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);
}

Expand All @@ -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.
Expand Down
7 changes: 6 additions & 1 deletion src/liblog/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)+)
}
})
)
Expand Down
6 changes: 6 additions & 0 deletions src/libstd/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions src/test/run-pass/capturing-logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/test/run-pass/ifmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down