#![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://docs.rs/log/0.4.5"
)]
#![warn(missing_docs)]
#![deny(missing_debug_implementations)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(rustbuild, feature(staged_api, rustc_private))]
#![cfg_attr(rustbuild, unstable(feature = "rustc_private", issue = "27812"))]
#[cfg(not(feature = "std"))]
extern crate core as std;
#[macro_use]
extern crate cfg_if;
use std::cmp;
#[cfg(feature = "std")]
use std::error;
use std::fmt;
use std::mem;
use std::str::FromStr;
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
#[macro_use]
mod macros;
mod serde;
static mut LOGGER: &'static Log = &NopLogger;
static STATE: AtomicUsize = ATOMIC_USIZE_INIT;
const UNINITIALIZED: usize = 0;
const INITIALIZING: usize = 1;
const INITIALIZED: usize = 2;
static MAX_LOG_LEVEL_FILTER: AtomicUsize = ATOMIC_USIZE_INIT;
static LOG_LEVEL_NAMES: [&'static str; 6] = ["OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"];
static SET_LOGGER_ERROR: &'static str = "attempted to set a logger after the logging system \
was already initialized";
static LEVEL_PARSE_ERROR: &'static str =
"attempted to convert a string that doesn't match an existing log level";
#[repr(usize)]
#[derive(Copy, Eq, Debug, Hash)]
pub enum Level {
Error = 1, Warn,
Info,
Debug,
Trace,
}
impl Clone for Level {
#[inline]
fn clone(&self) -> Level {
*self
}
}
impl PartialEq for Level {
#[inline]
fn eq(&self, other: &Level) -> bool {
*self as usize == *other as usize
}
}
impl PartialEq<LevelFilter> for Level {
#[inline]
fn eq(&self, other: &LevelFilter) -> bool {
*self as usize == *other as usize
}
}
impl PartialOrd for Level {
#[inline]
fn partial_cmp(&self, other: &Level) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
#[inline]
fn lt(&self, other: &Level) -> bool {
(*self as usize) < *other as usize
}
#[inline]
fn le(&self, other: &Level) -> bool {
*self as usize <= *other as usize
}
#[inline]
fn gt(&self, other: &Level) -> bool {
*self as usize > *other as usize
}
#[inline]
fn ge(&self, other: &Level) -> bool {
*self as usize >= *other as usize
}
}
impl PartialOrd<LevelFilter> for Level {
#[inline]
fn partial_cmp(&self, other: &LevelFilter) -> Option<cmp::Ordering> {
Some((*self as usize).cmp(&(*other as usize)))
}
#[inline]
fn lt(&self, other: &LevelFilter) -> bool {
(*self as usize) < *other as usize
}
#[inline]
fn le(&self, other: &LevelFilter) -> bool {
*self as usize <= *other as usize
}
#[inline]
fn gt(&self, other: &LevelFilter) -> bool {
*self as usize > *other as usize
}
#[inline]
fn ge(&self, other: &LevelFilter) -> bool {
*self as usize >= *other as usize
}
}
impl Ord for Level {
#[inline]
fn cmp(&self, other: &Level) -> 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 Level {
type Err = ParseLevelError;
fn from_str(level: &str) -> Result<Level, Self::Err> {
ok_or(
LOG_LEVEL_NAMES
.iter()
.position(|&name| eq_ignore_ascii_case(name, level))
.into_iter()
.filter(|&idx| idx != 0)
.map(|idx| Level::from_usize(idx).unwrap())
.next(),
ParseLevelError(()),
)
}
}
impl fmt::Display for Level {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.pad(LOG_LEVEL_NAMES[*self as usize])
}
}
impl Level {
fn from_usize(u: usize) -> Option<Level> {
match u {
1 => Some(Level::Error),
2 => Some(Level::Warn),
3 => Some(Level::Info),
4 => Some(Level::Debug),
5 => Some(Level::Trace),
_ => None,
}
}
#[inline]
pub fn max() -> Level {
Level::Trace
}
#[inline]
pub fn to_level_filter(&self) -> LevelFilter {
LevelFilter::from_usize(*self as usize).unwrap()
}
}
#[repr(usize)]
#[derive(Copy, Eq, Debug, Hash)]
pub enum LevelFilter {
Off,
Error,
Warn,
Info,
Debug,
Trace,
}
impl Clone for LevelFilter {
#[inline]
fn clone(&self) -> LevelFilter {
*self
}
}
impl PartialEq for LevelFilter {
#[inline]
fn eq(&self, other: &LevelFilter) -> bool {
*self as usize == *other as usize
}
}
impl PartialEq<Level> for LevelFilter {
#[inline]
fn eq(&self, other: &Level) -> bool {
other.eq(self)
}
}
impl PartialOrd for LevelFilter {
#[inline]
fn partial_cmp(&self, other: &LevelFilter) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
#[inline]
fn lt(&self, other: &LevelFilter) -> bool {
(*self as usize) < *other as usize
}
#[inline]
fn le(&self, other: &LevelFilter) -> bool {
*self as usize <= *other as usize
}
#[inline]
fn gt(&self, other: &LevelFilter) -> bool {
*self as usize > *other as usize
}
#[inline]
fn ge(&self, other: &LevelFilter) -> bool {
*self as usize >= *other as usize
}
}
impl PartialOrd<Level> for LevelFilter {
#[inline]
fn partial_cmp(&self, other: &Level) -> Option<cmp::Ordering> {
Some((*self as usize).cmp(&(*other as usize)))
}
#[inline]
fn lt(&self, other: &Level) -> bool {
(*self as usize) < *other as usize
}
#[inline]
fn le(&self, other: &Level) -> bool {
*self as usize <= *other as usize
}
#[inline]
fn gt(&self, other: &Level) -> bool {
*self as usize > *other as usize
}
#[inline]
fn ge(&self, other: &Level) -> bool {
*self as usize >= *other as usize
}
}
impl Ord for LevelFilter {
#[inline]
fn cmp(&self, other: &LevelFilter) -> cmp::Ordering {
(*self as usize).cmp(&(*other as usize))
}
}
impl FromStr for LevelFilter {
type Err = ParseLevelError;
fn from_str(level: &str) -> Result<LevelFilter, Self::Err> {
ok_or(
LOG_LEVEL_NAMES
.iter()
.position(|&name| eq_ignore_ascii_case(name, level))
.map(|p| LevelFilter::from_usize(p).unwrap()),
ParseLevelError(()),
)
}
}
impl fmt::Display for LevelFilter {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", LOG_LEVEL_NAMES[*self as usize])
}
}
impl LevelFilter {
fn from_usize(u: usize) -> Option<LevelFilter> {
match u {
0 => Some(LevelFilter::Off),
1 => Some(LevelFilter::Error),
2 => Some(LevelFilter::Warn),
3 => Some(LevelFilter::Info),
4 => Some(LevelFilter::Debug),
5 => Some(LevelFilter::Trace),
_ => None,
}
}
#[inline]
pub fn max() -> LevelFilter {
LevelFilter::Trace
}
#[inline]
pub fn to_level(&self) -> Option<Level> {
Level::from_usize(*self as usize)
}
}
#[derive(Clone, Debug)]
pub struct Record<'a> {
metadata: Metadata<'a>,
args: fmt::Arguments<'a>,
module_path: Option<&'a str>,
file: Option<&'a str>,
line: Option<u32>,
}
impl<'a> Record<'a> {
#[inline]
pub fn builder() -> RecordBuilder<'a> {
RecordBuilder::new()
}
#[inline]
pub fn args(&self) -> &fmt::Arguments<'a> {
&self.args
}
#[inline]
pub fn metadata(&self) -> &Metadata<'a> {
&self.metadata
}
#[inline]
pub fn level(&self) -> Level {
self.metadata.level()
}
#[inline]
pub fn target(&self) -> &'a str {
self.metadata.target()
}
#[inline]
pub fn module_path(&self) -> Option<&'a str> {
self.module_path
}
#[inline]
pub fn file(&self) -> Option<&'a str> {
self.file
}
#[inline]
pub fn line(&self) -> Option<u32> {
self.line
}
}
#[derive(Debug)]
pub struct RecordBuilder<'a> {
record: Record<'a>,
}
impl<'a> RecordBuilder<'a> {
#[inline]
pub fn new() -> RecordBuilder<'a> {
RecordBuilder {
record: Record {
args: format_args!(""),
metadata: Metadata::builder().build(),
module_path: None,
file: None,
line: None,
},
}
}
#[inline]
pub fn args(&mut self, args: fmt::Arguments<'a>) -> &mut RecordBuilder<'a> {
self.record.args = args;
self
}
#[inline]
pub fn metadata(&mut self, metadata: Metadata<'a>) -> &mut RecordBuilder<'a> {
self.record.metadata = metadata;
self
}
#[inline]
pub fn level(&mut self, level: Level) -> &mut RecordBuilder<'a> {
self.record.metadata.level = level;
self
}
#[inline]
pub fn target(&mut self, target: &'a str) -> &mut RecordBuilder<'a> {
self.record.metadata.target = target;
self
}
#[inline]
pub fn module_path(&mut self, path: Option<&'a str>) -> &mut RecordBuilder<'a> {
self.record.module_path = path;
self
}
#[inline]
pub fn file(&mut self, file: Option<&'a str>) -> &mut RecordBuilder<'a> {
self.record.file = file;
self
}
#[inline]
pub fn line(&mut self, line: Option<u32>) -> &mut RecordBuilder<'a> {
self.record.line = line;
self
}
#[inline]
pub fn build(&self) -> Record<'a> {
self.record.clone()
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Metadata<'a> {
level: Level,
target: &'a str,
}
impl<'a> Metadata<'a> {
#[inline]
pub fn builder() -> MetadataBuilder<'a> {
MetadataBuilder::new()
}
#[inline]
pub fn level(&self) -> Level {
self.level
}
#[inline]
pub fn target(&self) -> &'a str {
self.target
}
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct MetadataBuilder<'a> {
metadata: Metadata<'a>,
}
impl<'a> MetadataBuilder<'a> {
#[inline]
pub fn new() -> MetadataBuilder<'a> {
MetadataBuilder {
metadata: Metadata {
level: Level::Info,
target: "",
},
}
}
#[inline]
pub fn level(&mut self, arg: Level) -> &mut MetadataBuilder<'a> {
self.metadata.level = arg;
self
}
#[inline]
pub fn target(&mut self, target: &'a str) -> &mut MetadataBuilder<'a> {
self.metadata.target = target;
self
}
#[inline]
pub fn build(&self) -> Metadata<'a> {
self.metadata.clone()
}
}
pub trait Log: Sync + Send {
fn enabled(&self, metadata: &Metadata) -> bool;
fn log(&self, record: &Record);
fn flush(&self);
}
struct NopLogger;
impl Log for NopLogger {
fn enabled(&self, _: &Metadata) -> bool {
false
}
fn log(&self, _: &Record) {}
fn flush(&self) {}
}
#[inline]
pub fn set_max_level(level: LevelFilter) {
MAX_LOG_LEVEL_FILTER.store(level as usize, Ordering::SeqCst)
}
#[inline(always)]
pub fn max_level() -> LevelFilter {
unsafe { mem::transmute(MAX_LOG_LEVEL_FILTER.load(Ordering::Relaxed)) }
}
#[cfg(feature = "std")]
pub fn set_boxed_logger(logger: Box<Log>) -> Result<(), SetLoggerError> {
set_logger_inner(|| unsafe { &*Box::into_raw(logger) })
}
pub fn set_logger(logger: &'static Log) -> Result<(), SetLoggerError> {
set_logger_inner(|| logger)
}
fn set_logger_inner<F>(make_logger: F) -> Result<(), SetLoggerError>
where
F: FnOnce() -> &'static Log,
{
unsafe {
match STATE.compare_and_swap(UNINITIALIZED, INITIALIZING, Ordering::SeqCst) {
UNINITIALIZED => {
LOGGER = make_logger();
STATE.store(INITIALIZED, Ordering::SeqCst);
Ok(())
}
INITIALIZING => {
while STATE.load(Ordering::SeqCst) == INITIALIZING {}
Err(SetLoggerError(()))
}
_ => Err(SetLoggerError(())),
}
}
}
#[allow(missing_copy_implementations)]
#[derive(Debug)]
pub struct SetLoggerError(());
impl fmt::Display for SetLoggerError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(SET_LOGGER_ERROR)
}
}
#[cfg(feature = "std")]
impl error::Error for SetLoggerError {
fn description(&self) -> &str {
SET_LOGGER_ERROR
}
}
#[allow(missing_copy_implementations)]
#[derive(Debug, PartialEq)]
pub struct ParseLevelError(());
impl fmt::Display for ParseLevelError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(LEVEL_PARSE_ERROR)
}
}
#[cfg(feature = "std")]
impl error::Error for ParseLevelError {
fn description(&self) -> &str {
LEVEL_PARSE_ERROR
}
}
pub fn logger() -> &'static Log {
unsafe {
if STATE.load(Ordering::SeqCst) != INITIALIZED {
static NOP: NopLogger = NopLogger;
&NOP
} else {
LOGGER
}
}
}
#[doc(hidden)]
pub fn __private_api_log(
args: fmt::Arguments,
level: Level,
&(target, module_path, file, line): &(&str, &str, &str, u32),
) {
logger().log(
&Record::builder()
.args(args)
.level(level)
.target(target)
.module_path(Some(module_path))
.file(Some(file))
.line(Some(line))
.build(),
);
}
#[doc(hidden)]
pub fn __private_api_enabled(level: Level, target: &str) -> bool {
logger().enabled(&Metadata::builder().level(level).target(target).build())
}
pub const STATIC_MAX_LEVEL: LevelFilter = MAX_LEVEL_INNER;
cfg_if! {
if #[cfg(all(not(debug_assertions), feature = "release_max_level_off"))] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Off;
} else if #[cfg(all(not(debug_assertions), feature = "release_max_level_error"))] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Error;
} else if #[cfg(all(not(debug_assertions), feature = "release_max_level_warn"))] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Warn;
} else if #[cfg(all(not(debug_assertions), feature = "release_max_level_info"))] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Info;
} else if #[cfg(all(not(debug_assertions), feature = "release_max_level_debug"))] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Debug;
} else if #[cfg(all(not(debug_assertions), feature = "release_max_level_trace"))] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Trace;
} else if #[cfg(feature = "max_level_off")] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Off;
} else if #[cfg(feature = "max_level_error")] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Error;
} else if #[cfg(feature = "max_level_warn")] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Warn;
} else if #[cfg(feature = "max_level_info")] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Info;
} else if #[cfg(feature = "max_level_debug")] {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Debug;
} else {
const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Trace;
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::{Level, LevelFilter, ParseLevelError};
use tests::std::string::ToString;
#[test]
fn test_levelfilter_from_str() {
let tests = [
("off", Ok(LevelFilter::Off)),
("error", Ok(LevelFilter::Error)),
("warn", Ok(LevelFilter::Warn)),
("info", Ok(LevelFilter::Info)),
("debug", Ok(LevelFilter::Debug)),
("trace", Ok(LevelFilter::Trace)),
("OFF", Ok(LevelFilter::Off)),
("ERROR", Ok(LevelFilter::Error)),
("WARN", Ok(LevelFilter::Warn)),
("INFO", Ok(LevelFilter::Info)),
("DEBUG", Ok(LevelFilter::Debug)),
("TRACE", Ok(LevelFilter::Trace)),
("asdf", Err(ParseLevelError(()))),
];
for &(s, ref expected) in &tests {
assert_eq!(expected, &s.parse());
}
}
#[test]
fn test_level_from_str() {
let tests = [
("OFF", Err(ParseLevelError(()))),
("error", Ok(Level::Error)),
("warn", Ok(Level::Warn)),
("info", Ok(Level::Info)),
("debug", Ok(Level::Debug)),
("trace", Ok(Level::Trace)),
("ERROR", Ok(Level::Error)),
("WARN", Ok(Level::Warn)),
("INFO", Ok(Level::Info)),
("DEBUG", Ok(Level::Debug)),
("TRACE", Ok(Level::Trace)),
("asdf", Err(ParseLevelError(()))),
];
for &(s, ref expected) in &tests {
assert_eq!(expected, &s.parse());
}
}
#[test]
fn test_level_show() {
assert_eq!("INFO", Level::Info.to_string());
assert_eq!("ERROR", Level::Error.to_string());
}
#[test]
fn test_levelfilter_show() {
assert_eq!("OFF", LevelFilter::Off.to_string());
assert_eq!("ERROR", LevelFilter::Error.to_string());
}
#[test]
fn test_cross_cmp() {
assert!(Level::Debug > LevelFilter::Error);
assert!(LevelFilter::Warn < Level::Trace);
assert!(LevelFilter::Off < Level::Error);
}
#[test]
fn test_cross_eq() {
assert!(Level::Error == LevelFilter::Error);
assert!(LevelFilter::Off != Level::Error);
assert!(Level::Trace == LevelFilter::Trace);
}
#[test]
fn test_to_level() {
assert_eq!(Some(Level::Error), LevelFilter::Error.to_level());
assert_eq!(None, LevelFilter::Off.to_level());
assert_eq!(Some(Level::Debug), LevelFilter::Debug.to_level());
}
#[test]
fn test_to_level_filter() {
assert_eq!(LevelFilter::Error, Level::Error.to_level_filter());
assert_eq!(LevelFilter::Trace, Level::Trace.to_level_filter());
}
#[test]
#[cfg(feature = "std")]
fn test_error_trait() {
use super::SetLoggerError;
use std::error::Error;
let e = SetLoggerError(());
assert_eq!(
e.description(),
"attempted to set a logger after the logging system \
was already initialized"
);
}
#[test]
fn test_metadata_builder() {
use super::MetadataBuilder;
let target = "myApp";
let metadata_test = MetadataBuilder::new()
.level(Level::Debug)
.target(target)
.build();
assert_eq!(metadata_test.level(), Level::Debug);
assert_eq!(metadata_test.target(), "myApp");
}
#[test]
fn test_metadata_convenience_builder() {
use super::Metadata;
let target = "myApp";
let metadata_test = Metadata::builder()
.level(Level::Debug)
.target(target)
.build();
assert_eq!(metadata_test.level(), Level::Debug);
assert_eq!(metadata_test.target(), "myApp");
}
#[test]
fn test_record_builder() {
use super::{MetadataBuilder, RecordBuilder};
let target = "myApp";
let metadata = MetadataBuilder::new().target(target).build();
let fmt_args = format_args!("hello");
let record_test = RecordBuilder::new()
.args(fmt_args)
.metadata(metadata)
.module_path(Some("foo"))
.file(Some("bar"))
.line(Some(30))
.build();
assert_eq!(record_test.metadata().target(), "myApp");
assert_eq!(record_test.module_path(), Some("foo"));
assert_eq!(record_test.file(), Some("bar"));
assert_eq!(record_test.line(), Some(30));
}
#[test]
fn test_record_convenience_builder() {
use super::{Metadata, Record};
let target = "myApp";
let metadata = Metadata::builder().target(target).build();
let fmt_args = format_args!("hello");
let record_test = Record::builder()
.args(fmt_args)
.metadata(metadata)
.module_path(Some("foo"))
.file(Some("bar"))
.line(Some(30))
.build();
assert_eq!(record_test.target(), "myApp");
assert_eq!(record_test.module_path(), Some("foo"));
assert_eq!(record_test.file(), Some("bar"));
assert_eq!(record_test.line(), Some(30));
}
#[test]
fn test_record_complete_builder() {
use super::{Level, Record};
let target = "myApp";
let record_test = Record::builder()
.module_path(Some("foo"))
.file(Some("bar"))
.line(Some(30))
.target(target)
.level(Level::Error)
.build();
assert_eq!(record_test.target(), "myApp");
assert_eq!(record_test.level(), Level::Error);
assert_eq!(record_test.module_path(), Some("foo"));
assert_eq!(record_test.file(), Some("bar"));
assert_eq!(record_test.line(), Some(30));
}
}