{"action":"created","comment":{"url":"https://api.github.com/repos/rust-lang/log/pulls/comments/23588386","id":23588386,"diff_hunk":"@@ -1,506 +1,632 @@\n-// Copyright 2014 The Rust Project Developers. See the COPYRIGHT\n-// file at the top-level directory of this distribution and at\n-// http://rust-lang.org/COPYRIGHT.\n-//\n-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or\n-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license\n-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your\n-// option. This file may not be copied, modified, or distributed\n-// except according to those terms.\n-\n-//! Utilities for program-wide and customizable logging\n-//!\n-//! ## Example\n-//!\n-//! ```\n-//! #[macro_use] extern crate log;\n-//!\n-//! fn main() {\n-//!     debug!(\"this is a debug {}\", \"message\");\n-//!     error!(\"this is printed by default\");\n-//!\n-//!     if log_enabled!(log::INFO) {\n-//!         let x = 3i * 4i; // expensive computation\n-//!         info!(\"the answer was: {}\", x);\n+//! A lightweight logging facade.\n+//!\n+//! A logging facade provides a single logging API that abstracts over the\n+//! actual logging implementation. Libraries can use the logging API provided\n+//! by this crate, and the consumer of those libraries can choose the logging\n+//! framework that is most suitable for its use case.\n+//!\n+//! If no logging implementation is selected, the facade falls back to a \"noop\"\n+//! implementation that ignores all log messages. The overhead in this case\n+//! is very small - just an integer load, comparison and jump.\n+//!\n+//! # Use\n+//!\n+//! ## In libraries\n+//!\n+//! Libraries should link only to the `log` crate, and use the provided\n+//! macros to log whatever information will be useful to downstream consumers.\n+//!\n+//! ### Examples\n+//!\n+//! ```rust\n+//! # #![allow(unstable)]\n+//! #[macro_use]\n+//! extern crate log;\n+//!\n+//! # pub struct Yak(String);\n+//! # impl Yak { fn shave(&self, _: u32) {} }\n+//! # fn find_a_razor() -> Result<u32, u32> { Ok(1) }\n+//! pub fn shave_the_yak(yak: &Yak) {\n+//!     trace!(\"Commencing yak shaving\");\n+//!\n+//!     loop {\n+//!         match find_a_razor() {\n+//!             Ok(razor) => {\n+//!                 info!(\"Razor located: {}\", razor);\n+//!                 yak.shave(razor);\n+//!                 break;\n+//!             }\n+//!             Err(err) => {\n+//!                 warn!(\"Unable to locate a razor: {}, retrying\", err);\n+//!             }\n+//!         }\n //!     }\n //! }\n+//! # fn main() {}\n //! ```\n //!\n-//! Assumes the binary is `main`:\n-//!\n-//! ```{.bash}\n-//! $ RUST_LOG=error ./main\n-//! ERROR:main: this is printed by default\n-//! ```\n-//!\n-//! ```{.bash}\n-//! $ RUST_LOG=info ./main\n-//! ERROR:main: this is printed by default\n-//! INFO:main: the answer was: 12\n-//! ```\n-//!\n-//! ```{.bash}\n-//! $ RUST_LOG=debug ./main\n-//! DEBUG:main: this is a debug message\n-//! ERROR:main: this is printed by default\n-//! INFO:main: the answer was: 12\n-//! ```\n-//!\n-//! You can also set the log level on a per module basis:\n-//!\n-//! ```{.bash}\n-//! $ RUST_LOG=main=info ./main\n-//! ERROR:main: this is printed by default\n-//! INFO:main: the answer was: 12\n-//! ```\n-//!\n-//! And enable all logging:\n-//!\n-//! ```{.bash}\n-//! $ RUST_LOG=main ./main\n-//! DEBUG:main: this is a debug message\n-//! ERROR:main: this is printed by default\n-//! INFO:main: the answer was: 12\n-//! ```\n-//!\n+//! ## In executables\n //!\n-//! ## Logging Macros\n+//! Executables should chose a logging framework and initialize it early in the\n+//! runtime of the program. Logging frameworks will typically include a\n+//! function to do this. Any log messages generated before the framework is\n+//! initialized will be ignored.\n //!\n-//! There are five macros that the logging subsystem uses:\n+//! The executable itself may use the `log` crate to log as well.\n //!\n-//! * `log!(level, ...)` - the generic logging macro, takes a level as a u32 and any\n-//!                        related `format!` arguments\n-//! * `debug!(...)` - a macro hard-wired to the log level of `DEBUG`\n-//! * `info!(...)` - a macro hard-wired to the log level of `INFO`\n-//! * `warn!(...)` - a macro hard-wired to the log level of `WARN`\n-//! * `error!(...)` - a macro hard-wired to the log level of `ERROR`\n+//! ### Warning\n //!\n-//! All of these macros use the same style of syntax as the `format!` syntax\n-//! extension. Details about the syntax can be found in the documentation of\n-//! `std::fmt` along with the Rust tutorial/manual.\n+//! The logging system may only be initialized once.\n //!\n-//! If you want to check at runtime if a given logging level is enabled (e.g. if the\n-//! information you would want to log is expensive to produce), you can use the\n-//! following macro:\n+//! ### Examples\n //!\n-//! * `log_enabled!(level)` - returns true if logging of the given level is enabled\n+//! ```rust,ignore\n+//! #[macro_use]\n+//! extern crate log;\n+//! extern crate my_logger;\n //!\n-//! ## Enabling logging\n+//! fn main() {\n+//!     my_logger::init();\n //!\n-//! Log levels are controlled on a per-module basis, and by default all logging is\n-//! disabled except for `error!` (a log level of 1). Logging is controlled via the\n-//! `RUST_LOG` environment variable. The value of this environment variable is a\n-//! comma-separated list of logging directives. A logging directive is of the form:\n+//!     info!(\"starting up\");\n //!\n-//! ```text\n-//! path::to::module=log_level\n+//!     // ...\n+//! }\n //! ```\n //!\n-//! The path to the module is rooted in the name of the crate it was compiled for,\n-//! so if your program is contained in a file `hello.rs`, for example, to turn on\n-//! logging for this file you would use a value of `RUST_LOG=hello`.\n-//! Furthermore, this path is a prefix-search, so all modules nested in the\n-//! specified module will also have logging enabled.\n+//! # Logger implementations\n //!\n-//! The actual `log_level` is optional to specify. If omitted, all logging will be\n-//! enabled. If specified, the it must be either a numeric in the range of 1-255, or\n-//! it must be one of the strings `debug`, `error`, `info`, or `warn`. If a numeric\n-//! is specified, then all logging less than or equal to that numeral is enabled.\n-//! For example, if logging level 3 is active, error, warn, and info logs will be\n-//! printed, but debug will be omitted.\n+//! Loggers implement the `Log` trait. Here's a very basic example that simply\n+//! logs all messages at the `Error`, `Warn` or `Info` levels to stdout:\n //!\n-//! As the log level for a module is optional, the module to enable logging for is\n-//! also optional. If only a `log_level` is provided, then the global log level for\n-//! all modules is set to this value.\n+//! ```rust\n+//! extern crate log;\n //!\n-//! Some examples of valid values of `RUST_LOG` are:\n+//! use log::{LogRecord, LogLevel};\n //!\n-//! * `hello` turns on all logging for the 'hello' module\n-//! * `info` turns on all info logging\n-//! * `hello=debug` turns on debug logging for 'hello'\n-//! * `hello=3` turns on info logging for 'hello'\n-//! * `hello,std::option` turns on hello, and std's option logging\n-//! * `error,hello=warn` turn on global error logging and also warn for hello\n+//! struct SimpleLogger;\n //!\n-//! ## Filtering results\n-//!\n-//! A RUST_LOG directive may include a regex filter. The syntax is to append `/`\n-//! followed by a regex. Each message is checked against the regex, and is only\n-//! logged if it matches. Note that the matching is done after formatting the log\n-//! string but before adding any logging meta-data. There is a single filter for all\n-//! modules.\n-//!\n-//! Some examples:\n-//!\n-//! * `hello/foo` turns on all logging for the 'hello' module where the log message\n-//! includes 'foo'.\n-//! * `info/f.o` turns on all info logging where the log message includes 'foo',\n-//! 'f1o', 'fao', etc.\n-//! * `hello=debug/foo*foo` turns on debug logging for 'hello' where the log\n-//! message includes 'foofoo' or 'fofoo' or 'fooooooofoo', etc.\n-//! * `error,hello=warn/[0-9] scopes` turn on global error logging and also warn for\n-//!  hello. In both cases the log message must include a single digit number\n-//!  followed by 'scopes'\n+//! impl log::Log for SimpleLogger {\n+//!     fn enabled(&self, level: LogLevel, _module: &str) -> bool {\n+//!         level <= LogLevel::Info\n+//!     }\n //!\n-//! ## Performance and Side Effects\n+//!     fn log(&self, record: &LogRecord) {\n+//!         if self.enabled(record.level, record.location.module_path) {\n+//!             println!(\"{} - {}\", record.level, record.args);\n+//!         }\n+//!     }\n+//! }\n //!\n-//! Each of these macros will expand to code similar to:\n+//! # fn main() {}\n+//! ```\n //!\n-//! ```rust,ignore\n-//! if log_level <= my_module_log_level() {\n-//!     ::log::log(log_level, format!(...));\n+//! Loggers are installed by calling the `set_logger` function. It takes a\n+//! closure which is provided a `MaxLogLevel` token and returns a `Log` trait\n+//! object. The `MaxLogLevel` token controls the global maximum log level. The\n+//! logging facade uses this as an optimization to improve performance of log\n+//! messages at levels that are disabled. In the case of our example logger,\n+//! we'll want to set the maximum log level to `Info`, since we ignore any\n+//! `Debug` or `Trace` level log messages. A logging framework should provide a\n+//! function that wraps a call to `set_logger`, handling initialization of the\n+//! logger:\n+//!\n+//! ```rust\n+//! # extern crate log;\n+//! # use log::{LogLevel, LogLevelFilter, SetLoggerError};\n+//! # struct SimpleLogger;\n+//! # impl log::Log for SimpleLogger {\n+//! #   fn enabled(&self, _: LogLevel, _: &str) -> bool { false }\n+//! #   fn log(&self, _: &log::LogRecord) {}\n+//! # }\n+//! # fn main() {}\n+//! pub fn init() -> Result<(), SetLoggerError> {\n+//!     log::set_logger(|max_log_level| {\n+//!         max_log_level.set(LogLevelFilter::Info);\n+//!         Box::new(SimpleLogger)\n+//!     })\n //! }\n //! ```\n-//!\n-//! What this means is that each of these macros are very cheap at runtime if\n-//! they're turned off (just a load and an integer comparison). This also means that\n-//! if logging is disabled, none of the components of the log will be executed.\n-\n #![doc(html_logo_url = \"http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png\",\n        html_favicon_url = \"http://www.rust-lang.org/favicon.ico\",\n        html_root_url = \"http://doc.rust-lang.org/log/\")]\n-#![deny(missing_docs)]\n-#![cfg_attr(test, deny(warnings))]\n+#![warn(missing_docs)]\n #![allow(unstable)]\n \n-extern crate regex;\n-\n-use std::cell::RefCell;\n+use std::ascii::AsciiExt;\n+use std::cmp;\n use std::fmt;\n-use std::io::LineBufferedWriter;\n-use std::io;\n use std::mem;\n-use std::os;\n+use std::ops::Deref;\n use std::rt;\n-use std::slice;\n-use std::sync::{Once, ONCE_INIT};\n+use std::str::FromStr;\n+use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};\n+mod macros;\n \n-use regex::Regex;\n+// The setup here is a bit weird to make at_exit work.\n+//\n+// There are four different states that we care about: the logger's\n+// uninitialized, the logger's initializing (set_logger's been called but\n+// LOGGER hasn't actually been set yet), the logger's active, or the logger's\n+// shutting down inside of at_exit.\n+//\n+// The LOGGER static is normally a Box<Box<Log>> with some special possible\n+// values as well. The uninitialized and initializing states are represented by\n+// the values 0 and 1 respectively. The shutting down state is also represented\n+// by 1. Any other value is a valid pointer to the logger.\n+//\n+// The at_exit routine needs to make sure that no threads are actively logging\n+// when it deallocates the logger. The number of actively logging threads is\n+// tracked in the REFCOUNT static. The routine first sets LOGGER back to 1.\n+// All logging calls past that point will immediatly return without accessing\n+// the logger. At that point, the at_exit routine just waits for the refcount\n+// to reach 0 before deallocating the logger. Note that the refcount does not\n+// necessarily monotonically decrease at this point, as new log calls still\n+// increment and decrement it, but the interval in between is small enough that\n+// the wait is really just for the active log calls to finish.\n+static LOGGER: AtomicUsize = ATOMIC_USIZE_INIT;\n+static REFCOUNT: AtomicUsize = ATOMIC_USIZE_INIT;\n+\n+const UNINITIALIZED: usize = 0;\n+const INITIALIZING: usize = 1;\n+\n+static MAX_LOG_LEVEL_FILTER: AtomicUsize = ATOMIC_USIZE_INIT;\n+\n+static LOG_LEVEL_NAMES: [&'static str; 6] = [\"OFF\", \"ERROR\", \"WARN\", \"INFO\", \"DEBUG\", \"TRACE\"];\n+\n+/// An enum representing the available verbosity levels of the logging framework\n+///\n+/// A `LogLevel` may be compared directly to a `LogLevelFilter`.\n+#[repr(usize)]\n+#[derive(Copy, Eq, Debug)]\n+pub enum LogLevel {\n+    /// The \"error\" level.\n+    ///\n+    /// Designates very serious errors.\n+    Error,\n+    /// The \"warn\" level.\n+    ///\n+    /// Designates hazardous situations.\n+    Warn,\n+    /// The \"info\" level.\n+    ///\n+    /// Designates useful information.\n+    Info,\n+    /// The \"debug\" level.\n+    ///\n+    /// Designates lower priority information.\n+    Debug,\n+    /// The \"trace\" level.\n+    ///\n+    /// Designates very low priority, often extremely verbose, information.\n+    Trace,\n+}\n \n-use directive::LOG_LEVEL_NAMES;\n+impl Clone for LogLevel {\n+    #[inline]\n+    fn clone(&self) -> LogLevel {\n+        *self\n+    }\n+}\n \n-#[macro_use]\n-pub mod macros;\n-mod directive;\n+impl PartialEq for LogLevel {\n+    #[inline]\n+    fn eq(&self, other: &LogLevel) -> bool {\n+        *self as usize == *other as usize\n+    }\n+}\n \n-/// Maximum logging level of a module that can be specified. Common logging\n-/// levels are found in the DEBUG/INFO/WARN/ERROR constants.\n-pub const MAX_LOG_LEVEL: u32 = 255;\n+impl PartialEq<LogLevelFilter> for LogLevel {\n+    #[inline]\n+    fn eq(&self, other: &LogLevelFilter) -> bool {\n+        (*self as usize) + 1 == *other as usize\n+    }\n+}\n \n-/// The default logging level of a crate if no other is specified.\n-const DEFAULT_LOG_LEVEL: u32 = 1;\n+impl PartialOrd for LogLevel {\n+    #[inline]\n+    fn partial_cmp(&self, other: &LogLevel) -> Option<cmp::Ordering> {\n+        Some(self.cmp(other))\n+    }\n+}\n \n-/// An unsafe constant that is the maximum logging level of any module\n-/// specified. This is the first line of defense to determining whether a\n-/// logging statement should be run.\n-static mut LOG_LEVEL: u32 = MAX_LOG_LEVEL;\n+impl PartialOrd<LogLevelFilter> for LogLevel {\n+    #[inline]\n+    fn partial_cmp(&self, other: &LogLevelFilter) -> Option<cmp::Ordering> {\n+        Some(((*self as usize) + 1).cmp(&(*other as usize)))\n+    }\n+}\n \n-static mut DIRECTIVES: *const Vec<directive::LogDirective> =\n-    0 as *const Vec<directive::LogDirective>;\n+impl Ord for LogLevel {\n+    #[inline]\n+    fn cmp(&self, other: &LogLevel) -> cmp::Ordering {\n+        (*self as usize).cmp(&(*other as usize))\n+    }\n+}\n \n-/// Optional regex filter.\n-static mut FILTER: *const Regex = 0 as *const _;\n+impl FromStr for LogLevel {\n+    fn from_str(level: &str) -> Option<LogLevel> {\n+        LOG_LEVEL_NAMES.iter()\n+            .position(|&name| name.eq_ignore_ascii_case(level))\n+            .into_iter()\n+            .filter(|&idx| idx != 0)\n+            .map(|idx| unsafe { mem::transmute(idx - 1) })\n+            .next()\n+    }\n+}\n \n-/// Debug log level\n-pub const DEBUG: u32 = 4;\n-/// Info log level\n-pub const INFO: u32 = 3;\n-/// Warn log level\n-pub const WARN: u32 = 2;\n-/// Error log level\n-pub const ERROR: u32 = 1;\n+impl fmt::Display for LogLevel {\n+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n+        write!(fmt, \"{}\", LOG_LEVEL_NAMES[(*self as usize) + 1])\n+    }\n+}\n \n-thread_local!(static LOCAL_LOGGER: RefCell<Option<Box<Logger + Send>>> = {\n-    RefCell::new(None)\n-});\n+impl LogLevel {\n+    /// Returns the most verbose logging level.\n+    #[inline]\n+    pub fn max() -> LogLevel {\n+        LogLevel::Trace\n+    }\n \n-/// A trait used to represent an interface to a task-local logger. Each task\n-/// can have its own custom logger which can respond to logging messages\n-/// however it likes.\n-pub trait Logger {\n-    /// Logs a single message described by the `record`.\n-    fn log(&mut self, record: &LogRecord);\n+    /// Converts the `LogLevel` to the equivalent `LogLevelFilter`.\n+    #[inline]\n+    pub fn to_log_level_filter(&self) -> LogLevelFilter {\n+        unsafe { mem::transmute((*self as usize) + 1) }\n+    }\n }\n \n-struct DefaultLogger {\n-    handle: LineBufferedWriter<io::stdio::StdWriter>,\n+/// An enum representing the available verbosity level filters of the logging\n+/// framework.\n+///\n+/// A `LogLevelFilter` may be compared directly to a `LogLevel`.\n+#[repr(usize)]\n+#[derive(Copy, Eq, Debug)]\n+pub enum LogLevelFilter {\n+    /// A level lower than all log levels.\n+    Off,\n+    /// Corresponds to the `Error` log level.\n+    Error,\n+    /// Corresponds to the `Warn` log level.\n+    Warn,\n+    /// Corresponds to the `Trace` log level.\n+    Info,\n+    /// Corresponds to the `Debug` log level.\n+    Debug,\n+    /// Corresponds to the `Trace` log level.\n+    Trace,\n }\n \n-/// Wraps the log level with fmt implementations.\n-#[derive(PartialEq, PartialOrd, Show)]\n-pub struct LogLevel(pub u32);\n-\n-impl Copy for LogLevel {}\n+// Deriving generates terrible impls of these traits\n \n-impl fmt::Display for LogLevel {\n-    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n-        let LogLevel(level) = *self;\n-        match LOG_LEVEL_NAMES.get(level as usize - 1) {\n-            Some(name) => name.fmt(fmt),\n-            None => level.fmt(fmt)\n-        }\n+impl Clone for LogLevelFilter {\n+    #[inline]\n+    fn clone(&self) -> LogLevelFilter {\n+        *self\n     }\n }\n \n-impl Logger for DefaultLogger {\n-    fn log(&mut self, record: &LogRecord) {\n-        match writeln!(&mut self.handle,\n-                       \"{}:{}: {}\",\n-                       record.level,\n-                       record.module_path,\n-                       record.args) {\n-            Err(e) => panic!(\"failed to log: {}\", e),\n-            Ok(()) => {}\n-        }\n+impl PartialEq for LogLevelFilter {\n+    #[inline]\n+    fn eq(&self, other: &LogLevelFilter) -> bool {\n+        *self as usize == *other as usize\n     }\n }\n \n-impl Drop for DefaultLogger {\n-    fn drop(&mut self) {\n-        // FIXME(#12628): is panicking the right thing to do?\n-        match self.handle.flush() {\n-            Err(e) => panic!(\"failed to flush a logger: {}\", e),\n-            Ok(()) => {}\n-        }\n+impl PartialEq<LogLevel> for LogLevelFilter {\n+    #[inline]\n+    fn eq(&self, other: &LogLevel) -> bool {\n+        other.eq(self)\n     }\n }\n \n-/// This function is called directly by the compiler when using the logging\n-/// macros. This function does not take into account whether the log level\n-/// specified is active or not, it will always log something if this method is\n-/// called.\n-///\n-/// It is not recommended to call this function directly, rather it should be\n-/// invoked through the logging family of macros.\n-#[doc(hidden)]\n-pub fn log(level: u32, loc: &'static LogLocation, args: fmt::Arguments) {\n-    // Test the literal string from args against the current filter, if there\n-    // is one.\n-    match unsafe { FILTER.as_ref() } {\n-        Some(filter) if !filter.is_match(args.to_string().as_slice()) => return,\n-        _ => {}\n+impl PartialOrd for LogLevelFilter {\n+    #[inline]\n+    fn partial_cmp(&self, other: &LogLevelFilter) -> Option<cmp::Ordering> {\n+        Some(self.cmp(other))\n     }\n+}\n \n-    // Completely remove the local logger from TLS in case anyone attempts to\n-    // frob the slot while we're doing the logging. This will destroy any logger\n-    // set during logging.\n-    let mut logger = LOCAL_LOGGER.with(|s| {\n-        s.borrow_mut().take()\n-    }).unwrap_or_else(|| {\n-        Box::new(DefaultLogger { handle: io::stderr() }) as Box<Logger + Send>\n-    });\n-    logger.log(&LogRecord {\n-        level: LogLevel(level),\n-        args: args,\n-        file: loc.file,\n-        module_path: loc.module_path,\n-        line: loc.line,\n-    });\n-    set_logger(logger);\n+impl PartialOrd<LogLevel> for LogLevelFilter {\n+    #[inline]\n+    fn partial_cmp(&self, other: &LogLevel) -> Option<cmp::Ordering> {\n+        other.partial_cmp(self).map(|x| x.reverse())\n+    }\n }\n \n-/// Getter for the global log level. This is a function so that it can be called\n-/// safely\n-#[doc(hidden)]\n-#[inline(always)]\n-pub fn log_level() -> u32 { unsafe { LOG_LEVEL } }\n+impl Ord for LogLevelFilter {\n+    #[inline]\n+    fn cmp(&self, other: &LogLevelFilter) -> cmp::Ordering {\n+        (*self as usize).cmp(&(*other as usize))\n+    }\n+}\n \n-/// Replaces the task-local logger with the specified logger, returning the old\n-/// logger.\n-pub fn set_logger(logger: Box<Logger + Send>) -> Option<Box<Logger + Send>> {\n-    let mut l = Some(logger);\n-    LOCAL_LOGGER.with(|slot| {\n-        mem::replace(&mut *slot.borrow_mut(), l.take())\n-    })\n+impl FromStr for LogLevelFilter {\n+    fn from_str(level: &str) -> Option<LogLevelFilter> {\n+        LOG_LEVEL_NAMES.iter()\n+            .position(|&name| name.eq_ignore_ascii_case(level))\n+            .map(|p| unsafe { mem::transmute(p) })\n+    }\n }\n \n-/// A LogRecord is created by the logging macros, and passed as the only\n-/// argument to Loggers.\n-#[derive(Show)]\n-pub struct LogRecord<'a> {\n+impl fmt::Display for LogLevelFilter {\n+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n+        write!(fmt, \"{}\", LOG_LEVEL_NAMES[*self as usize])\n+    }\n+}\n \n-    /// The module path of where the LogRecord originated.\n-    pub module_path: &'a str,\n+impl LogLevelFilter {\n+    /// Returns the most verbose logging level filter.\n+    #[inline]\n+    pub fn max() -> LogLevelFilter {\n+        LogLevelFilter::Trace\n+    }\n \n-    /// The LogLevel of this record.\n-    pub level: LogLevel,\n+    /// Converts `self` to the equivalent `LogLevel`.\n+    ///\n+    /// Returns `None` if `self` is `LogLevel::Off`.\n+    #[inline]\n+    pub fn to_log_level(&self) -> Option<LogLevel> {\n+        match *self {\n+            LogLevelFilter::Off => None,\n+            v => unsafe { Some(mem::transmute((v as usize) - 1)) }\n+        }\n+    }\n+}\n \n-    /// The arguments from the log line.\n+/// The \"payload\" of a log message.\n+pub struct LogRecord<'a> {\n+    /// The message body.\n     pub args: fmt::Arguments<'a>,\n+    /// The location of the log directive.\n+    pub location: &'static LogLocation,\n+    /// The verbosity level of the message.\n+    pub level: LogLevel,\n+}\n \n-    /// The file of where the LogRecord originated.\n-    pub file: &'a str,\n-\n-    /// The line number of where the LogRecord originated.\n-    pub line: usize,\n+/// A trait encapsulating the operations required of a logger\n+pub trait Log: Sync+Send {\n+    /// Determines if a log message sent at the specified level from the\n+    /// specified module would be logged.\n+    ///\n+    /// This is used by the `log_enabled!` macro to allow callers to avoid\n+    /// expensive computation of log message arguments if the message would be\n+    /// discarded anyway.\n+    fn enabled(&self, level: LogLevel, module: &str) -> bool;\n+\n+    /// Logs the `LogRecord`.\n+    ///\n+    /// Note that `enabled` is *not* necessarily called before this method.\n+    /// Implementations of `log` should perform all necessary filtering\n+    /// internally.\n+    fn log(&self, record: &LogRecord);\n }\n \n-#[doc(hidden)]\n+/// The location of a log message.\n+#[derive(Copy, Clone, Debug)]\n pub struct LogLocation {\n+    /// The module path of the message.\n     pub module_path: &'static str,\n+    /// The source file containing the message.\n     pub file: &'static str,\n+    /// The line containing the message.\n     pub line: usize,\n }\n \n-impl Copy for LogLocation {}\n-\n-/// Tests whether a given module's name is enabled for a particular level of\n-/// logging. This is the second layer of defense about determining whether a\n-/// module's log statement should be emitted or not.\n-#[doc(hidden)]\n-pub fn mod_enabled(level: u32, module: &str) -> bool {\n-    static INIT: Once = ONCE_INIT;\n-    INIT.call_once(init);\n-\n-    // It's possible for many threads are in this function, only one of them\n-    // will perform the global initialization, but all of them will need to check\n-    // again to whether they should really be here or not. Hence, despite this\n-    // check being expanded manually in the logging macro, this function checks\n-    // the log level again.\n-    if level > unsafe { LOG_LEVEL } { return false }\n-\n-    // This assertion should never get tripped unless we're in an at_exit\n-    // handler after logging has been torn down and a logging attempt was made.\n-    assert!(unsafe { !DIRECTIVES.is_null() });\n-\n-    enabled(level, module, unsafe { (*DIRECTIVES).iter() })\n-}\n-\n-fn enabled(level: u32,\n-           module: &str,\n-           iter: slice::Iter<directive::LogDirective>)\n-           -> bool {\n-    // Search for the longest match, the vector is assumed to be pre-sorted.\n-    for directive in iter.rev() {\n-        match directive.name {\n-            Some(ref name) if !module.starts_with(name.as_slice()) => {},\n-            Some(..) | None => {\n-                return level <= directive.level\n-            }\n-        }\n+/// A token providing read and write access to the global maximum log level\n+/// filter.\n+///\n+/// The maximum log level is used as an optimization to avoid evaluating log\n+/// messages that will be ignored by the logger. Any message with a level\n+/// higher than the maximum log level filter will be ignored. A logger should\n+/// make sure to keep the maximum log level filter in sync with its current\n+/// configuration.\n+#[allow(missing_copy_implementations)]\n+pub struct MaxLogLevelFilter(());\n+\n+impl fmt::Debug for MaxLogLevelFilter {\n+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n+        write!(fmt, \"MaxLogLevelFilter\")\n     }\n-    level <= DEFAULT_LOG_LEVEL\n }\n \n-/// Initialize logging for the current process.\n+impl MaxLogLevelFilter {\n+    /// Gets the current maximum log level filter.\n+    pub fn get(&self) -> LogLevelFilter {\n+        max_log_level()\n+    }\n+\n+    /// Sets the maximum log level.\n+    pub fn set(&self, level: LogLevelFilter) {\n+        MAX_LOG_LEVEL_FILTER.store(level as usize, Ordering::Relaxed)\n+    }\n+}\n+\n+/// Returns the current maximum log level.\n ///\n-/// This is not threadsafe at all, so initialization is performed through a\n-/// `Once` primitive (and this function is called from that primitive).\n-fn init() {\n-    let (mut directives, filter) = match os::getenv(\"RUST_LOG\") {\n-        Some(spec) => directive::parse_logging_spec(spec.as_slice()),\n-        None => (Vec::new(), None),\n-    };\n-\n-    // Sort the provided directives by length of their name, this allows a\n-    // little more efficient lookup at runtime.\n-    directives.sort_by(|a, b| {\n-        let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0);\n-        let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0);\n-        alen.cmp(&blen)\n-    });\n+/// The `log!`, `error!`, `warn!`, `info!`, `debug!`, and `trace!` macros check\n+/// this value and discard any message logged at a higher level. The maximum\n+/// log level is set by the `MaxLogLevel` token passed to loggers.\n+#[inline(always)]\n+pub fn max_log_level() -> LogLevelFilter {\n+    unsafe { mem::transmute(MAX_LOG_LEVEL_FILTER.load(Ordering::Relaxed)) }\n+}\n \n-    let max_level = {\n-        let max = directives.iter().max_by(|d| d.level);\n-        max.map(|d| d.level).unwrap_or(DEFAULT_LOG_LEVEL)\n-    };\n+/// Sets the global logger.\n+///\n+/// The `make_logger` closure is passed a `MaxLogLevel` object, which the\n+/// logger should use to keep the global maximum log level in sync with the\n+/// highest log level that the logger will not ignore.\n+///\n+/// This function may only be called once in the lifetime of a program. Any log\n+/// events that occur before the call to `set_logger` completes will be\n+/// ignored.\n+///\n+/// This function does not typically need to be called manually. Logger\n+/// implementations should provide an initialization method that calls\n+/// `set_logger` internally.\n+pub fn set_logger<M>(make_logger: M) -> Result<(), SetLoggerError>\n+        where M: FnOnce(MaxLogLevelFilter) -> Box<Log> {\n+    if LOGGER.compare_and_swap(UNINITIALIZED, INITIALIZING, Ordering::Relaxed) != UNINITIALIZED {\n+        return Err(SetLoggerError);\n+    }\n \n-    unsafe {\n-        LOG_LEVEL = max_level;\n+    let logger = Box::new(make_logger(MaxLogLevelFilter(())));\n+    let logger = unsafe { mem::transmute::<Box<Box<Log>>, usize>(logger) };\n+    LOGGER.store(logger, Ordering::Release);\n+    rt::at_exit(|| {\n+        // Set to INITIALIZING to prevent re-initialization after\n+        let logger = LOGGER.swap(INITIALIZING, Ordering::Acquire);\n \n-        assert!(FILTER.is_null());\n-        match filter {\n-            Some(f) => FILTER = mem::transmute(Box::new(f)),\n-            None => {}\n+        while REFCOUNT.load(Ordering::Relaxed) != 0 {\n+            // FIXME add a sleep here when it doesn't involve timers\n         }\n \n-        assert!(DIRECTIVES.is_null());\n-        DIRECTIVES = mem::transmute(Box::new(directives));\n-\n-        // Schedule the cleanup for the globals for when the runtime exits.\n-        rt::at_exit(|| {\n-            assert!(!DIRECTIVES.is_null());\n-            let _directives: Box<Vec<directive::LogDirective>> =\n-                mem::transmute(DIRECTIVES);\n-            DIRECTIVES = 0 as *const Vec<directive::LogDirective>;\n-\n-            if !FILTER.is_null() {\n-                let _filter: Box<Regex> = mem::transmute(FILTER);\n-                FILTER = 0 as *const _;\n-            }\n-        });\n-    }\n+        unsafe { mem::transmute::<usize, Box<Box<Log>>>(logger); }\n+    });\n+\n+    Ok(())\n }\n \n-#[cfg(test)]\n-mod tests {\n-    use super::enabled;\n-    use directive::LogDirective;\n-\n-    #[test]\n-    fn match_full_path() {\n-        let dirs = [\n-            LogDirective {\n-                name: Some(\"crate2\".to_string()),\n-                level: 3\n-            },\n-            LogDirective {\n-                name: Some(\"crate1::mod1\".to_string()),\n-                level: 2\n-            }\n-        ];\n-        assert!(enabled(2, \"crate1::mod1\", dirs.iter()));\n-        assert!(!enabled(3, \"crate1::mod1\", dirs.iter()));\n-        assert!(enabled(3, \"crate2\", dirs.iter()));\n-        assert!(!enabled(4, \"crate2\", dirs.iter()));\n+/// The type returned by `set_logger` if `set_logger` has already been called.\n+#[allow(missing_copy_implementations)]\n+#[derive(Debug)]\n+pub struct SetLoggerError;\n+\n+impl fmt::Display for SetLoggerError {\n+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n+        write!(fmt, \"Attempted to set a logger after the logging system was already initialized\")","path":"src/lib.rs","position":863,"original_position":863,"commit_id":"42a353eaf46528b99917dd8dc141cc93c791f7eb","original_commit_id":"42a353eaf46528b99917dd8dc141cc93c791f7eb","user":{"login":"alexcrichton","id":64996,"avatar_url":"https://avatars.githubusercontent.com/u/64996?v=3","gravatar_id":"","url":"https://api.github.com/users/alexcrichton","html_url":"https://github.com/alexcrichton","followers_url":"https://api.github.com/users/alexcrichton/followers","following_url":"https://api.github.com/users/alexcrichton/following{/other_user}","gists_url":"https://api.github.com/users/alexcrichton/gists{/gist_id}","starred_url":"https://api.github.com/users/alexcrichton/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexcrichton/subscriptions","organizations_url":"https://api.github.com/users/alexcrichton/orgs","repos_url":"https://api.github.com/users/alexcrichton/repos","events_url":"https://api.github.com/users/alexcrichton/events{/privacy}","received_events_url":"https://api.github.com/users/alexcrichton/received_events","type":"User","site_admin":false},"body":"lowercase `A`","created_at":"2015-01-27T05:40:55Z","updated_at":"2015-01-27T05:40:55Z","html_url":"https://github.com/rust-lang/log/pull/12#discussion_r23588386","pull_request_url":"https://api.github.com/repos/rust-lang/log/pulls/12","_links":{"self":{"href":"https://api.github.com/repos/rust-lang/log/pulls/comments/23588386"},"html":{"href":"https://github.com/rust-lang/log/pull/12#discussion_r23588386"},"pull_request":{"href":"https://api.github.com/repos/rust-lang/log/pulls/12"}}},"pull_request":{"url":"https://api.github.com/repos/rust-lang/log/pulls/12","id":27988500,"html_url":"https://github.com/rust-lang/log/pull/12","diff_url":"https://github.com/rust-lang/log/pull/12.diff","patch_url":"https://github.com/rust-lang/log/pull/12.patch","issue_url":"https://api.github.com/repos/rust-lang/log/issues/12","number":12,"state":"open","locked":false,"title":"Rewrite liblog into a logging facade","user":{"login":"sfackler","id":1455697,"avatar_url":"https://avatars.githubusercontent.com/u/1455697?v=3","gravatar_id":"","url":"https://api.github.com/users/sfackler","html_url":"https://github.com/sfackler","followers_url":"https://api.github.com/users/sfackler/followers","following_url":"https://api.github.com/users/sfackler/following{/other_user}","gists_url":"https://api.github.com/users/sfackler/gists{/gist_id}","starred_url":"https://api.github.com/users/sfackler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sfackler/subscriptions","organizations_url":"https://api.github.com/users/sfackler/orgs","repos_url":"https://api.github.com/users/sfackler/repos","events_url":"https://api.github.com/users/sfackler/events{/privacy}","received_events_url":"https://api.github.com/users/sfackler/received_events","type":"User","site_admin":false},"body":"See issue #3 for background.\r\n\r\nCloses #3\r\nCloses #7\r\nCloses #11","created_at":"2015-01-25T06:51:27Z","updated_at":"2015-01-27T05:40:55Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8634e685b1faf10e381de9356edc233de52a0bc1","assignee":{"login":"huonw","id":1203825,"avatar_url":"https://avatars.githubusercontent.com/u/1203825?v=3","gravatar_id":"","url":"https://api.github.com/users/huonw","html_url":"https://github.com/huonw","followers_url":"https://api.github.com/users/huonw/followers","following_url":"https://api.github.com/users/huonw/following{/other_user}","gists_url":"https://api.github.com/users/huonw/gists{/gist_id}","starred_url":"https://api.github.com/users/huonw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/huonw/subscriptions","organizations_url":"https://api.github.com/users/huonw/orgs","repos_url":"https://api.github.com/users/huonw/repos","events_url":"https://api.github.com/users/huonw/events{/privacy}","received_events_url":"https://api.github.com/users/huonw/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/rust-lang/log/pulls/12/commits","review_comments_url":"https://api.github.com/repos/rust-lang/log/pulls/12/comments","review_comment_url":"https://api.github.com/repos/rust-lang/log/pulls/comments/{number}","comments_url":"https://api.github.com/repos/rust-lang/log/issues/12/comments","statuses_url":"https://api.github.com/repos/rust-lang/log/statuses/42a353eaf46528b99917dd8dc141cc93c791f7eb","head":{"label":"sfackler:facade","ref":"facade","sha":"42a353eaf46528b99917dd8dc141cc93c791f7eb","user":{"login":"sfackler","id":1455697,"avatar_url":"https://avatars.githubusercontent.com/u/1455697?v=3","gravatar_id":"","url":"https://api.github.com/users/sfackler","html_url":"https://github.com/sfackler","followers_url":"https://api.github.com/users/sfackler/followers","following_url":"https://api.github.com/users/sfackler/following{/other_user}","gists_url":"https://api.github.com/users/sfackler/gists{/gist_id}","starred_url":"https://api.github.com/users/sfackler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sfackler/subscriptions","organizations_url":"https://api.github.com/users/sfackler/orgs","repos_url":"https://api.github.com/users/sfackler/repos","events_url":"https://api.github.com/users/sfackler/events{/privacy}","received_events_url":"https://api.github.com/users/sfackler/received_events","type":"User","site_admin":false},"repo":{"id":28021251,"name":"log","full_name":"sfackler/log","owner":{"login":"sfackler","id":1455697,"avatar_url":"https://avatars.githubusercontent.com/u/1455697?v=3","gravatar_id":"","url":"https://api.github.com/users/sfackler","html_url":"https://github.com/sfackler","followers_url":"https://api.github.com/users/sfackler/followers","following_url":"https://api.github.com/users/sfackler/following{/other_user}","gists_url":"https://api.github.com/users/sfackler/gists{/gist_id}","starred_url":"https://api.github.com/users/sfackler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sfackler/subscriptions","organizations_url":"https://api.github.com/users/sfackler/orgs","repos_url":"https://api.github.com/users/sfackler/repos","events_url":"https://api.github.com/users/sfackler/events{/privacy}","received_events_url":"https://api.github.com/users/sfackler/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/sfackler/log","description":"","fork":true,"url":"https://api.github.com/repos/sfackler/log","forks_url":"https://api.github.com/repos/sfackler/log/forks","keys_url":"https://api.github.com/repos/sfackler/log/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sfackler/log/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sfackler/log/teams","hooks_url":"https://api.github.com/repos/sfackler/log/hooks","issue_events_url":"https://api.github.com/repos/sfackler/log/issues/events{/number}","events_url":"https://api.github.com/repos/sfackler/log/events","assignees_url":"https://api.github.com/repos/sfackler/log/assignees{/user}","branches_url":"https://api.github.com/repos/sfackler/log/branches{/branch}","tags_url":"https://api.github.com/repos/sfackler/log/tags","blobs_url":"https://api.github.com/repos/sfackler/log/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sfackler/log/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sfackler/log/git/refs{/sha}","trees_url":"https://api.github.com/repos/sfackler/log/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sfackler/log/statuses/{sha}","languages_url":"https://api.github.com/repos/sfackler/log/languages","stargazers_url":"https://api.github.com/repos/sfackler/log/stargazers","contributors_url":"https://api.github.com/repos/sfackler/log/contributors","subscribers_url":"https://api.github.com/repos/sfackler/log/subscribers","subscription_url":"https://api.github.com/repos/sfackler/log/subscription","commits_url":"https://api.github.com/repos/sfackler/log/commits{/sha}","git_commits_url":"https://api.github.com/repos/sfackler/log/git/commits{/sha}","comments_url":"https://api.github.com/repos/sfackler/log/comments{/number}","issue_comment_url":"https://api.github.com/repos/sfackler/log/issues/comments/{number}","contents_url":"https://api.github.com/repos/sfackler/log/contents/{+path}","compare_url":"https://api.github.com/repos/sfackler/log/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sfackler/log/merges","archive_url":"https://api.github.com/repos/sfackler/log/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sfackler/log/downloads","issues_url":"https://api.github.com/repos/sfackler/log/issues{/number}","pulls_url":"https://api.github.com/repos/sfackler/log/pulls{/number}","milestones_url":"https://api.github.com/repos/sfackler/log/milestones{/number}","notifications_url":"https://api.github.com/repos/sfackler/log/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sfackler/log/labels{/name}","releases_url":"https://api.github.com/repos/sfackler/log/releases{/id}","created_at":"2014-12-15T04:55:43Z","updated_at":"2014-12-15T20:34:16Z","pushed_at":"2015-01-25T06:55:15Z","git_url":"git://github.com/sfackler/log.git","ssh_url":"git@github.com:sfackler/log.git","clone_url":"https://github.com/sfackler/log.git","svn_url":"https://github.com/sfackler/log","homepage":null,"size":730,"stargazers_count":0,"watchers_count":0,"language":"Rust","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"rust-lang:master","ref":"master","sha":"e0403e68758645ecfe79f3c3e93dbfbb9e34d400","user":{"login":"rust-lang","id":5430905,"avatar_url":"https://avatars.githubusercontent.com/u/5430905?v=3","gravatar_id":"","url":"https://api.github.com/users/rust-lang","html_url":"https://github.com/rust-lang","followers_url":"https://api.github.com/users/rust-lang/followers","following_url":"https://api.github.com/users/rust-lang/following{/other_user}","gists_url":"https://api.github.com/users/rust-lang/gists{/gist_id}","starred_url":"https://api.github.com/users/rust-lang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rust-lang/subscriptions","organizations_url":"https://api.github.com/users/rust-lang/orgs","repos_url":"https://api.github.com/users/rust-lang/repos","events_url":"https://api.github.com/users/rust-lang/events{/privacy}","received_events_url":"https://api.github.com/users/rust-lang/received_events","type":"Organization","site_admin":false},"repo":{"id":27974762,"name":"log","full_name":"rust-lang/log","owner":{"login":"rust-lang","id":5430905,"avatar_url":"https://avatars.githubusercontent.com/u/5430905?v=3","gravatar_id":"","url":"https://api.github.com/users/rust-lang","html_url":"https://github.com/rust-lang","followers_url":"https://api.github.com/users/rust-lang/followers","following_url":"https://api.github.com/users/rust-lang/following{/other_user}","gists_url":"https://api.github.com/users/rust-lang/gists{/gist_id}","starred_url":"https://api.github.com/users/rust-lang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rust-lang/subscriptions","organizations_url":"https://api.github.com/users/rust-lang/orgs","repos_url":"https://api.github.com/users/rust-lang/repos","events_url":"https://api.github.com/users/rust-lang/events{/privacy}","received_events_url":"https://api.github.com/users/rust-lang/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/rust-lang/log","description":"","fork":false,"url":"https://api.github.com/repos/rust-lang/log","forks_url":"https://api.github.com/repos/rust-lang/log/forks","keys_url":"https://api.github.com/repos/rust-lang/log/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rust-lang/log/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rust-lang/log/teams","hooks_url":"https://api.github.com/repos/rust-lang/log/hooks","issue_events_url":"https://api.github.com/repos/rust-lang/log/issues/events{/number}","events_url":"https://api.github.com/repos/rust-lang/log/events","assignees_url":"https://api.github.com/repos/rust-lang/log/assignees{/user}","branches_url":"https://api.github.com/repos/rust-lang/log/branches{/branch}","tags_url":"https://api.github.com/repos/rust-lang/log/tags","blobs_url":"https://api.github.com/repos/rust-lang/log/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rust-lang/log/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rust-lang/log/git/refs{/sha}","trees_url":"https://api.github.com/repos/rust-lang/log/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rust-lang/log/statuses/{sha}","languages_url":"https://api.github.com/repos/rust-lang/log/languages","stargazers_url":"https://api.github.com/repos/rust-lang/log/stargazers","contributors_url":"https://api.github.com/repos/rust-lang/log/contributors","subscribers_url":"https://api.github.com/repos/rust-lang/log/subscribers","subscription_url":"https://api.github.com/repos/rust-lang/log/subscription","commits_url":"https://api.github.com/repos/rust-lang/log/commits{/sha}","git_commits_url":"https://api.github.com/repos/rust-lang/log/git/commits{/sha}","comments_url":"https://api.github.com/repos/rust-lang/log/comments{/number}","issue_comment_url":"https://api.github.com/repos/rust-lang/log/issues/comments/{number}","contents_url":"https://api.github.com/repos/rust-lang/log/contents/{+path}","compare_url":"https://api.github.com/repos/rust-lang/log/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rust-lang/log/merges","archive_url":"https://api.github.com/repos/rust-lang/log/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rust-lang/log/downloads","issues_url":"https://api.github.com/repos/rust-lang/log/issues{/number}","pulls_url":"https://api.github.com/repos/rust-lang/log/pulls{/number}","milestones_url":"https://api.github.com/repos/rust-lang/log/milestones{/number}","notifications_url":"https://api.github.com/repos/rust-lang/log/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rust-lang/log/labels{/name}","releases_url":"https://api.github.com/repos/rust-lang/log/releases{/id}","created_at":"2014-12-13T21:45:04Z","updated_at":"2015-01-23T13:59:37Z","pushed_at":"2015-01-26T13:00:50Z","git_url":"git://github.com/rust-lang/log.git","ssh_url":"git@github.com:rust-lang/log.git","clone_url":"https://github.com/rust-lang/log.git","svn_url":"https://github.com/rust-lang/log","homepage":null,"size":1029,"stargazers_count":7,"watchers_count":7,"language":"Rust","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":7,"mirror_url":null,"open_issues_count":5,"forks":7,"open_issues":5,"watchers":7,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/rust-lang/log/pulls/12"},"html":{"href":"https://github.com/rust-lang/log/pull/12"},"issue":{"href":"https://api.github.com/repos/rust-lang/log/issues/12"},"comments":{"href":"https://api.github.com/repos/rust-lang/log/issues/12/comments"},"review_comments":{"href":"https://api.github.com/repos/rust-lang/log/pulls/12/comments"},"review_comment":{"href":"https://api.github.com/repos/rust-lang/log/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/rust-lang/log/pulls/12/commits"},"statuses":{"href":"https://api.github.com/repos/rust-lang/log/statuses/42a353eaf46528b99917dd8dc141cc93c791f7eb"}}}}