#![forbid(unsafe_code)]
#![allow(clippy::tabs_in_doc_comments)]
#![warn(missing_docs)]
#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
#[cfg(all(not(feature = "log"), not(feature = "tracing")))]
compile_error!("at least one of 'log' or 'tracing' features must be enabled");
mod compat;
#[cfg(feature = "log")]
mod log_impl;
#[cfg(feature = "tracing")]
mod tracing_impl;
mod util;
use crate::{
compat::{Level, Metadata},
util::StringLike,
};
use std::io;
#[cfg(feature = "parking_lot")]
use parking_lot::Mutex;
#[cfg(not(feature = "parking_lot"))]
use std::sync::Mutex;
#[cfg(feature = "timestamps")]
use std::time::SystemTime;
#[non_exhaustive]
#[derive(Debug)]
pub struct Logger<T: io::Write + Send + Sync + 'static = io::Stdout> {
output: Mutex<T>,
pub color: bool,
#[cfg(feature = "timestamps")]
pub timezone: time::UtcOffset,
}
impl Default for Logger<io::Stdout> {
fn default() -> Self {
Self::new(io::stdout())
}
}
struct PrefixOptions {
align: bool,
#[cfg(feature = "timestamps")]
time: Option<SystemTime>,
}
impl<T: io::Write + Send + Sync + 'static> Logger<T> {
pub fn new(output: T) -> Self {
Self {
output: Mutex::new(output),
#[cfg(not(feature = "detect-color"))]
color: false,
#[cfg(feature = "detect-color")]
color: supports_color::on(supports_color::Stream::Stdout)
.map(|i| i.has_basic)
.unwrap_or(false),
#[cfg(all(feature = "timestamps", feature = "detect-timezone"))]
timezone: time::UtcOffset::current_local_offset()
.expect("failed to get local utc offset"),
#[cfg(all(feature = "timestamps", not(feature = "detect-timezone")))]
timezone: time::UtcOffset::UTC,
}
}
fn write_prefix<S: StringLike>(
&self,
output: &mut S,
meta: &Metadata,
options: &PrefixOptions,
) {
let color = self.color;
let (icon, level_str, color_code) = match meta.level {
Level::Trace => ('→', "trace", '4'),
Level::Debug => ('○', "debug", '6'),
Level::Info => ('●', "info", '2'),
Level::Warn => ('⚠', "warn", '3'),
Level::Error => ('✘', "error", '1'),
};
if options.align && matches!(meta.level, Level::Info | Level::Warn) {
output.push(' ');
}
if color {
output.push_str("\x1b[9");
output.push(color_code);
output.push('m');
}
output.push(icon);
output.push(' ');
if color {
output.push_str("\x1b[1;4m");
}
output.push_str(level_str);
if color {
output.push_str("\x1b[;3");
output.push(color_code);
output.push('m');
}
output.push(' ');
let mut module_path_parts = meta.module_path.split("::");
if let Some(first_part) = module_path_parts.next() {
output.push_str(first_part);
for part in module_path_parts {
output.push('/');
output.push_str(part);
}
}
if let Some(line) = meta.line {
if color {
output.push_str("\x1b[2m");
}
output.push(':');
output.push_str(itoa::Buffer::new().format(line));
}
#[cfg(feature = "timestamps")]
if let Some(time) = options.time {
let time = time::OffsetDateTime::from(time).to_offset(self.timezone);
output.push(' ');
if color {
output.push_str("\x1b[;2m");
}
let mut hour = time.hour();
let mut am_or_pm = 'A';
if hour >= 12 {
am_or_pm = 'P';
if hour != 12 {
hour -= 12;
}
}
output.push_str(itoa::Buffer::new().format(hour));
output.push(':');
let minute = time.minute();
if minute < 10 {
output.push('0');
}
output.push_str(itoa::Buffer::new().format(minute));
output.push(':');
let second = time.second();
if second < 10 {
output.push('0');
}
output.push_str(itoa::Buffer::new().format(second));
output.push('-');
output.push(am_or_pm);
output.push_str("M-");
output.push_str(itoa::Buffer::new().format(time.year()));
output.push('/');
output.push_str(itoa::Buffer::new().format(time.month() as u8));
output.push('/');
output.push_str(itoa::Buffer::new().format(time.day()));
}
if color {
output.push_str("\x1b[m");
}
}
}