The Logger system can now be expanded with external logging libraries.

While external loggers can be used, if you are compiling with
--features use_std, then it will default to just printing everything
to the stdout unless a logger is created.

This library can be used without the STD library, however, you will have
to write an external logger.
This commit is contained in:
Myrddin Dundragon 2016-08-24 11:41:48 -04:00
parent c655eca358
commit 13a2cee826
14 changed files with 884 additions and 56 deletions

2
Cargo.lock generated
View File

@ -1,4 +1,4 @@
[root]
name = "scribe"
version = "0.1.0"
version = "0.2.0"

View File

@ -1,9 +1,13 @@
[package]
name = "scribe"
version = "0.1.0"
version = "0.5.0"
authors = ["Jason Travis Smith <Jason@CyberMagesLLC.com>"]
description = "Handles logging."
license = ""
repository = "https://gitlab.com/CyberMages/scribe.git"
documentation = ""
keywords = ["scribe", "logging"]
[features]
default = []
use_std = []

110
examples/logger.rs Normal file
View File

@ -0,0 +1,110 @@
#[macro_use]
extern crate scribe;
use scribe::{Levels, Logger, Record};
pub struct TestLogger
{
}
impl TestLogger
{
pub fn start()
{
match scribe::set_logger(TestLogger::set_logger)
{
Ok(_) =>
{
println!("Logger sucessfully set.");
}
Err(error) =>
{
println!("ERROR: {}", error);
}
}
}
pub fn stop()
{
match scribe::clear_logger()
{
Ok(_) =>
{
println!("Logger cleared.");
}
Err(error) =>
{
println!("ERROR: {}", error);
}
}
}
fn set_logger() -> Box<Logger>
{
let logger: TestLogger;
logger =
TestLogger
{
};
Box::new(logger)
}
}
impl Logger for TestLogger
{
fn is_domain_enabled(&self, domain_name: &'static str) -> bool
{
println!("Domain '{}': {}", domain_name, true);
true
}
fn is_level_enabled(&self, lvl: Levels) -> bool
{
println!("Level '{}': {}", lvl, true);
true
}
fn log(&self, record: &Record)
{
println!("Record: {}", record);
}
}
mod temp_mod
{
pub fn print()
{
info!("Mod test.");
}
}
pub fn main()
{
TestLogger::start();
temp_mod::print();
debug!("This seemed to work alright.");
debug!(domain: "Temp Domain", "This seemed to work alright.");
info!("Jason is awesome.");
info!(domain: "Temp Domain", "Jason is awesome.");
warn!("Danger Will Robinson, danger!");
warn!(domain: "Temp Domain", "Danger Will Robinson, danger!");
//error!("There was an error!");
//error!(domain: "Temp Domain", "There was an error!");
TestLogger::stop();
}

View File

@ -6,7 +6,14 @@ extern crate scribe;
pub fn main()
{
debug!("This seemed to work alright.");
debug!(domain: "Temp Domain", "This seemed to work alright.");
info!("Jason is awesome.");
info!(domain: "Temp Domain", "Jason is awesome.");
warn!("Danger Will Robinson, danger!");
error!("There was an error!");
warn!(domain: "Temp Domain", "Danger Will Robinson, danger!");
//error!("There was an error!");
//error!(domain: "Temp Domain", "There was an error!");
}

54
src/faux_logger.rs Normal file
View File

@ -0,0 +1,54 @@
use ::levels::Levels;
use ::logger::Logger;
use ::record::Record;
/// This is a fake Logger.
///
/// No domains or severity Levels are enabled,
/// and all logging calls are ignored.
pub struct FauxLogger;
// The fake Logger will do nothing if this is built
// to just use the core library.
#[cfg(not(feature="use_std"))]
impl Logger for FauxLogger
{
fn is_domain_enabled(&self, _domain_name: &'static str) -> bool
{
false
}
fn is_level_enabled(&self, _lvl: Levels) -> bool
{
false
}
fn log(&self, _record: &Record)
{
}
}
// When built using the STD library, the fake Logger will
// act as a simple console printer. This is to make development easier.
#[cfg(feature="use_std")]
impl Logger for FauxLogger
{
fn is_domain_enabled(&self, _domain_name: &'static str) -> bool
{
true
}
fn is_level_enabled(&self, _lvl: Levels) -> bool
{
true
}
fn log(&self, record: &Record)
{
println!("{}", record);
}
}

66
src/guard.rs Normal file
View File

@ -0,0 +1,66 @@
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
use ::logger::Logger;
/// The amount of references that have currently been created.
static REF_COUNT: AtomicUsize = ATOMIC_USIZE_INIT;
/// This is an access guard for the Logger.
/// It helps keep track of REF_COUNTS. Since there can be
/// only one GLOBAL_LOGGER that needs guarded, this is done
/// with static variables.
pub struct Guard
{
logger: &'static Logger
}
impl Guard
{
/// Create a new reference counted Guard.
pub fn new(global_logger: *const Logger) -> Guard
{
REF_COUNT.fetch_add(1, Ordering::SeqCst);
unsafe
{
Guard
{
logger: &*global_logger
}
}
}
/// Do a spin lock while waiting for the desired REF_COUNT to be hit.
/// Normally, this is waiting for REF_COUNT to be zero.
pub fn spin_until(desired_refcount: usize)
{
while REF_COUNT.load(Ordering::SeqCst) != desired_refcount
{
// It would be nice to sleep this thread here,
// but I'm not sure how to do that without the STD.
}
}
}
impl Drop for Guard
{
fn drop(&mut self)
{
REF_COUNT.fetch_sub(1, Ordering::SeqCst);
}
}
impl Deref for Guard
{
type Target = Logger;
fn deref(&self) -> &(Logger + 'static)
{
self.logger
}
}

View File

@ -3,8 +3,9 @@ use std::fmt::Display;
/// The different available logging levels.
#[repr(u8)]
#[derive(Clone, Copy, Eq, Debug)]
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub enum LogLevel
{
/// Only errors will be written to the logs.

90
src/levels.rs Normal file
View File

@ -0,0 +1,90 @@
/// The different available logging levels.
// These only need to be represented as an unsigned
// integer as there won't be that many of these.
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub enum Levels
{
/// Designates a serious, program
/// stopping, error.
Error,
/// Designates a problem, but it is not
/// something that will stop the program
/// from running.
Warn,
/// Designates useful information.
Info,
/// Designates debugging only information.
Debug
}
impl Levels
{
/// Get a str representation of this variant.
pub fn to_str(&self) -> &'static str
{
match *self
{
Levels::Error => "Error",
Levels::Warn => "Warn",
Levels::Info => "Info",
Levels::Debug => "Debug"
}
}
/// Get the byte(u8) value of this variant.
pub fn get_val(&self) -> u8
{
match *self
{
Levels::Error => 0,
Levels::Warn => 1,
Levels::Info => 2,
Levels::Debug => 3
}
}
}
impl ::std::convert::From<u8> for Levels
{
fn from(lvl: u8) -> Levels
{
match lvl
{
0 => Levels::Error,
1 => Levels::Warn,
2 => Levels::Info,
3 => Levels::Debug,
_ => Levels::Debug
}
}
}
impl ::std::convert::From<Levels> for u8
{
fn from(lvl: Levels) -> u8
{
lvl.get_val()
}
}
impl ::std::fmt::Debug for Levels
{
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result
{
write!(f, "{}", self.to_str())
}
}
impl ::std::fmt::Display for Levels
{
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result
{
write!(f, "{}", self.to_str())
}
}

View File

@ -1,12 +1,210 @@
///
///
///
//! This is the logging system used by different CyberMages projects.
//! This is meant to work with both std and non std Rust programs.
// Handle using the core or the std of Rust depending on the chosen feature.
#![cfg_attr(not(feature="use_std"), no_std)]
// This crate can only use parts of Rust that are in both the core
// and the standard library. This way the logging system will work for
// libraries that use either.
//
// This is handled by coding using the std library and referencing the core
// library as the std library if the core library is desired.
#[cfg(not(feature="use_std"))]
extern crate core as std;
// Define the modules that are a
// part of this library.
mod log_level;
mod guard;
mod levels;
mod location;
mod logger;
mod logger_states;
mod macros;
mod faux_logger;
mod record;
pub use self::log_level::LogLevel;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
use self::faux_logger::FauxLogger;
use self::guard::Guard;
use self::logger_states::LoggerStates;
pub use self::levels::Levels;
pub use self::location::Location;
pub use self::logger::Logger;
pub use self::record::Record;
/// This is the currently set global Logger.
/// It will default to a fake Logger that does nothing.
static mut GLOBAL_LOGGER: *const Logger = &FauxLogger;
/// The STATE of the GLOBAL_LOGGER.
static STATE: AtomicUsize = ATOMIC_USIZE_INIT;
/// Handles setting the GLOBAL_LOGGER and getting
/// it ready to log new Records.
fn set_logger_base<F>(create_logger: F)
-> Result<(), u8>
where F: FnOnce() -> *const Logger
{
if STATE.compare_and_swap(LoggerStates::Uninitialized.into(),
LoggerStates::Initializing.into(),
Ordering::SeqCst) !=
LoggerStates::Uninitialized.into()
{
return Err(1);
}
unsafe
{
GLOBAL_LOGGER = create_logger();
}
STATE.store(LoggerStates::Initialized.into(), Ordering::SeqCst);
Ok(())
}
/// Handles clearing the GLOBAL_LOGGER.
/// This will return a pointer to the Logger that
/// was previously set.
fn clear_logger_base() -> Result<*const Logger, u8>
{
let logger: *const Logger;
// Set to INITIALIZING to prevent re-initialization after
if STATE.compare_and_swap(LoggerStates::Initialized.into(),
LoggerStates::Initializing.into(),
Ordering::SeqCst) !=
LoggerStates::Initialized.into()
{
return Err(2);
}
// Wait until there are no references alive to the
// current GLOBAL_LOGGER.
Guard::spin_until(0);
// Change the GLOBAL_LOGGER to the fake one to unset it.
unsafe
{
logger = GLOBAL_LOGGER;
GLOBAL_LOGGER = &FauxLogger;
Ok(logger)
}
}
/// Get the current logger.
///
/// This should be the FauxLogger if no logger has been set,
/// or whatever logger has been set. If a logger has been
/// cleared then this will return None.
fn get_logger() -> Option<Guard>
{
let guard: Guard;
if STATE.load(Ordering::SeqCst) != LoggerStates::Initializing.into()
{
None
}
else
{
unsafe
{
guard = Guard::new(GLOBAL_LOGGER);
}
Some(guard)
}
}
/// Set the Logger to use for logging.
#[cfg(not(feature="use_std"))]
pub fn set_logger<F>(create_logger: F)
-> Result<(), u8>
where F: FnOnce() -> *const Logger
{
set_logger_base(create_logger)
}
/// Clear the logger currently being used.
///
/// This will return a pointer to the Logger that
/// was previously set.
#[cfg(not(feature="use_std"))]
pub fn clear_logger() -> Result<*const Logger, u8>
{
clear_logger_base()
}
/// Set the Logger to use for logging.
#[cfg(feature="use_std")]
pub fn set_logger<F>(create_logger: F)
-> Result<(), u8>
where F: FnOnce() -> Box<Logger>
{
// I hate using closures, but it is the only way I
// can think of to handle this right now.
unsafe
{
set_logger_base(|| ::std::mem::transmute(create_logger()))
}
}
/// Clear the logger currently being used.
///
/// This will return a pointer to the Logger that
/// was previously set.
#[cfg(feature="use_std")]
pub fn clear_logger() -> Result<Box<Logger>, u8>
{
match clear_logger_base()
{
Ok(logger) =>
{
unsafe
{
Ok(::std::mem::transmute(logger))
}
}
Err(error) =>
{
Err(error)
}
}
}
/// Do NOT use outside of this crate.
///
/// This records a log message.
#[doc(hidden)]
pub fn log_record(domain: &str, lvl: Levels, module_path: &str,
file_name: &str, line_num: u32,
message: ::std::fmt::Arguments)
{
let mut record: Record;
match get_logger()
{
Some(logger) =>
{
record = Record::new(domain, lvl, message);
record.set_location(module_path, file_name, line_num);
logger.log(&record)
}
None =>
{
}
}
}

69
src/location.rs Normal file
View File

@ -0,0 +1,69 @@
/// The Location where a log message was generated.
pub struct Location<'a>
{
/// The module path where the message was genereated.
/// <crate_name>[::<module>]*
module_path: &'a str,
/// The name of the file where the message was generated.
file_name: &'a str,
/// The line of the file where the message was generated.
line: u32
}
impl<'a> Location<'a>
{
/// Create a new Location.
///
/// This can easily be done using 'module_path!', 'file!',
/// 'line!'.
pub fn new(module: &'a str, file: &'a str, line_num: u32)
-> Location<'a>
{
Location
{
module_path: module,
file_name: file,
line: line_num
}
}
/// Get the module path where the message was generated.
pub fn get_module_path(&self) -> &str
{
self.module_path
}
/// Get the name of the file where the message was generated.
pub fn get_file_name(&self) -> &str
{
self.file_name
}
/// Get the line number of the file where the
/// message was generated.
pub fn get_line_number(&self) -> u32
{
self.line
}
}
impl<'a> ::std::fmt::Debug for Location<'a>
{
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result
{
write!(f, "{}", self)
}
}
impl<'a> ::std::fmt::Display for Location<'a>
{
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result
{
write!(f, "{}::{}::{}", self.get_module_path(),
self.get_file_name(), self.get_line_number())
}
}

17
src/logger.rs Normal file
View File

@ -0,0 +1,17 @@
use ::levels::Levels;
use ::record::Record;
/// Defines the minimum requirements for a Logger.
pub trait Logger: Send + Sync
{
/// Query if the given domain has logging enabled or disabled.
fn is_domain_enabled(&self, domain_name: &'static str) -> bool;
/// Query if the given severity Level has logging enabled or disabled.
fn is_level_enabled(&self, lvl: Levels) -> bool;
/// Log the given Record.
fn log(&self, record: &Record);
}

79
src/logger_states.rs Normal file
View File

@ -0,0 +1,79 @@
/// The different states the the GLOBAL_LOGGER can be in.
#[repr(usize)]
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub enum LoggerStates
{
/// The GLOBAL_LOGGER has not been initialized yet.
Uninitialized,
/// The GLOBAL_LOGGER is currently being initialized.
Initializing,
/// The GLOBAL_LOGGER is initialized and ready to be used.
Initialized
}
impl LoggerStates
{
/// Get a str representation of this variant.
pub fn to_str(&self) -> &'static str
{
match *self
{
LoggerStates::Uninitialized => "Uninitialized",
LoggerStates::Initializing => "Initializing",
LoggerStates::Initialized => "Initialized"
}
}
/// Get the value of this variant.
pub fn get_val(&self) -> usize
{
match *self
{
LoggerStates::Uninitialized => 0,
LoggerStates::Initializing => 1,
LoggerStates::Initialized => 2
}
}
}
impl ::std::convert::From<usize> for LoggerStates
{
fn from(lvl: usize) -> LoggerStates
{
match lvl
{
0 => LoggerStates::Uninitialized,
1 => LoggerStates::Initializing,
2 => LoggerStates::Initialized,
_ => LoggerStates::Uninitialized
}
}
}
impl ::std::convert::From<LoggerStates> for usize
{
fn from(state: LoggerStates) -> usize
{
state.get_val()
}
}
impl ::std::fmt::Debug for LoggerStates
{
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result
{
write!(f, "{}", self.to_str())
}
}
impl ::std::fmt::Display for LoggerStates
{
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result
{
write!(f, "{}", self.to_str())
}
}

View File

@ -1,103 +1,130 @@
/// This will force a panic to occur with the given arguments.
/// This is mostly used from the error! macro.
#[doc(hidden)]
#[macro_export]
macro_rules! generate_panic
{
($($arg: tt)+) =>
({
use std::fmt;
panic!(fmt::format(format_args!($($arg)+)));
panic!(::std::fmt::format(format_args!($($arg)+)));
})
}
/// This handles turning a module path into a domain name.
#[doc(hidden)]
#[macro_export]
macro_rules! determine_domain
{
() =>
({
match module_path!().find(":")
{
Some(pos) =>
{
&module_path!()[0..pos]
}
None =>
{
module_path!()
}
}
});
}
/// This handles calling the actual logging function.
#[doc(hidden)]
#[macro_export]
macro_rules! log
{
(domain: $domain_name: expr, $log_level: expr, $($arg: tt)+) =>
({
$crate::log_record($domain_name, $log_level, module_path!(),
file!(), line!(), format_args!($($arg)+));
});
($log_level: expr, $($arg: tt)+) =>
({
// Specifically use the Path from std in this block.
use std::path::Path;
let file_path: &Path;
let mut file_name: &str;
file_name = file!();
file_path = Path::new(file!());
match file_path.file_name()
{
Some(name) =>
{
match name.to_str()
{
Some(string) =>
{
file_name = string;
}
None =>
{
}
}
}
None =>
{
}
}
println!("{0}: {1}::{2}::{3} -- {4}", $log_level,
module_path!(), file_name, line!(),
format_args!($($arg)+));
})
log!(domain: determine_domain!(), $log_level, $($arg)+);
});
}
/// Logs a message at the Error severity Level.
///
/// This is for a serious, program stopping, problem.
#[macro_export]
macro_rules! error
{
(domain: $domain_name: expr, $($arg: tt)*) =>
({
log!(domain: $domain_name, $crate::Levels::Error, $($arg)*);
generate_panic!($($arg)*);
});
($($arg: tt)*) =>
({
use scribe::LogLevel;
log!(LogLevel::ERROR, $($arg)*);
log!($crate::Levels::Error, $($arg)*);
generate_panic!($($arg)*);
})
});
}
/// Logs a message at the Warn severity Level.
///
/// This is for a problem that will not stop the
/// program from running.
#[macro_export]
macro_rules! warn
{
(domain: $domain_name: expr, $($arg: tt)*) =>
({
log!(domain: $domain_name, $crate::Levels::Warn, $($arg)*);
});
($($arg: tt)*) =>
({
use scribe::LogLevel;
log!(LogLevel::WARN, $($arg)*);
})
log!($crate::Levels::Warn, $($arg)*);
});
}
/// Logs a message at the Info severity Level.
///
/// This is for general useful information.
#[macro_export]
macro_rules! info
{
(domain: $domain_name: expr, $($arg: tt)*) =>
({
log!(domain: $domain_name, $crate::Levels::Info, $($arg)*);
});
($($arg: tt)*) =>
({
use scribe::LogLevel;
log!(LogLevel::INFO, $($arg)*);
log!($crate::Levels::Info, $($arg)*);
})
}
/// Logs a message at the Debug severity Level.
///
/// This is for debugging only information.
///
/// Logging at this level is disabled when in release mode.
#[macro_export]
macro_rules! debug
{
(domain: $domain_name: expr, $($arg: tt)*) =>
({
if cfg!(debug_assertions) == true
{
log!(domain: $domain_name, $crate::Levels::Debug, $($arg)*);
}
});
($($arg: tt)*) =>
({
use scribe::LogLevel;
// Only log this if we are currently in a debug build.
if cfg!(debug_assertions) == true
{
log!(LogLevel::DEBUG, $($arg)*);
log!($crate::Levels::Debug, $($arg)*);
}
})
});
}

106
src/record.rs Normal file
View File

@ -0,0 +1,106 @@
use ::levels::Levels;
use ::location::Location;
/// A single message to be logged.
pub struct Record<'a>
{
/// The domain that the message is supposed to be
/// logged to.
domain: &'a str,
/// The severity Level for this message.
level: Levels,
/// The Location that the message was generated at.
location: Location<'a>,
/// The message to be logged.
message: ::std::fmt::Arguments<'a>
}
impl<'a> Record<'a>
{
/// Create a new Record.
pub fn new(domain_name: &'a str, lvl: Levels,
message_data: ::std::fmt::Arguments<'a>)
-> Record<'a>
{
Record
{
domain: domain_name,
level: lvl,
location: Location::new("", "", 0),
message: message_data
}
}
/// Get the domain of this Record.
pub fn get_domain(&self) -> &str
{
self.domain
}
/// Get the Level of this Record.
pub fn get_level(&self) -> Levels
{
self.level
}
/// Get the Location where this Record was generated.
pub fn get_location(&self) -> &Location
{
&self.location
}
/// Get the message that this Record holds.
pub fn get_message(&self) -> ::std::fmt::Arguments<'a>
{
self.message
}
/// Set the domain that this Record is to be logged to.
pub fn set_domain(&mut self, domain_name: &'a str)
{
self.domain = domain_name;
}
/// Set the severity Level of the Record.
pub fn set_level(&mut self, lvl: Levels)
{
self.level = lvl;
}
/// Set the Location where this Record was generated.
pub fn set_location(&mut self, module: &'a str, file: &'a str,
line: u32)
{
self.location = Location::new(module, file, line);
}
/// Set the message for this Record.
pub fn set_message(&mut self, message_data: ::std::fmt::Arguments<'a>)
{
self.message = message_data;
}
}
impl<'a> ::std::fmt::Debug for Record<'a>
{
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result
{
write!(f, "{}", self)
}
}
impl<'a> ::std::fmt::Display for Record<'a>
{
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result
{
write!(f, "{}-{}: {} -- {}", self.get_domain(), self.get_level(),
self.get_location(), self.get_message())
}
}