#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "https://www.rust-lang.org/favicon.ico",
html_root_url = "https://doc.rust-lang.org/log/")]
#![warn(missing_docs)]
#![cfg_attr(feature = "nightly", feature(panic_handler))]
#![cfg_attr(not(feature = "use_std"), no_std)]
#![cfg_attr(rustbuild, feature(staged_api, rustc_private))]
#![cfg_attr(rustbuild, unstable(feature = "rustc_private", issue = "27812"))]
#[cfg(not(feature = "use_std"))]
extern crate core as std;
extern crate log;
use std::cmp;
#[cfg(feature = "use_std")]
use std::error;
use std::fmt;
use std::mem;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
#[macro_use]
mod macros;
static mut LOGGER: *const Log = &NopLogger;
static STATE: AtomicUsize = ATOMIC_USIZE_INIT;
static REFCOUNT: AtomicUsize = ATOMIC_USIZE_INIT;
const INITIALIZING: usize = 1;
const INITIALIZED: usize = 2;
static LOG_LEVEL_NAMES: [&'static str; 6] = ["OFF", "ERROR", "WARN", "INFO",
"DEBUG", "TRACE"];
#[repr(usize)]
#[derive(Copy, Eq, Debug)]
pub enum LogLevel {
Error = 1, Warn,
Info,
Debug,
Trace,
}
impl Clone for LogLevel {
#[inline]
fn clone(&self) -> LogLevel {
*self
}
}
impl PartialEq for LogLevel {
#[inline]
fn eq(&self, other: &LogLevel) -> bool {
*self as usize == *other as usize
}
}
impl PartialEq<LogLevelFilter> for LogLevel {
#[inline]
fn eq(&self, other: &LogLevelFilter) -> bool {
*self as usize == *other as usize
}
}
impl PartialOrd for LogLevel {
#[inline]
fn partial_cmp(&self, other: &LogLevel) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<LogLevelFilter> for LogLevel {
#[inline]
fn partial_cmp(&self, other: &LogLevelFilter) -> Option<cmp::Ordering> {
Some((*self as usize).cmp(&(*other as usize)))
}
}
impl Ord for LogLevel {
#[inline]
fn cmp(&self, other: &LogLevel) -> cmp::Ordering {
(*self as usize).cmp(&(*other as usize))
}
}
fn ok_or<T, E>(t: Option<T>, e: E) -> Result<T, E> {
match t {
Some(t) => Ok(t),
None => Err(e),
}
}
fn eq_ignore_ascii_case(a: &str, b: &str) -> bool {
fn to_ascii_uppercase(c: u8) -> u8 {
if c >= b'a' && c <= b'z' {
c - b'a' + b'A'
} else {
c
}
}
if a.len() == b.len() {
a.bytes()
.zip(b.bytes())
.all(|(a, b)| to_ascii_uppercase(a) == to_ascii_uppercase(b))
} else {
false
}
}
impl FromStr for LogLevel {
type Err = ();
fn from_str(level: &str) -> Result<LogLevel, ()> {
ok_or(LOG_LEVEL_NAMES.iter()
.position(|&name| eq_ignore_ascii_case(name, level))
.into_iter()
.filter(|&idx| idx != 0)
.map(|idx| LogLevel::from_usize(idx).unwrap())
.next(), ())
}
}
impl fmt::Display for LogLevel {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.pad(LOG_LEVEL_NAMES[*self as usize])
}
}
impl LogLevel {
fn from_usize(u: usize) -> Option<LogLevel> {
match u {
1 => Some(LogLevel::Error),
2 => Some(LogLevel::Warn),
3 => Some(LogLevel::Info),
4 => Some(LogLevel::Debug),
5 => Some(LogLevel::Trace),
_ => None
}
}
fn from_new(level: log::Level) -> LogLevel {
match level {
log::Level::Error => LogLevel::Error,
log::Level::Warn => LogLevel::Warn,
log::Level::Info => LogLevel::Info,
log::Level::Debug => LogLevel::Debug,
log::Level::Trace => LogLevel::Trace,
}
}
fn to_new(&self) -> log::Level {
match *self {
LogLevel::Error => log::Level::Error,
LogLevel::Warn => log::Level::Warn,
LogLevel::Info => log::Level::Info,
LogLevel::Debug => log::Level::Debug,
LogLevel::Trace => log::Level::Trace,
}
}
#[inline]
pub fn max() -> LogLevel {
LogLevel::Trace
}
#[inline]
pub fn to_log_level_filter(&self) -> LogLevelFilter {
LogLevelFilter::from_usize(*self as usize).unwrap()
}
}
#[repr(usize)]
#[derive(Copy, Eq, Debug)]
pub enum LogLevelFilter {
Off,
Error,
Warn,
Info,
Debug,
Trace,
}
impl Clone for LogLevelFilter {
#[inline]
fn clone(&self) -> LogLevelFilter {
*self
}
}
impl PartialEq for LogLevelFilter {
#[inline]
fn eq(&self, other: &LogLevelFilter) -> bool {
*self as usize == *other as usize
}
}
impl PartialEq<LogLevel> for LogLevelFilter {
#[inline]
fn eq(&self, other: &LogLevel) -> bool {
other.eq(self)
}
}
impl PartialOrd for LogLevelFilter {
#[inline]
fn partial_cmp(&self, other: &LogLevelFilter) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<LogLevel> for LogLevelFilter {
#[inline]
fn partial_cmp(&self, other: &LogLevel) -> Option<cmp::Ordering> {
other.partial_cmp(self).map(|x| x.reverse())
}
}
impl Ord for LogLevelFilter {
#[inline]
fn cmp(&self, other: &LogLevelFilter) -> cmp::Ordering {
(*self as usize).cmp(&(*other as usize))
}
}
impl FromStr for LogLevelFilter {
type Err = ();
fn from_str(level: &str) -> Result<LogLevelFilter, ()> {
ok_or(LOG_LEVEL_NAMES.iter()
.position(|&name| eq_ignore_ascii_case(name, level))
.map(|p| LogLevelFilter::from_usize(p).unwrap()), ())
}
}
impl fmt::Display for LogLevelFilter {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", LOG_LEVEL_NAMES[*self as usize])
}
}
impl LogLevelFilter {
fn from_usize(u: usize) -> Option<LogLevelFilter> {
match u {
0 => Some(LogLevelFilter::Off),
1 => Some(LogLevelFilter::Error),
2 => Some(LogLevelFilter::Warn),
3 => Some(LogLevelFilter::Info),
4 => Some(LogLevelFilter::Debug),
5 => Some(LogLevelFilter::Trace),
_ => None
}
}
fn from_new(filter: log::LevelFilter) -> LogLevelFilter {
match filter {
log::LevelFilter::Off => LogLevelFilter::Off,
log::LevelFilter::Error => LogLevelFilter::Error,
log::LevelFilter::Warn => LogLevelFilter::Warn,
log::LevelFilter::Info => LogLevelFilter::Info,
log::LevelFilter::Debug => LogLevelFilter::Debug,
log::LevelFilter::Trace => LogLevelFilter::Trace,
}
}
fn to_new(&self) -> log::LevelFilter {
match *self {
LogLevelFilter::Off => log::LevelFilter::Off,
LogLevelFilter::Error => log::LevelFilter::Error,
LogLevelFilter::Warn => log::LevelFilter::Warn,
LogLevelFilter::Info => log::LevelFilter::Info,
LogLevelFilter::Debug => log::LevelFilter::Debug,
LogLevelFilter::Trace => log::LevelFilter::Trace,
}
}
#[inline]
pub fn max() -> LogLevelFilter {
LogLevelFilter::Trace
}
#[inline]
pub fn to_log_level(&self) -> Option<LogLevel> {
LogLevel::from_usize(*self as usize)
}
}
pub struct LogRecord<'a> {
metadata: LogMetadata<'a>,
location: &'a LogLocation,
args: fmt::Arguments<'a>,
}
impl<'a> LogRecord<'a> {
pub fn args(&self) -> &fmt::Arguments<'a> {
&self.args
}
pub fn metadata(&self) -> &LogMetadata {
&self.metadata
}
pub fn location(&self) -> &LogLocation {
self.location
}
pub fn level(&self) -> LogLevel {
self.metadata.level()
}
pub fn target(&self) -> &str {
self.metadata.target()
}
}
pub struct LogMetadata<'a> {
level: LogLevel,
target: &'a str,
}
impl<'a> LogMetadata<'a> {
pub fn level(&self) -> LogLevel {
self.level
}
pub fn target(&self) -> &str {
self.target
}
}
pub trait Log: Sync+Send {
fn enabled(&self, metadata: &LogMetadata) -> bool;
fn log(&self, record: &LogRecord);
}
struct NopLogger;
impl Log for NopLogger {
fn enabled(&self, _: &LogMetadata) -> bool { false }
fn log(&self, _: &LogRecord) {}
}
#[derive(Copy, Clone, Debug)]
pub struct LogLocation {
#[doc(hidden)]
pub __module_path: &'static str,
#[doc(hidden)]
pub __file: &'static str,
#[doc(hidden)]
pub __line: u32,
}
impl LogLocation {
pub fn module_path(&self) -> &str {
self.__module_path
}
pub fn file(&self) -> &str {
self.__file
}
pub fn line(&self) -> u32 {
self.__line
}
}
pub struct MaxLogLevelFilter(());
impl fmt::Debug for MaxLogLevelFilter {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "MaxLogLevelFilter")
}
}
impl MaxLogLevelFilter {
pub fn get(&self) -> LogLevelFilter {
max_log_level()
}
pub fn set(&self, level: LogLevelFilter) {
log::set_max_level(level.to_new())
}
}
#[inline(always)]
pub fn max_log_level() -> LogLevelFilter {
LogLevelFilter::from_new(log::max_level())
}
#[cfg(feature = "use_std")]
pub fn set_logger<M>(make_logger: M) -> Result<(), SetLoggerError>
where M: FnOnce(MaxLogLevelFilter) -> Box<Log> {
unsafe { set_logger_raw(|max_level| mem::transmute(make_logger(max_level))) }
}
pub unsafe fn set_logger_raw<M>(make_logger: M) -> Result<(), SetLoggerError>
where M: FnOnce(MaxLogLevelFilter) -> *const Log {
static ADAPTOR: LoggerAdaptor = LoggerAdaptor;
match log::set_logger(&ADAPTOR) {
Ok(()) => {
LOGGER = make_logger(MaxLogLevelFilter(()));
STATE.store(INITIALIZED, Ordering::SeqCst);
Ok(())
}
Err(_) => Err(SetLoggerError(())),
}
}
#[cfg(feature = "use_std")]
pub fn shutdown_logger() -> Result<Box<Log>, ShutdownLoggerError> {
shutdown_logger_raw().map(|l| unsafe { mem::transmute(l) })
}
pub fn shutdown_logger_raw() -> Result<*const Log, ShutdownLoggerError> {
if STATE.compare_and_swap(INITIALIZED, INITIALIZING,
Ordering::SeqCst) != INITIALIZED {
return Err(ShutdownLoggerError(()));
}
while REFCOUNT.load(Ordering::SeqCst) != 0 {
}
unsafe {
let logger = LOGGER;
LOGGER = &NopLogger;
Ok(logger)
}
}
#[allow(missing_copy_implementations)]
#[derive(Debug)]
pub struct SetLoggerError(());
impl fmt::Display for SetLoggerError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "attempted to set a logger after the logging system \
was already initialized")
}
}
#[cfg(feature = "use_std")]
impl error::Error for SetLoggerError {
fn description(&self) -> &str { "set_logger() called multiple times" }
}
#[allow(missing_copy_implementations)]
#[derive(Debug)]
pub struct ShutdownLoggerError(());
impl fmt::Display for ShutdownLoggerError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "attempted to shut down the logger without an active logger")
}
}
#[cfg(feature = "use_std")]
impl error::Error for ShutdownLoggerError {
fn description(&self) -> &str { "shutdown_logger() called without an active logger" }
}
#[cfg(all(feature = "nightly", feature = "use_std"))]
pub fn log_panics() {
std::panic::set_hook(Box::new(panic::log));
}
#[cfg(all(feature = "nightly", feature = "use_std"))]
mod panic {
use std::panic::PanicInfo;
use std::thread;
pub fn log(info: &PanicInfo) {
let thread = thread::current();
let thread = thread.name().unwrap_or("<unnamed>");
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &s[..],
None => "Box<Any>",
}
};
match info.location() {
Some(location) => {
error!("thread '{}' panicked at '{}': {}:{}",
thread,
msg,
location.file(),
location.line())
}
None => error!("thread '{}' panicked at '{}'", thread, msg),
}
}
}
struct LoggerGuard(&'static Log);
impl Drop for LoggerGuard {
fn drop(&mut self) {
REFCOUNT.fetch_sub(1, Ordering::SeqCst);
}
}
impl Deref for LoggerGuard {
type Target = Log;
fn deref(&self) -> &(Log + 'static) {
self.0
}
}
fn logger() -> Option<LoggerGuard> {
REFCOUNT.fetch_add(1, Ordering::SeqCst);
if STATE.load(Ordering::SeqCst) != INITIALIZED {
REFCOUNT.fetch_sub(1, Ordering::SeqCst);
None
} else {
Some(LoggerGuard(unsafe { &*LOGGER }))
}
}
struct LoggerAdaptor;
impl log::Log for LoggerAdaptor {
fn log(&self, record: &log::Record) {
if let Some(logger) = logger() {
let record = LogRecord {
metadata: LogMetadata {
level: LogLevel::from_new(record.level()),
target: record.target(),
},
location: &LogLocation {
__file: "<unknown>",
__line: record.line().unwrap_or(0),
__module_path: "<unknown>",
},
args: *record.args(),
};
logger.log(&record);
}
}
fn enabled(&self, metadata: &log::Metadata) -> bool {
match logger() {
Some(logger) => {
let metadata = LogMetadata {
level: LogLevel::from_new(metadata.level()),
target: metadata.target(),
};
logger.enabled(&metadata)
}
None => false
}
}
fn flush(&self) {}
}
#[doc(hidden)]
pub fn __enabled(level: LogLevel, target: &str) -> bool {
match logger() {
Some(logger) => {
let metadata = LogMetadata {
level: level,
target: target,
};
logger.enabled(&metadata)
}
None => {
log::Log::enabled(
log::logger(),
&log::Metadata::builder()
.level(level.to_new())
.target(target)
.build()
)
}
}
}
#[doc(hidden)]
pub fn __log(level: LogLevel, target: &str, loc: &LogLocation,
args: fmt::Arguments) {
match logger() {
Some(logger) => {
let record = LogRecord {
metadata: LogMetadata {
level: level,
target: target,
},
location: loc,
args: args,
};
logger.log(&record);
}
None => {
log::Log::log(
log::logger(),
&log::Record::builder()
.level(level.to_new())
.target(target)
.file(Some(loc.__file))
.line(Some(loc.__line))
.module_path(Some(loc.__module_path))
.args(args)
.build()
)
}
}
}
#[inline(always)]
#[doc(hidden)]
pub fn __static_max_level() -> LogLevelFilter {
LogLevelFilter::from_new(log::STATIC_MAX_LEVEL)
}
#[cfg(test)]
mod tests {
extern crate std;
use tests::std::string::ToString;
use super::{LogLevel, LogLevelFilter};
#[test]
fn test_loglevelfilter_from_str() {
let tests = [
("off", Ok(LogLevelFilter::Off)),
("error", Ok(LogLevelFilter::Error)),
("warn", Ok(LogLevelFilter::Warn)),
("info", Ok(LogLevelFilter::Info)),
("debug", Ok(LogLevelFilter::Debug)),
("trace", Ok(LogLevelFilter::Trace)),
("OFF", Ok(LogLevelFilter::Off)),
("ERROR", Ok(LogLevelFilter::Error)),
("WARN", Ok(LogLevelFilter::Warn)),
("INFO", Ok(LogLevelFilter::Info)),
("DEBUG", Ok(LogLevelFilter::Debug)),
("TRACE", Ok(LogLevelFilter::Trace)),
("asdf", Err(())),
];
for &(s, ref expected) in &tests {
assert_eq!(expected, &s.parse());
}
}
#[test]
fn test_loglevel_from_str() {
let tests = [
("OFF", Err(())),
("error", Ok(LogLevel::Error)),
("warn", Ok(LogLevel::Warn)),
("info", Ok(LogLevel::Info)),
("debug", Ok(LogLevel::Debug)),
("trace", Ok(LogLevel::Trace)),
("ERROR", Ok(LogLevel::Error)),
("WARN", Ok(LogLevel::Warn)),
("INFO", Ok(LogLevel::Info)),
("DEBUG", Ok(LogLevel::Debug)),
("TRACE", Ok(LogLevel::Trace)),
("asdf", Err(())),
];
for &(s, ref expected) in &tests {
assert_eq!(expected, &s.parse());
}
}
#[test]
fn test_loglevel_show() {
assert_eq!("INFO", LogLevel::Info.to_string());
assert_eq!("ERROR", LogLevel::Error.to_string());
}
#[test]
fn test_loglevelfilter_show() {
assert_eq!("OFF", LogLevelFilter::Off.to_string());
assert_eq!("ERROR", LogLevelFilter::Error.to_string());
}
#[test]
fn test_cross_cmp() {
assert!(LogLevel::Debug > LogLevelFilter::Error);
assert!(LogLevelFilter::Warn < LogLevel::Trace);
assert!(LogLevelFilter::Off < LogLevel::Error);
}
#[test]
fn test_cross_eq() {
assert!(LogLevel::Error == LogLevelFilter::Error);
assert!(LogLevelFilter::Off != LogLevel::Error);
assert!(LogLevel::Trace == LogLevelFilter::Trace);
}
#[test]
fn test_to_log_level() {
assert_eq!(Some(LogLevel::Error), LogLevelFilter::Error.to_log_level());
assert_eq!(None, LogLevelFilter::Off.to_log_level());
assert_eq!(Some(LogLevel::Debug), LogLevelFilter::Debug.to_log_level());
}
#[test]
fn test_to_log_level_filter() {
assert_eq!(LogLevelFilter::Error, LogLevel::Error.to_log_level_filter());
assert_eq!(LogLevelFilter::Trace, LogLevel::Trace.to_log_level_filter());
}
#[test]
#[cfg(feature = "use_std")]
fn test_error_trait() {
use std::error::Error;
use super::SetLoggerError;
let e = SetLoggerError(());
assert_eq!(e.description(), "set_logger() called multiple times");
}
}