#![deny(
missing_docs,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unused_import_braces,
unused_qualifications
)]
mod log;
mod u32map;
pub use log::*;
use u32map::*;
use chrono::prelude::*;
use colored::*;
use crossbeam_channel::{bounded, Receiver, RecvError, Sender, TrySendError};
use std::{
collections::HashMap,
fmt::{self, Debug, Display, LowerHex},
marker::Send,
sync::{
atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
Arc, Mutex,
},
thread::{self, JoinHandle},
};
pub type Compatibility =
Box<dyn Fn(u8, Box<dyn Fn(&mut fmt::Formatter) -> fmt::Result + Send + Sync>)>;
pub type Logger<C> = LoggerV2Async<C>;
#[derive(Clone)]
pub struct LoggerV2Async<C: Display + Send> {
log_channel: Sender<(u8, u32, C)>,
context_specific_level: Arc<Mutex<HashMap<String, u8>>>,
level: Arc<AtomicU8>,
log_channel_full_count: Arc<AtomicUsize>,
thread_handle: Arc<AutoJoinHandle>,
colorize: Arc<AtomicBool>,
context_map: Arc<Mutex<U32Map<String>>>,
context: u32,
}
pub trait GenericLogger {
fn log_generic(&self, level: u8, message: Generic);
fn to_logpass(self) -> Logpass;
fn to_compatibility(self) -> Compatibility;
}
impl<C: 'static + Display + From<Generic> + Send> GenericLogger for LoggerV2Async<C> {
fn log_generic(&self, level: u8, message: Generic) {
self.log(level, message);
}
fn to_logpass(self) -> Logpass {
Logpass::PassThrough(Box::new(self))
}
fn to_compatibility(self) -> Compatibility {
Box::new(move |level, writer| {
self.log_generic(level, Generic(Arc::new(writer)));
})
}
}
pub enum Logpass {
Compatibility(Compatibility),
PassThrough(Box<dyn GenericLogger>),
}
impl Logpass {
pub fn log(&self, level: u8, message: Generic) {
match self {
Logpass::Compatibility(compat) => {
(compat)(level, Box::new(move |f| write!(f, "{}", message)))
}
Logpass::PassThrough(passthrough) => passthrough.log_generic(level, message),
}
}
pub fn from_compatibility(compatibility: Compatibility) -> Self {
Logpass::Compatibility(compatibility)
}
}
struct AutoJoinHandle {
thread: Option<JoinHandle<()>>,
}
impl Drop for AutoJoinHandle {
fn drop(&mut self) {
self.thread.take().map(JoinHandle::join);
}
}
#[derive(Clone)]
pub struct Generic(Arc<dyn Fn(&mut fmt::Formatter) -> fmt::Result + Send + Sync>);
impl Display for Generic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(self.0)(f)
}
}
#[doc(hidden)]
pub fn make_generic__(
arg: Arc<dyn Fn(&mut fmt::Formatter) -> fmt::Result + Send + Sync>,
) -> Generic {
Generic(arg)
}
static CHANNEL_SIZE: usize = 30_000;
static DEFAULT_LEVEL: u8 = 128;
static LOGGER_QUIT_LEVEL: u8 = 196;
impl<C: 'static + Display + Send> LoggerV2Async<C> {
pub fn spawn(ctx: &'static str) -> Logger<C> {
let (tx, rx) = bounded(CHANNEL_SIZE);
let colorize = Arc::new(AtomicBool::new(false));
let colorize_clone = colorize.clone();
let full_count = Arc::new(AtomicUsize::new(0));
let full_count_clone = full_count.clone();
let level = Arc::new(AtomicU8::new(DEFAULT_LEVEL));
let level_clone = level.clone();
let ex = std::io::stdout();
let context_specific_level = create_context_specific_log_level(Some(ctx));
let context_specific_level_clone = context_specific_level.clone();
let context_map = create_context_map(Some(ctx));
let context_map_clone = context_map.clone();
let logger_thread = thread::Builder::new()
.name("logger".to_string())
.spawn(move || {
logger_thread(
rx,
full_count_clone,
ex.lock(),
context_specific_level_clone,
level_clone,
colorize_clone,
context_map,
)
})
.unwrap();
Logger {
thread_handle: Arc::new(AutoJoinHandle {
thread: Some(logger_thread),
}),
log_channel: tx,
log_channel_full_count: full_count,
level,
context_specific_level,
colorize,
context_map: context_map_clone,
context: 1,
}
}
pub fn spawn_with_writer<T: 'static + std::io::Write + Send>(
ctx: &'static str,
writer: T,
) -> Logger<C> {
let (tx, rx) = bounded(CHANNEL_SIZE);
let colorize = Arc::new(AtomicBool::new(false));
let colorize_clone = colorize.clone();
let full_count = Arc::new(AtomicUsize::new(0));
let full_count_clone = full_count.clone();
let level = Arc::new(AtomicU8::new(DEFAULT_LEVEL));
let level_clone = level.clone();
let context_specific_level = create_context_specific_log_level(Some(ctx));
let context_specific_level_clone = context_specific_level.clone();
let context_map = create_context_map(Some(ctx));
let context_map_clone = context_map.clone();
let logger_thread = thread::Builder::new()
.name("logger".to_string())
.spawn(move || {
logger_thread(
rx,
full_count_clone,
writer,
context_specific_level_clone,
level_clone,
colorize_clone,
context_map,
)
})
.unwrap();
Logger {
thread_handle: Arc::new(AutoJoinHandle {
thread: Some(logger_thread),
}),
log_channel: tx,
log_channel_full_count: full_count,
level,
context_specific_level,
colorize,
context_map: context_map_clone,
context: 1,
}
}
pub fn spawn_void() -> Logger<C> {
let (tx, rx) = bounded(CHANNEL_SIZE);
let colorize = Arc::new(AtomicBool::new(false));
let colorize_clone = colorize.clone();
let full_count = Arc::new(AtomicUsize::new(0));
let full_count_clone = full_count.clone();
let level = Arc::new(AtomicU8::new(0));
let level_clone = level.clone();
struct Void {}
impl std::io::Write for Void {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let context_specific_level = create_context_specific_log_level(Some("void"));
let context_specific_level_clone = context_specific_level.clone();
let context_map = create_context_map(Some("void"));
let context_map_clone = context_map.clone();
let logger_thread = thread::Builder::new()
.name("logger".to_string())
.spawn(move || {
logger_thread(
rx,
full_count_clone,
Void {},
context_specific_level_clone,
level_clone,
colorize_clone,
context_map,
)
})
.unwrap();
Logger {
thread_handle: Arc::new(AutoJoinHandle {
thread: Some(logger_thread),
}),
log_channel: tx,
log_channel_full_count: full_count,
level,
context_specific_level,
colorize,
context_map: context_map_clone,
context: 1,
}
}
pub fn spawn_test() -> Logger<C> {
let (tx, rx) = bounded(CHANNEL_SIZE);
let colorize = Arc::new(AtomicBool::new(true));
let colorize_clone = colorize.clone();
let full_count = Arc::new(AtomicUsize::new(0));
let full_count_clone = full_count.clone();
let level = Arc::new(AtomicU8::new(255));
let level_clone = level.clone();
let ex = std::io::stderr();
let context_specific_level = create_context_specific_log_level(Some("test"));
let context_specific_level_clone = context_specific_level.clone();
let context_map = create_context_map(Some("test"));
let context_map_clone = context_map.clone();
let logger_thread = thread::Builder::new()
.name("logger".to_string())
.spawn(move || {
logger_thread(
rx,
full_count_clone,
ex.lock(),
context_specific_level_clone,
level_clone,
colorize_clone,
context_map,
)
})
.unwrap();
Logger {
thread_handle: Arc::new(AutoJoinHandle {
thread: Some(logger_thread),
}),
log_channel: tx,
log_channel_full_count: full_count,
level,
context_specific_level,
colorize,
context_map: context_map_clone,
context: 1,
}
}
pub fn clone_with_context(&self, ctx: &'static str) -> Self {
if let Ok(ref mut lvl) = self.context_specific_level.lock() {
lvl.insert(ctx.to_string(), DEFAULT_LEVEL);
}
let ctxval = if let Ok(ref mut ctxmap) = self.context_map.lock() {
ctxmap.insert(ctx.to_string()).expect("Context map is full")
} else {
panic!();
};
Logger {
thread_handle: self.thread_handle.clone(),
log_channel: self.log_channel.clone(),
log_channel_full_count: self.log_channel_full_count.clone(),
level: self.level.clone(),
context_specific_level: self.context_specific_level.clone(),
colorize: self.colorize.clone(),
context_map: self.context_map.clone(),
context: ctxval,
}
}
pub fn clone_add_context(&self, ctx: &'static str) -> Self {
let newctx;
let ctxval = if let Ok(ref mut ctxmap) = self.context_map.lock() {
let prev_ctx = ctxmap.get(&self.context).unwrap().clone();
newctx = prev_ctx + "-" + ctx;
ctxmap.insert(newctx.clone()).expect("Context map is full")
} else {
panic!();
};
if let Ok(ref mut lvl) = self.context_specific_level.lock() {
lvl.insert(newctx.to_string(), DEFAULT_LEVEL);
}
Logger {
thread_handle: self.thread_handle.clone(),
log_channel: self.log_channel.clone(),
log_channel_full_count: self.log_channel_full_count.clone(),
level: self.level.clone(),
context_specific_level: self.context_specific_level.clone(),
colorize: self.colorize.clone(),
context_map: self.context_map.clone(),
context: ctxval,
}
}
pub fn set_log_level(&self, level: u8) {
self.level.store(level, Ordering::Relaxed);
}
pub fn get_log_level(&self) -> u8 {
self.level.load(Ordering::Relaxed)
}
pub fn set_context_specific_log_level(&self, ctx: &str, level: u8) -> bool {
if let Ok(ref mut lvl) = self.context_specific_level.lock() {
if let Some(stored_level) = lvl.get_mut(ctx) {
*stored_level = level;
return true;
}
}
false
}
pub fn set_this_log_level(&self, level: u8) {
let ctx = if let Ok(ref mut ctxs) = self.context_map.lock() {
ctxs.get(&self.context).unwrap().clone()
} else {
panic!("Unable to acquire context map lock");
};
assert!(self.set_context_specific_log_level(&ctx, level));
}
pub fn set_colorize(&self, on: bool) {
self.colorize.store(on, Ordering::Relaxed);
}
pub fn get_colorize(&self) -> bool {
self.colorize.load(Ordering::Relaxed)
}
fn log_internal(&self, level: u8, message: impl Into<C>) -> bool {
if level <= self.level.load(Ordering::Relaxed) {
match self
.log_channel
.try_send((level, self.context, message.into()))
{
Ok(()) => true,
Err(TrySendError::Full(_)) => {
self.log_channel_full_count.fetch_add(1, Ordering::Relaxed);
false
}
Err(TrySendError::Disconnected(_)) => false,
}
} else {
false
}
}
}
#[cfg(not(test))]
impl<C: 'static + Display + Send> LoggerV2Async<C> {
pub fn log(&self, level: u8, message: impl Into<C>) {
self.log_internal(level, message);
}
#[cfg(not(debug_assertions))]
pub fn trace(&self, _: impl Into<C>) {}
#[cfg(debug_assertions)]
pub fn trace(&self, message: impl Into<C>) {
self.log(255, message)
}
pub fn debug(&self, message: impl Into<C>) {
self.log(192, message)
}
pub fn info(&self, message: impl Into<C>) {
self.log(128, message)
}
pub fn warn(&self, message: impl Into<C>) {
self.log(64, message)
}
pub fn error(&self, message: impl Into<C>) {
self.log(0, message)
}
}
#[cfg(test)]
impl<C: 'static + Display + Send> LoggerV2Async<C> {
pub fn log(&self, level: u8, message: impl Into<C>) -> bool {
self.log_internal(level, message)
}
#[cfg(not(debug_assertions))]
pub fn trace(&self, _: impl Into<C>) -> bool {
false
}
#[cfg(debug_assertions)]
pub fn trace(&self, message: impl Into<C>) -> bool {
self.log(255, message)
}
pub fn debug(&self, message: impl Into<C>) -> bool {
self.log(192, message)
}
pub fn info(&self, message: impl Into<C>) -> bool {
self.log(128, message)
}
pub fn warn(&self, message: impl Into<C>) -> bool {
self.log(64, message)
}
pub fn error(&self, message: impl Into<C>) -> bool {
self.log(0, message)
}
}
impl<C: 'static + Display + Send + From<String>> LoggerV2Async<C> {
pub fn make_writer(&self, level: u8) -> impl std::fmt::Write + '_ {
struct Writer<'a, C: Display + Send + From<String>> {
logger: &'a Logger<C>,
level: u8,
}
impl<'a, C: 'static + Display + Send + From<String>> std::fmt::Write for Writer<'a, C> {
fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
self.logger.log(self.level, s.to_string());
Ok(())
}
}
Writer {
logger: self,
level,
}
}
}
fn create_context_specific_log_level(ctx: Option<&'static str>) -> Arc<Mutex<HashMap<String, u8>>> {
let mut map = HashMap::new();
map.insert("logger".to_string(), LOGGER_QUIT_LEVEL);
if let Some(string) = ctx {
map.insert(string.to_string(), DEFAULT_LEVEL);
}
Arc::new(Mutex::new(map))
}
fn create_context_map(ctx: Option<&'static str>) -> Arc<Mutex<U32Map<String>>> {
let mut map = U32Map::new();
map.insert("logger".to_string());
if let Some(string) = ctx {
map.insert(string.to_string()).expect("Added");
}
Arc::new(Mutex::new(map))
}
fn count_digits_base_10(mut number: usize) -> usize {
let mut digits = 1;
while number >= 10 {
number /= 10;
digits += 1;
}
digits
}
fn colorize_level(level: u8) -> ColoredString {
if level < 64 {
format!("{:03}", level).red()
} else if level < 128 {
format!("{:03}", level).yellow()
} else if level < 192 {
format!("{:03}", level).green()
} else if level < 255 {
format!("{:03}", level).cyan()
} else if level == 255 {
format!("{:03}", level).magenta()
} else {
unreachable!()
}
}
fn do_write_nocolor<W: std::io::Write, T: Display>(
writer: &mut W,
lvl: u8,
ctx: &str,
msg: &str,
now: T,
newlines: usize,
last_is_line: bool,
) -> std::io::Result<()> {
if newlines > 1 {
for (idx, line) in msg.lines().enumerate() {
writeln!(
writer,
"{}: {:03} {} [{:0width$}/{}]: {}",
now,
lvl,
ctx,
idx + 1,
newlines,
line,
width = count_digits_base_10(newlines),
)?;
}
if last_is_line {
writeln!(
writer,
"{}: {:03} {} [{}/{}]: ",
now, lvl, ctx, newlines, newlines
)?;
}
} else {
writeln!(writer, "{}: {:03} {}: {}", now, lvl, ctx, msg,)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn do_write_color<W: std::io::Write, T: Display>(
writer: &mut W,
lvl: u8,
ctx: &str,
msg: &str,
now: T,
newlines: usize,
last_is_line: bool,
color_counter: &mut usize,
) -> std::io::Result<()> {
*color_counter = (*color_counter + 1) % 2;
let color;
match color_counter {
0 => color = "blue",
1 => color = "magenta",
_ => unimplemented!(),
}
let msg_color;
match color_counter {
0 => msg_color = "bright blue",
1 => msg_color = "bright magenta",
_ => unimplemented!(),
}
let msg = msg.color(color);
if newlines > 1 {
for (idx, line) in msg.lines().enumerate() {
writeln!(
writer,
"{}: {} {} {}: {}",
now.to_string().color(color),
colorize_level(lvl),
ctx.bright_green(),
format!(
"[{:0width$}/{}]",
idx + 1,
newlines,
width = count_digits_base_10(newlines),
)
.bright_yellow(),
line.color(msg_color),
)?;
}
if last_is_line {
writeln!(
writer,
"{}: {} {} {}: ",
now.to_string().color(color),
colorize_level(lvl),
ctx.bright_green(),
format!("[{}/{}]", newlines, newlines,).bright_yellow(),
)?;
}
} else {
writeln!(
writer,
"{}: {} {}: {}",
now.to_string().color(color),
colorize_level(lvl),
ctx.bright_green(),
msg.color(msg_color),
)?;
}
Ok(())
}
fn do_write<C: Display + Send, W: std::io::Write>(
writer: &mut W,
lvl: u8,
ctx: &str,
msg: C,
colorize: bool,
color_counter: &mut usize,
) -> std::io::Result<()> {
const ITEMS: &[chrono::format::Item] = {
use chrono::format::{Fixed::*, Item::*, Numeric::*, Pad::*};
&[
Fixed(ShortMonthName),
Literal(" "),
Numeric(Day, None),
Literal(" "),
Numeric(Year, None),
Literal(" "),
Numeric(Hour, Zero),
Literal(":"),
Numeric(Minute, Zero),
Literal(":"),
Numeric(Second, Zero),
Fixed(Nanosecond9),
Fixed(TimezoneOffset),
]
};
let now = Local::now().format_with_items(ITEMS.iter().cloned());
let msg = format!("{}", msg);
let mut newlines = 1;
let mut last_is_line = false;
for ch in msg.chars() {
if ch == '\n' {
newlines += 1;
last_is_line = true;
} else {
last_is_line = false;
}
}
if colorize {
do_write_color(
writer,
lvl,
ctx,
msg.as_str(),
now,
newlines,
last_is_line,
color_counter,
)
} else {
do_write_nocolor(writer, lvl, ctx, msg.as_str(), now, newlines, last_is_line)
}
}
fn logger_thread<C: Display + Send, W: std::io::Write>(
rx: Receiver<(u8, u32, C)>,
dropped: Arc<AtomicUsize>,
mut writer: W,
context_specific_level: Arc<Mutex<HashMap<String, u8>>>,
global_level: Arc<AtomicU8>,
colorize: Arc<AtomicBool>,
contexts: Arc<Mutex<U32Map<String>>>,
) {
let mut color_counter = 0;
let mut color;
'outer_loop: loop {
match rx.recv() {
Ok(msg) => {
color = colorize.load(Ordering::Relaxed);
let contexts = contexts.lock();
let ctx = match contexts {
Ok(contexts) => contexts,
Err(_poison) => {
let _ = do_write(
&mut writer,
0,
"logger",
"Context map lock has been poisoned. Exiting logger",
color,
&mut color_counter,
);
break 'outer_loop;
}
};
let ctx = ctx.get(&msg.1).expect("Entry is not in table");
let lvls = context_specific_level.lock();
match lvls {
Ok(lvls) => {
if let Some(lvl) = lvls.get(ctx) {
if msg.0 <= *lvl
&& do_write(
&mut writer,
msg.0,
ctx,
msg.2,
color,
&mut color_counter,
)
.is_err()
{
break 'outer_loop;
}
} else if do_write(
&mut writer,
msg.0,
ctx,
msg.2,
color,
&mut color_counter,
)
.is_err()
{
break 'outer_loop;
}
}
Err(_poison) => {
let _ = do_write(
&mut writer,
0,
"logger",
"Context specific level lock has been poisoned. Exiting logger",
color,
&mut color_counter,
);
break 'outer_loop;
}
}
}
Err(error @ RecvError { .. }) => {
color = colorize.load(Ordering::Relaxed);
let lvls = context_specific_level.lock();
match lvls {
Ok(lvls) => {
if let Some(lvl) = lvls.get("logger") {
if LOGGER_QUIT_LEVEL <= *lvl
&& LOGGER_QUIT_LEVEL <= global_level.load(Ordering::Relaxed)
{
let _ = do_write(
&mut writer,
LOGGER_QUIT_LEVEL,
"logger",
format!(
"Unable to receive message. Exiting logger, reason={}",
error
),
color,
&mut color_counter,
);
}
} else if LOGGER_QUIT_LEVEL <= global_level.load(Ordering::Relaxed) {
let _ = do_write(
&mut writer,
LOGGER_QUIT_LEVEL,
"logger",
format!(
"Unable to receive message. Exiting logger, reason={}",
error
),
color,
&mut color_counter,
);
}
}
Err(_poison) => {
}
}
break 'outer_loop;
}
}
let dropped_messages = dropped.swap(0, Ordering::Relaxed);
if dropped_messages > 0
&& do_write(
&mut writer,
64,
"logger",
format!(
"Logger dropped messages due to channel overflow, count={}",
dropped_messages
),
color,
&mut color_counter,
)
.is_err()
{
break 'outer_loop;
}
}
}
pub struct InDebug<'a, T: Debug>(pub &'a T);
impl<'a, T: Debug> Display for InDebug<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
pub struct InDebugPretty<'a, T: Debug>(pub &'a T);
impl<'a, T: Debug> Display for InDebugPretty<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:#?}", self.0)
}
}
pub struct InHex<'a, T: LowerHex>(pub &'a T);
impl<'a, T: LowerHex> Display for InHex<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use regex::Regex;
use std::{fmt, io};
fn remove_time(line: &str) -> String {
let regex = Regex::new(
r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: (.*)",
)
.unwrap();
let x = regex.captures_iter(&line).next().unwrap()[3].to_string();
x
}
fn read_messages_without_date(delegate: fn(&Logger<Log>)) -> Vec<String> {
let store = Arc::new(Mutex::new(vec![]));
let writer = Store {
store: store.clone(),
};
let logger = Logger::<Log>::spawn_with_writer("tst", writer);
delegate(&logger);
drop(logger);
let string = String::from_utf8(store.lock().unwrap().to_vec()).unwrap();
string.lines().map(remove_time).collect()
}
enum Log {
Static(&'static str),
Generic(Generic),
}
impl Display for Log {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Log::Static(str) => write!(f, "{}", str),
Log::Generic(generic) => generic.fmt(f),
}
}
}
impl From<Generic> for Log {
fn from(generic: Generic) -> Self {
Log::Generic(generic)
}
}
struct Void {}
impl io::Write for Void {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
struct Store {
pub store: Arc<Mutex<Vec<u8>>>,
}
impl io::Write for Store {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut vector = self.store.lock().unwrap();
vector.extend(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn send_simple_string() {
use std::fmt::Write;
let logger = Logger::<String>::spawn("tst");
assert_eq!(true, logger.info("Message"));
let mut writer = logger.make_writer(128);
write!(writer, "Message 2").unwrap();
drop(writer);
}
#[test]
fn send_successful_message() {
let logger = Logger::<Log>::spawn("tst");
assert_eq!(true, logger.info(Log::Static("Message")));
}
#[test]
fn trace_is_disabled_by_default() {
let logger = Logger::<Log>::spawn("tst");
assert_eq!(false, logger.trace(Log::Static("Message")));
}
#[test]
fn debug_is_disabled_by_default() {
let logger = Logger::<Log>::spawn("tst");
assert_eq!(false, logger.debug(Log::Static("Message")));
}
#[test]
fn info_is_enabled_by_default() {
let logger = Logger::<Log>::spawn("tst");
assert_eq!(true, logger.info(Log::Static("Message")));
}
#[test]
fn warn_is_enabled_by_default() {
let logger = Logger::<Log>::spawn("tst");
assert_eq!(true, logger.warn(Log::Static("Message")));
}
#[test]
fn error_is_enabled_by_default() {
let logger = Logger::<Log>::spawn("tst");
assert_eq!(true, logger.error(Log::Static("Message")));
}
#[test]
fn custom_writer() {
let writer = Void {};
let logger = Logger::<Log>::spawn_with_writer("tst", writer);
assert_eq!(true, logger.error(Log::Static("Message")));
}
#[test]
fn count_digits() {
for i in 0..10 {
assert_eq!(1, count_digits_base_10(i));
}
for i in 10..100 {
assert_eq!(2, count_digits_base_10(i));
}
for i in &[100usize, 123, 321, 980] {
assert_eq!(3, count_digits_base_10(*i));
}
assert_eq!(4, count_digits_base_10(1248));
assert_eq!(10, count_digits_base_10(01329583467));
}
#[test]
fn ensure_proper_message_format() {
let store = Arc::new(Mutex::new(vec![]));
let writer = Store {
store: store.clone(),
};
let logger = Logger::<Log>::spawn_with_writer("tst", writer);
assert_eq!(true, logger.error(Log::Static("Message")));
assert_eq!(true, logger.error(Log::Static("Second message")));
let regex = Regex::new(
r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 000 tst: Message\n",
)
.unwrap();
drop(logger);
assert!(regex.is_match(&String::from_utf8(store.lock().unwrap().to_vec()).unwrap()));
}
#[test]
fn ensure_ending_message_when_exit_1() {
let store = Arc::new(Mutex::new(vec![]));
let writer = Store {
store: store.clone(),
};
let logger = Logger::<Log>::spawn_with_writer("tst", writer);
logger.set_log_level(LOGGER_QUIT_LEVEL);
let regex = Regex::new(
r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 196 logger: Unable to receive message. Exiting logger, reason=receiving on an empty and disconnected channel\n$",
)
.unwrap();
drop(logger);
assert!(regex.is_match(&String::from_utf8(store.lock().unwrap().to_vec()).unwrap()));
}
#[test]
fn ensure_ending_message_when_exit_2() {
let store = Arc::new(Mutex::new(vec![]));
let writer = Store {
store: store.clone(),
};
let logger = Logger::<Log>::spawn_with_writer("tst", writer);
let regex = Regex::new(r"^$").unwrap();
drop(logger);
assert!(regex.is_match(&String::from_utf8(store.lock().unwrap().to_vec()).unwrap()));
}
#[test]
fn ensure_ending_message_when_exit_3() {
let store = Arc::new(Mutex::new(vec![]));
let writer = Store {
store: store.clone(),
};
let logger = Logger::<Log>::spawn_with_writer("tst", writer);
logger.set_log_level(LOGGER_QUIT_LEVEL);
assert!(logger.set_context_specific_log_level("logger", 195));
let regex = Regex::new(r"^$").unwrap();
drop(logger);
assert!(regex.is_match(&String::from_utf8(store.lock().unwrap().to_vec()).unwrap()));
}
#[test]
fn spawn_void() {
let logger = Logger::<Log>::spawn_void();
assert_eq!(0, logger.get_log_level());
assert_eq!(true, logger.error(Log::Static("Message\n")));
assert_eq!(false, logger.warn(Log::Static("Message\n")));
assert_eq!(false, logger.info(Log::Static("Message\n")));
assert_eq!(false, logger.debug(Log::Static("Message\n")));
assert_eq!(false, logger.trace(Log::Static("Message\n")));
}
#[test]
fn ensure_proper_message_format_line_ending_with_newline() {
let store = Arc::new(Mutex::new(vec![]));
let writer = Store {
store: store.clone(),
};
let logger = Logger::<Log>::spawn_with_writer("tst", writer);
assert_eq!(true, logger.error(Log::Static("Message\n")));
let regex = Regex::new(
r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 000 tst \[1/2\]: Message
(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 000 tst \[2/2\]: \n",
)
.unwrap();
drop(logger);
assert!(
regex.is_match(&String::from_utf8(store.lock().unwrap().to_vec()).unwrap()),
"{}",
String::from_utf8(store.lock().unwrap().to_vec()).unwrap()
);
}
#[test]
fn nested_contexts() {
let lines = read_messages_without_date(|lgr| {
let client = lgr.clone_with_context("client");
let laminar = client.clone_add_context("laminar");
error!(laminar, "Hello world");
let vxdraw = laminar.clone_add_context("vxdraw");
warn!(vxdraw, "Initializing something {}", "Something");
laminar.set_this_log_level(0);
warn!(vxdraw, "A warning");
warn!(laminar, "Another warning");
});
assert_eq!(3, lines.len());
assert_eq!("000 client-laminar: Hello world", lines[0]);
assert_eq!(
"064 client-laminar-vxdraw: Initializing something Something",
lines[1]
);
assert_eq!("064 client-laminar-vxdraw: A warning", lines[2]);
}
#[test]
fn multistuff() {
let lines = read_messages_without_date(|lgr| {
trace!(lgr.clone_with_context("tracing"), "Message 1");
debug!(lgr.clone_with_context("debugging"), "Message 2");
info!(lgr.clone_with_context("infoing"), "Message 3");
warn!(lgr.clone_with_context("warning"), "Message 4");
error!(lgr.clone_with_context("erroring"), "Message 5");
log!(123, lgr.clone_with_context("logging"), "Message 6");
assert!(!lgr.set_context_specific_log_level("overdebug", 191));
log!(
191,
lgr.clone_with_context("overdebug"),
"This gets filtered"
);
lgr.set_log_level(191);
let overdebug = lgr.clone_with_context("overdebug");
assert!(lgr.set_context_specific_log_level("overdebug", 191));
log!(191, overdebug, "Just above debugging worked");
});
assert_eq!(5, lines.len());
assert_eq!("128 infoing: Message 3", lines[0]);
assert_eq!("064 warning: Message 4", lines[1]);
assert_eq!("000 erroring: Message 5", lines[2]);
assert_eq!("123 logging: Message 6", lines[3]);
assert_eq!("191 overdebug: Just above debugging worked", lines[4]);
}
#[test]
fn multiple_lines_count_correctly() {
let store = Arc::new(Mutex::new(vec![]));
let writer = Store {
store: store.clone(),
};
let logger = Logger::<Log>::spawn_with_writer("tst", writer);
assert_eq!(true, logger.error(Log::Static("Message\nPart\n2")));
let regex = Regex::new(
r#"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 000 tst \[1/3\]: Message\n(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 000 tst \[2/3\]: Part\n(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 000 tst \[3/3\]: 2\n"#,
)
.unwrap();
drop(logger);
assert!(
regex.is_match(&String::from_utf8(store.lock().unwrap().to_vec()).unwrap()),
"{}",
String::from_utf8(store.lock().unwrap().to_vec()).unwrap()
);
}
#[test]
fn multiple_lines_count_correctly_trailing() {
let store = Arc::new(Mutex::new(vec![]));
let writer = Store {
store: store.clone(),
};
let logger = Logger::<Log>::spawn_with_writer("tst", writer);
assert_eq!(true, logger.error(Log::Static("Message\nPart\n2\n")));
let regex = Regex::new(
r#"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 000 tst \[1/4\]: Message\n(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 000 tst \[2/4\]: Part\n(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 000 tst \[3/4\]: 2\n(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d+ \d{2}:\d{2}:\d{2}.\d{9}(\+|-)\d{4}: 000 tst \[4/4\]: \n"#,
)
.unwrap();
drop(logger);
assert!(
regex.is_match(&String::from_utf8(store.lock().unwrap().to_vec()).unwrap()),
"{}",
String::from_utf8(store.lock().unwrap().to_vec()).unwrap()
);
}
#[test]
fn generic() {
let logger = Logger::<Generic>::spawn("tst");
log!(123, logger, "lorem {}", "ipsum"; "dolor" => "sit", "amet" => 1234);
trace!(logger, "lorem {}", "ipsum"; "a" => "b");
debug!(logger, "lorem {}", "ipsum"; "a" => "b");
info!(logger, "lorem {}", "ipsum"; "a" => "b");
warn!(logger, "lorem {}", "ipsum"; "a" => "b");
error!(logger, "lorem {}", "ipsum"; "a" => "b");
let ipsum = 1;
info!(logger, "lorem {}", ipsum; "dolor" => "sit");
}
#[test]
fn custom_writer_with_generic() {
let logger = Logger::<Log>::spawn("tst");
assert_eq!(true, logger.error(Log::Static("Message")));
assert_eq!(true, error!(logger, "Message"));
}
#[rustfmt::skip]
#[test]
fn ensure_all_macro_variants_can_be_used() {
let logger = Logger::<Log>::spawn("tst");
assert_eq!(false, trace!(logger, "Message"));
assert_eq!(false, trace!(logger, "Message",));
assert_eq!(false, trace!(logger, "Message {}", "argument"));
assert_eq!(false, trace!(logger, "Message {}", "argument",));
assert_eq!(false, trace!(logger, "Message";));
assert_eq!(false, trace!(logger, "Message"; "a" => "b"));
assert_eq!(false, trace!(logger, "Message"; "a" => "b",));
assert_eq!(false, trace!(logger, "Message",;));
assert_eq!(false, trace!(logger, "Message",; "a" => "b"));
assert_eq!(false, trace!(logger, "Message",; "a" => "b",));
assert_eq!(false, trace!(logger, "Message {}", "argument";));
assert_eq!(false, trace!(logger, "Message {}", "argument"; "a" => "b"));
assert_eq!(false, trace!(logger, "Message {}", "argument"; "a" => "b",));
assert_eq!(false, trace!(logger, "Message {}", "argument",;));
assert_eq!(false, trace!(logger, "Message {}", "argument",; "a" => "b"));
assert_eq!(false, trace!(logger, "Message {}", "argument",; "a" => "b",));
assert_eq!(false, debug!(logger, "Message"));
assert_eq!(false, debug!(logger, "Message",));
assert_eq!(false, debug!(logger, "Message {}", "argument"));
assert_eq!(false, debug!(logger, "Message {}", "argument",));
assert_eq!(false, debug!(logger, "Message";));
assert_eq!(false, debug!(logger, "Message"; "a" => "b"));
assert_eq!(false, debug!(logger, "Message"; "a" => "b",));
assert_eq!(false, debug!(logger, "Message",;));
assert_eq!(false, debug!(logger, "Message",; "a" => "b"));
assert_eq!(false, debug!(logger, "Message",; "a" => "b",));
assert_eq!(false, debug!(logger, "Message {}", "argument";));
assert_eq!(false, debug!(logger, "Message {}", "argument"; "a" => "b"));
assert_eq!(false, debug!(logger, "Message {}", "argument"; "a" => "b",));
assert_eq!(false, debug!(logger, "Message {}", "argument",;));
assert_eq!(false, debug!(logger, "Message {}", "argument",; "a" => "b"));
assert_eq!(false, debug!(logger, "Message {}", "argument",; "a" => "b",));
assert_eq!(true, info!(logger, "Message"));
assert_eq!(true, info!(logger, "Message",));
assert_eq!(true, info!(logger, "Message {}", "argument"));
assert_eq!(true, info!(logger, "Message {}", "argument",));
assert_eq!(true, info!(logger, "Message";));
assert_eq!(true, info!(logger, "Message"; "a" => "b"));
assert_eq!(true, info!(logger, "Message"; "a" => "b",));
assert_eq!(true, info!(logger, "Message",;));
assert_eq!(true, info!(logger, "Message",; "a" => "b"));
assert_eq!(true, info!(logger, "Message",; "a" => "b",));
assert_eq!(true, info!(logger, "Message {}", "argument";));
assert_eq!(true, info!(logger, "Message {}", "argument"; "a" => "b"));
assert_eq!(true, info!(logger, "Message {}", "argument"; "a" => "b",));
assert_eq!(true, info!(logger, "Message {}", "argument",;));
assert_eq!(true, info!(logger, "Message {}", "argument",; "a" => "b"));
assert_eq!(true, info!(logger, "Message {}", "argument",; "a" => "b",));
assert_eq!(true, warn!(logger, "Message"));
assert_eq!(true, warn!(logger, "Message",));
assert_eq!(true, warn!(logger, "Message {}", "argument"));
assert_eq!(true, warn!(logger, "Message {}", "argument",));
assert_eq!(true, warn!(logger, "Message";));
assert_eq!(true, warn!(logger, "Message"; "a" => "b"));
assert_eq!(true, warn!(logger, "Message"; "a" => "b",));
assert_eq!(true, warn!(logger, "Message",;));
assert_eq!(true, warn!(logger, "Message",; "a" => "b"));
assert_eq!(true, warn!(logger, "Message",; "a" => "b",));
assert_eq!(true, warn!(logger, "Message {}", "argument";));
assert_eq!(true, warn!(logger, "Message {}", "argument"; "a" => "b"));
assert_eq!(true, warn!(logger, "Message {}", "argument"; "a" => "b",));
assert_eq!(true, warn!(logger, "Message {}", "argument",;));
assert_eq!(true, warn!(logger, "Message {}", "argument",; "a" => "b"));
assert_eq!(true, warn!(logger, "Message {}", "argument",; "a" => "b",));
assert_eq!(true, error!(logger, "Message"));
assert_eq!(true, error!(logger, "Message",));
assert_eq!(true, error!(logger, "Message {}", "argument"));
assert_eq!(true, error!(logger, "Message {}", "argument",));
assert_eq!(true, error!(logger, "Message";));
assert_eq!(true, error!(logger, "Message"; "a" => "b"));
assert_eq!(true, error!(logger, "Message"; "a" => "b",));
assert_eq!(true, error!(logger, "Message",;));
assert_eq!(true, error!(logger, "Message",; "a" => "b"));
assert_eq!(true, error!(logger, "Message",; "a" => "b",));
assert_eq!(true, error!(logger, "Message {}", "argument";));
assert_eq!(true, error!(logger, "Message {}", "argument"; "a" => "b"));
assert_eq!(true, error!(logger, "Message {}", "argument"; "a" => "b",));
assert_eq!(true, error!(logger, "Message {}", "argument",;));
assert_eq!(true, error!(logger, "Message {}", "argument",; "a" => "b"));
assert_eq!(true, error!(logger, "Message {}", "argument",; "a" => "b",));
let value = 123;
assert_eq!(false, trace!(logger, "Message {}", value;; clone value));
assert_eq!(false, debug!(logger, "Message {}", value;; clone value));
assert_eq!(true, info!(logger, "Message {}", value;; clone value));
assert_eq!(true, warn!(logger, "Message {}", value;; clone value));
assert_eq!(true, error!(logger, "Message {}", value;; clone value));
assert_eq!(true, log!(128, logger, "Message {}", value;; clone value));
}
#[test]
fn colorize() {
let logger = Logger::<Log>::spawn("tst");
logger.set_log_level(255);
logger.set_colorize(true);
logger.trace(Log::Static("A trace message"));
logger.debug(Log::Static("A debug message"));
logger.info(Log::Static("An info message"));
logger.warn(Log::Static("A warning message"));
logger.error(Log::Static("An error message"));
logger.info(Log::Static("On\nmultiple\nlines\n"));
}
#[test]
fn test_spawn_test() {
let logger = Logger::<Log>::spawn_test();
assert_eq!(255, logger.get_log_level());
assert_eq!(true, logger.get_colorize());
#[cfg(debug_assertions)]
assert_eq!(true, logger.trace(Log::Static("A trace message")));
#[cfg(not(debug_assertions))]
assert_eq!(false, logger.trace(Log::Static("A trace message")));
assert_eq!(true, logger.debug(Log::Static("A debug message")));
assert_eq!(true, logger.info(Log::Static("An info message")));
assert_eq!(true, logger.warn(Log::Static("A warning message")));
assert_eq!(true, logger.error(Log::Static("An error message")));
}
#[test]
fn using_indebug() {
let logger = Logger::<Log>::spawn("tst");
#[derive(Clone)]
struct Value {}
impl Debug for Value {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Debug Value")
}
}
let value = Value {};
info!(logger, "Message"; "value" => InDebug(&value); clone value);
info!(logger, "Message"; "value" => InDebugPretty(&value); clone value);
}
#[test]
fn using_inhex() {
let store = Arc::new(Mutex::new(vec![]));
let writer = Store {
store: store.clone(),
};
let logger = Logger::<Log>::spawn_with_writer("tst", writer);
logger.set_log_level(128);
info!(logger, "Message"; "value" => InHex(&!127u32));
drop(logger);
assert_eq!(
"128 tst: Message, value=ffffff80",
remove_time(&String::from_utf8(store.lock().unwrap().to_vec()).unwrap()),
);
}
#[test]
fn indebug() {
assert_eq!("[1, 2, 3]", format!("{}", InDebug(&[1, 2, 3])));
assert_eq!(
"[\n 1,\n 2,\n 3,\n]",
format!("{}", InDebugPretty(&[1, 2, 3]))
);
}
#[test]
fn inhex() {
assert_eq!("ffffff80", format!("{}", InHex(&!127u32)));
}
#[test]
fn logpass() {
let logger = Logger::<Log>::spawn("tst").to_logpass();
info!(logger, "Message");
}
#[test]
fn compatibility_layer() {
let logger = Logger::<Log>::spawn("tst");
struct MyLibrary {
log: Logpass,
}
impl MyLibrary {
pub fn new(log: Compatibility) -> Self {
Self {
log: Logpass::from_compatibility(log),
}
}
pub fn function(&mut self) {
info!(self.log, "Compatibility layer");
}
}
let mut my_lib = MyLibrary::new(logger.to_compatibility());
my_lib.function();
}
}