#![warn(clippy::pedantic, clippy::style, clippy::nursery)]
use std::{
fmt,
path::PathBuf,
sync::atomic::{AtomicBool, Ordering},
};
pub mod error;
use error::Error;
pub use tracing;
pub use tracing::{debug, error, info, trace, warn, Level};
use tracing_appender::{self};
use tracing_subscriber::fmt::{format::FmtSpan, time};
use anyhow::{bail, Result};
pub const DEFAULT_LOG_LEVEL: Level = Level::INFO;
pub const DEFAULT_LOG_DIR: &str = "/dev/null";
static INITIALIZED: AtomicBool = AtomicBool::new(false);
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
#[allow(clippy::struct_excessive_bools)] pub struct LoggerBuilder {
log_to_file: bool,
log_dir: PathBuf,
ansi: bool,
display_filename: bool,
display_level: bool,
display_target: bool,
max_level: Level,
display_thread_ids: bool,
display_thread_names: bool,
display_line_number: bool,
pretty: bool,
show_time: bool,
uptime: bool,
span_events: FmtSpan,
}
impl LoggerBuilder {
pub fn build(self) -> Result<Logger> {
if INITIALIZED.load(Ordering::Relaxed) {
warn!("trying to reinitialize the logger, ignoring");
bail!(Error::Usage("logging is already initialized".to_string()));
}
let subscriber = tracing_subscriber::fmt::Subscriber::builder()
.with_level(self.display_level)
.with_max_level(self.max_level)
.with_ansi(self.ansi)
.with_target(self.display_target)
.with_file(self.display_filename)
.with_thread_ids(self.display_thread_ids)
.with_line_number(self.display_line_number)
.with_thread_names(self.display_thread_names)
.with_span_events(self.span_events);
match (self.log_to_file, self.show_time, self.pretty, self.uptime) {
(true, true, true, true) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.with_timer(time::uptime())
.pretty()
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(true, true, true, false) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.pretty()
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(true, false, true, _) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.without_time()
.pretty()
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(true, true, false, true) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.with_timer(time::uptime())
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(true, true, false, false) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(true, false, false, _) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.without_time()
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, true, true, true) => {
let subscriber = subscriber.pretty().with_timer(time::uptime()).finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, true, true, false) => {
let subscriber = subscriber.pretty().with_timer(time::uptime()).finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, false, true, _) => {
let subscriber = subscriber.without_time().pretty().finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, true, false, true) => {
let subscriber = subscriber.with_timer(time::uptime()).finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, true, false, false) => {
let subscriber = subscriber.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, false, false, _) => {
let subscriber = subscriber.without_time().finish();
tracing::subscriber::set_global_default(subscriber)?;
}
}
INITIALIZED.store(true, Ordering::Relaxed);
Ok(Logger {})
}
#[must_use]
pub const fn log_to_file(mut self, log_to_file: bool) -> Self {
self.log_to_file = log_to_file;
self
}
#[must_use]
pub fn log_dir(mut self, log_dir: PathBuf) -> Self {
self.log_dir = log_dir;
self
}
#[must_use]
pub const fn ansi(mut self, ansi: bool) -> Self {
self.ansi = ansi;
self
}
#[must_use]
pub const fn display_filename(mut self, display_filename: bool) -> Self {
self.display_filename = display_filename;
self
}
#[must_use]
pub const fn display_time(mut self, show_time: bool) -> Self {
self.show_time = show_time;
self
}
#[must_use]
pub const fn display_level(mut self, display_level: bool) -> Self {
self.display_level = display_level;
self
}
#[must_use]
pub const fn display_target(mut self, display_target: bool) -> Self {
self.display_target = display_target;
self
}
#[must_use]
pub const fn display_thread_ids(mut self, display_thread_ids: bool) -> Self {
self.display_thread_ids = display_thread_ids;
self
}
#[must_use]
pub const fn display_thread_names(mut self, display_thread_names: bool) -> Self {
self.display_thread_names = display_thread_names;
self
}
#[must_use]
pub const fn display_line_number(mut self, display_line_number: bool) -> Self {
self.display_line_number = display_line_number;
self
}
#[must_use]
pub const fn pretty(mut self, pretty: bool) -> Self {
self.pretty = pretty;
self
}
#[must_use]
pub const fn uptime(mut self, uptime: bool) -> Self {
self.uptime = uptime;
self
}
#[must_use]
pub const fn set_level(mut self, max_level: Level) -> Self {
self.max_level = max_level;
self
}
#[must_use]
pub const fn span_events(mut self, span_events: FmtSpan) -> Self {
self.span_events = span_events;
self
}
}
impl Default for LoggerBuilder {
fn default() -> Self {
Self {
log_to_file: false,
log_dir: PathBuf::from(DEFAULT_LOG_DIR),
ansi: true,
display_filename: false,
display_level: true,
display_target: false,
max_level: DEFAULT_LOG_LEVEL,
display_thread_ids: false,
display_thread_names: false,
display_line_number: false,
pretty: false,
show_time: true,
uptime: false,
span_events: FmtSpan::NONE,
}
}
}
pub struct Logger;
impl Logger {
#[must_use]
pub fn builder() -> LoggerBuilder {
LoggerBuilder::default()
}
pub fn error<T>(&self, printable: T)
where
T: fmt::Display,
{
error!("{}", printable);
}
pub fn warn<T>(&self, printable: T)
where
T: fmt::Display,
{
warn!("{}", printable);
}
pub fn info<T>(&self, printable: T)
where
T: fmt::Display,
{
info!("{}", printable);
}
pub fn debug<T>(&self, printable: T)
where
T: fmt::Display,
{
debug!("{}", printable);
}
pub fn trace<T>(&self, printable: T)
where
T: fmt::Display,
{
trace!("{}", printable);
}
}
impl fmt::Debug for Logger {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Logger: {{initialized: {}}} ",
INITIALIZED.load(Ordering::Relaxed)
)
}
}
impl Default for Logger {
fn default() -> Self {
LoggerBuilder::default()
.build()
.expect("building a Logger failed")
}
}
fn new_file_appender(log_dir: PathBuf) -> tracing_appender::rolling::RollingFileAppender {
tracing_appender::rolling::daily(
log_dir,
format!(
"{}.log",
libpt_core::get_crate_name().unwrap_or_else(|| "logfile".to_string())
),
)
}