#![cfg_attr(feature = "nightly", feature(thread_id_value))]
#[cfg(feature = "colored")]
use colored::*;
use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError};
use std::{collections::HashMap, str::FromStr};
#[cfg(feature = "timestamps")]
use chrono::{Local, Utc,FixedOffset};
#[cfg(feature = "timestamps")]
const TIMESTAMP_FORMAT_OFFSET: &str = "%Y-%m-%d %H:%M:%S";
#[cfg(feature = "timestamps")]
const TIMESTAMP_FORMAT_UTC: &str = "%Y-%m-%dT%H:%M:%S%.3f %z";
#[cfg(feature = "timestamps")]
#[derive(PartialEq)]
enum Timestamps {
None,
Local,
Utc,
UtcOffset(i32),
}
pub struct Logger {
default_level: LevelFilter,
module_levels: Vec<(String, LevelFilter)>,
#[cfg(feature = "threads")]
threads: bool,
#[cfg(feature = "timestamps")]
timestamps: Timestamps,
#[cfg(feature = "timestamps")]
timestamps_format: Option<String>,
#[cfg(feature = "colored")]
colors: bool,
}
impl Logger {
#[must_use = "You must call init() to begin logging"]
pub fn new() -> Logger {
Logger {
default_level: LevelFilter::Trace,
module_levels: Vec::new(),
#[cfg(feature = "threads")]
threads: false,
#[cfg(feature = "timestamps")]
timestamps: Timestamps::Utc,
#[cfg(feature = "timestamps")]
timestamps_format: None,
#[cfg(feature = "colored")]
colors: true,
}
}
#[must_use = "You must call init() to begin logging"]
#[deprecated(
since = "1.12.0",
note = "Use [`env`](#method.env) instead. Will be removed in version 2.0.0."
)]
pub fn from_env() -> Logger {
Logger::new().with_level(log::LevelFilter::Error).env()
}
#[must_use = "You must call init() to begin logging"]
pub fn env(mut self) -> Logger {
self.default_level = std::env::var("RUST_LOG")
.ok()
.as_deref()
.map(log::LevelFilter::from_str)
.and_then(Result::ok)
.unwrap_or(self.default_level);
self
}
#[must_use = "You must call init() to begin logging"]
pub fn with_level(mut self, level: LevelFilter) -> Logger {
self.default_level = level;
self
}
#[must_use = "You must call init() to begin logging"]
pub fn with_module_level(mut self, target: &str, level: LevelFilter) -> Logger {
self.module_levels.push((target.to_string(), level));
#[cfg(test)]
self.module_levels
.sort_by_key(|(name, _level)| name.len().wrapping_neg());
self
}
#[must_use = "You must call init() to begin logging"]
#[deprecated(
since = "1.11.0",
note = "Use [`with_module_level`](#method.with_module_level) instead. Will be removed in version 2.0.0."
)]
pub fn with_target_levels(mut self, target_levels: HashMap<String, LevelFilter>) -> Logger {
self.module_levels = target_levels.into_iter().collect();
#[cfg(test)]
self.module_levels
.sort_by_key(|(name, _level)| name.len().wrapping_neg());
self
}
#[must_use = "You must call init() to begin logging"]
#[cfg(feature = "threads")]
pub fn with_threads(mut self, threads: bool) -> Logger {
self.threads = threads;
self
}
#[must_use = "You must call init() to begin logging"]
#[cfg(feature = "timestamps")]
pub fn with_timestamp_format(mut self, format:&String) -> Logger {
self.timestamps_format = Some(format.clone());
self
}
#[must_use = "You must call init() to begin logging"]
#[cfg(feature = "timestamps")]
pub fn without_timestamps(mut self) -> Logger {
self.timestamps = Timestamps::None;
self
}
#[must_use = "You must call init() to begin logging"]
#[cfg(feature = "timestamps")]
pub fn with_local_timestamps(mut self) -> Logger {
self.timestamps = Timestamps::Local;
self
}
#[must_use = "You must call init() to begin logging"]
#[cfg(feature = "timestamps")]
pub fn with_utc_timestamps(mut self) -> Logger {
self.timestamps = Timestamps::Utc;
self
}
#[must_use = "You must call init() to begin logging"]
#[cfg(feature = "timestamps")]
pub fn with_utc_offset(mut self, offset: i32) -> Logger {
self.timestamps = Timestamps::UtcOffset(offset);
self
}
#[must_use = "You must call init() to begin logging"]
#[cfg(feature = "colored")]
pub fn with_colors(mut self, colors: bool) -> Logger {
self.colors = colors;
self
}
pub fn init(mut self) -> Result<(), SetLoggerError> {
#[cfg(all(windows, feature = "colored"))]
set_up_color_terminal();
self.module_levels
.sort_by_key(|(name, _level)| name.len().wrapping_neg());
let max_level = self.module_levels.iter().map(|(_name, level)| level).copied().max();
let max_level = max_level
.map(|lvl| lvl.max(self.default_level))
.unwrap_or(self.default_level);
log::set_max_level(max_level);
log::set_boxed_logger(Box::new(self))?;
Ok(())
}
}
impl Default for Logger {
fn default() -> Self {
Logger::new()
}
}
impl Log for Logger {
fn enabled(&self, metadata: &Metadata) -> bool {
&metadata.level().to_level_filter()
<= self
.module_levels
.iter()
.find(|(name, _level)| metadata.target().starts_with(name))
.map(|(_name, level)| level)
.unwrap_or(&self.default_level)
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let level_string = {
#[cfg(feature = "colored")]
{
if self.colors {
match record.level() {
Level::Error => format!("{:<5}", record.level().to_string()).red().to_string(),
Level::Warn => format!("{:<5}", record.level().to_string()).yellow().to_string(),
Level::Info => format!("{:<5}", record.level().to_string()).cyan().to_string(),
Level::Debug => format!("{:<5}", record.level().to_string()).purple().to_string(),
Level::Trace => format!("{:<5}", record.level().to_string()).normal().to_string(),
}
} else {
format!("{:<5}", record.level().to_string())
}
}
#[cfg(not(feature = "colored"))]
{
format!("{:<5}", record.level().to_string())
}
};
let target = if !record.target().is_empty() {
record.target()
} else {
record.module_path().unwrap_or_default()
};
let thread = {
#[cfg(feature = "threads")]
if self.threads {
let thread = std::thread::current();
format!("@{}", {
#[cfg(feature = "nightly")]
{
thread.name().unwrap_or(&thread.id().as_u64().to_string())
}
#[cfg(not(feature = "nightly"))]
{
thread.name().unwrap_or("?")
}
})
} else {
"".to_string()
}
#[cfg(not(feature = "threads"))]
""
};
let timestamp = {
#[cfg(feature = "timestamps")]
match self.timestamps {
Timestamps::None => "".to_string(),
Timestamps::Local => format!( "{} ", Local::now().format(&self.timestamps_format.clone().unwrap_or(String::from(TIMESTAMP_FORMAT_OFFSET)))),
Timestamps::Utc => format!("{} ", Utc::now().format(&self.timestamps_format.clone().unwrap_or(String::from(TIMESTAMP_FORMAT_UTC)))),
Timestamps::UtcOffset(offset) => {
let offset = FixedOffset::east_opt(offset).unwrap();
let now_with_offset = Utc::now().with_timezone(&offset);
let fmt = self.timestamps_format.clone().unwrap_or(String::from(TIMESTAMP_FORMAT_OFFSET));
format!("{} ", now_with_offset.format(&fmt))
},
}
#[cfg(not(feature = "timestamps"))]
""
};
let message = format!("{}{} [{}{}] {}", timestamp, level_string, target, thread, record.args());
#[cfg(not(feature = "stderr"))]
println!("{}", message);
#[cfg(feature = "stderr")]
eprintln!("{}", message);
}
}
fn flush(&self) {}
}
#[cfg(all(windows, feature = "colored"))]
fn set_up_color_terminal() {
use std::io::{stdout, IsTerminal};
if stdout().is_terminal() {
unsafe {
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::System::Console::{
GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
STD_OUTPUT_HANDLE,
};
let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
if stdout == INVALID_HANDLE_VALUE {
return;
}
let mut mode: CONSOLE_MODE = 0;
if GetConsoleMode(stdout, &mut mode) == 0 {
return;
}
SetConsoleMode(stdout, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
}
}
pub fn init() -> Result<(), SetLoggerError> {
Logger::new().init()
}
#[cfg(feature = "timestamps")]
pub fn init_utc() -> Result<(), SetLoggerError> {
Logger::new().with_utc_timestamps().init()
}
pub fn init_with_env() -> Result<(), SetLoggerError> {
Logger::new().env().init()
}
pub fn init_with_level(level: Level) -> Result<(), SetLoggerError> {
Logger::new().with_level(level.to_level_filter()).init()
}
#[deprecated(
since = "1.12.0",
note = "Use [`init_with_env`] instead, which does not unwrap the result. Will be removed in version 2.0.0."
)]
pub fn init_by_env() {
init_with_env().unwrap()
}