Reimplement logging by utilizing the tracing crate
Signed-off-by: trivernis <trivernis@protonmail.com>i18n
parent
f1bbb2626c
commit
052d176f4a
@ -0,0 +1,146 @@
|
||||
use colored::Colorize;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tracing_subscriber::registry::LookupSpan;
|
||||
|
||||
use tracing::field::Visit;
|
||||
use tracing::{span, Level, Metadata, Subscriber};
|
||||
use tracing_subscriber::Layer;
|
||||
|
||||
use super::handler::LogHandler;
|
||||
use super::Verbosity;
|
||||
|
||||
const ENABLED_MODULES: &[&str] = &["ame"];
|
||||
|
||||
pub struct AmeFormatLayer {
|
||||
logger: Arc<LogHandler>,
|
||||
}
|
||||
|
||||
impl AmeFormatLayer {
|
||||
pub fn new(logger: Arc<LogHandler>) -> Self {
|
||||
Self { logger }
|
||||
}
|
||||
|
||||
fn is_level_loggable(&self, level: &Level) -> bool {
|
||||
self.logger.is_loggable(Verbosity::from_level(level))
|
||||
}
|
||||
|
||||
fn is_enabled(&self, metadata: &Metadata) -> bool {
|
||||
let level = metadata.level();
|
||||
if !self.is_level_loggable(level) {
|
||||
false
|
||||
} else if let Some(module_path) = metadata.module_path() {
|
||||
ENABLED_MODULES.iter().any(|m| module_path.starts_with(m))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn log(&self, msg: String, level: &Level) {
|
||||
match Verbosity::from_level(level) {
|
||||
Verbosity::Error => self.logger.log_error(msg),
|
||||
Verbosity::Warning => self.logger.log_warning(msg),
|
||||
Verbosity::Info => self.logger.log_info(msg),
|
||||
Verbosity::Debug => self.logger.log_debug(msg),
|
||||
Verbosity::Trace => self.logger.log_trace(msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for AmeFormatLayer {
|
||||
/// When entering a span
|
||||
fn on_new_span(
|
||||
&self,
|
||||
attrs: &span::Attributes<'_>,
|
||||
_id: &span::Id,
|
||||
_ctx: tracing_subscriber::layer::Context<'_, S>,
|
||||
) {
|
||||
let metadata = attrs.metadata();
|
||||
if self.is_enabled(metadata) {
|
||||
let mut visitor = ValueDebugStorage::default();
|
||||
attrs.record(&mut visitor);
|
||||
let fields: Vec<String> = visitor
|
||||
.values
|
||||
.into_iter()
|
||||
.map(|(k, v)| format!("{k} = {v}"))
|
||||
.collect();
|
||||
let mut fields_str = fields.join("\n ");
|
||||
|
||||
if !fields_str.is_empty() {
|
||||
fields_str = format!("\n {fields_str}");
|
||||
}
|
||||
|
||||
if let Some(module) = metadata.module_path() {
|
||||
self.log(
|
||||
format!(
|
||||
"{} {}::{} {}",
|
||||
"ENTER".italic(),
|
||||
module,
|
||||
metadata.name(),
|
||||
fields_str.dimmed()
|
||||
),
|
||||
metadata.level(),
|
||||
)
|
||||
} else {
|
||||
self.log(
|
||||
format!(
|
||||
"{} {} {}",
|
||||
"ENTER".italic(),
|
||||
metadata.name(),
|
||||
fields_str.dimmed()
|
||||
),
|
||||
metadata.level(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_exit(&self, id: &span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
|
||||
let span = ctx.span(id).unwrap();
|
||||
let metadata = span.metadata();
|
||||
|
||||
if self.is_enabled(metadata) {
|
||||
if let Some(module) = metadata.module_path() {
|
||||
self.log(
|
||||
format!("{} {}::{}", "EXIT".italic(), module, metadata.name(),),
|
||||
metadata.level(),
|
||||
);
|
||||
} else {
|
||||
self.log(
|
||||
format!("{} {}", "EXIT".italic(), metadata.name()),
|
||||
metadata.level(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&self,
|
||||
event: &tracing::Event<'_>,
|
||||
_ctx: tracing_subscriber::layer::Context<'_, S>,
|
||||
) {
|
||||
let metadata = event.metadata();
|
||||
|
||||
if self.is_enabled(metadata) {
|
||||
let mut visitor = ValueDebugStorage::default();
|
||||
event.record(&mut visitor);
|
||||
let mut values = visitor.values;
|
||||
|
||||
if let Some(msg) = values.remove("message") {
|
||||
self.log(msg, metadata.level())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ValueDebugStorage {
|
||||
pub values: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Visit for ValueDebugStorage {
|
||||
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
|
||||
self.values
|
||||
.insert(field.name().to_string(), format!("{:?}", value));
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
use colored::Colorize;
|
||||
use indicatif::{MultiProgress, ProgressBar};
|
||||
use std::{
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::uwu;
|
||||
use dialoguer::Confirm;
|
||||
|
||||
use super::Verbosity;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
const OK_SYMBOL: &str = "❖";
|
||||
const ERR_SYMBOL: &str = "X";
|
||||
const WARN_SYMBOL: &str = "!";
|
||||
const DEBUG_SYMBOL: &str = "⌘";
|
||||
const TRACE_SYMBOL: &str = "🗲";
|
||||
const PROMPT_SYMBOL: &str = "?";
|
||||
|
||||
pub struct LogHandler {
|
||||
level: Arc<RwLock<Verbosity>>,
|
||||
output_type: Arc<RwLock<OutputType>>,
|
||||
uwu_enabled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Default for LogHandler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
level: Arc::new(RwLock::new(Verbosity::Info)),
|
||||
output_type: Arc::new(RwLock::new(OutputType::Stderr)),
|
||||
uwu_enabled: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub enum OutputType {
|
||||
Stdout,
|
||||
Stderr,
|
||||
MultiProgress(Arc<MultiProgress>),
|
||||
Progress(Arc<ProgressBar>),
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub enum PromptDefault {
|
||||
Yes,
|
||||
No,
|
||||
None,
|
||||
}
|
||||
|
||||
impl LogHandler {
|
||||
pub fn log_error(&self, msg: String) {
|
||||
if self.is_loggable(Verbosity::Error) {
|
||||
let msg = self.preformat_msg(msg);
|
||||
let msg = format!("{} {}", ERR_SYMBOL.red().bold(), msg.bold().red());
|
||||
self.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_warning(&self, msg: String) {
|
||||
if self.is_loggable(Verbosity::Warning) {
|
||||
let msg = self.preformat_msg(msg);
|
||||
let msg = format!("{} {}", WARN_SYMBOL.yellow(), msg.yellow().bold());
|
||||
self.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_info(&self, msg: String) {
|
||||
if self.is_loggable(Verbosity::Info) {
|
||||
let msg = self.preformat_msg(msg);
|
||||
let msg = format!("{} {}", OK_SYMBOL.purple(), msg.bold());
|
||||
self.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_debug(&self, msg: String) {
|
||||
if self.is_loggable(Verbosity::Debug) {
|
||||
let msg = self.preformat_msg(msg);
|
||||
let msg = format!("{} {}", DEBUG_SYMBOL.blue(), msg);
|
||||
|
||||
self.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_trace(&self, msg: String) {
|
||||
if self.is_loggable(Verbosity::Trace) {
|
||||
let msg = self.preformat_msg(msg);
|
||||
let msg = format!("{} {}", TRACE_SYMBOL.cyan(), msg.dimmed());
|
||||
self.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prompts the user with a question and a default selection
|
||||
pub fn prompt(&self, question: String, p_default: PromptDefault) -> bool {
|
||||
let question = self.preformat_msg(question);
|
||||
let question = format!("{} {}", PROMPT_SYMBOL.purple(), question.bold());
|
||||
let mut confirm = Confirm::new();
|
||||
confirm.with_prompt(question);
|
||||
|
||||
match p_default {
|
||||
PromptDefault::Yes => {
|
||||
confirm.default(true);
|
||||
}
|
||||
PromptDefault::No => {
|
||||
confirm.default(false);
|
||||
}
|
||||
PromptDefault::None => {}
|
||||
}
|
||||
confirm.interact().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_verbosity(&self, level: Verbosity) {
|
||||
(*self.level.write()) = level;
|
||||
}
|
||||
|
||||
pub fn reset_output_type(&self) {
|
||||
self.set_output_type(OutputType::Stdout);
|
||||
}
|
||||
|
||||
/// Creates a new progress spinner and registers it on the log handler
|
||||
pub fn new_progress_spinner(&self) -> Arc<ProgressBar> {
|
||||
let progress_bar = ProgressBar::new_spinner().with_message("Scanning for pacnew files");
|
||||
progress_bar.enable_steady_tick(Duration::from_millis(250));
|
||||
let pb = Arc::new(progress_bar);
|
||||
self.set_progress_bar(pb.clone());
|
||||
|
||||
pb
|
||||
}
|
||||
|
||||
/// Registeres a progress bar on the log handler
|
||||
pub fn set_progress_bar(&self, pb: Arc<ProgressBar>) {
|
||||
self.set_output_type(OutputType::Progress(pb))
|
||||
}
|
||||
|
||||
/// Sets the output type of the log handler to either stdout/stderr or a progress bar
|
||||
pub fn set_output_type(&self, output: OutputType) {
|
||||
(*self.output_type.write()) = output;
|
||||
}
|
||||
|
||||
pub fn set_uwu_enabled(&self, enabled: bool) {
|
||||
self.uwu_enabled
|
||||
.store(enabled, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub(crate) fn is_loggable(&self, level: Verbosity) -> bool {
|
||||
(*self.level.read()) >= level
|
||||
}
|
||||
|
||||
fn preformat_msg(&self, msg: String) -> String {
|
||||
let msg = self.apply_uwu(msg);
|
||||
let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2)
|
||||
.subsequent_indent(" ");
|
||||
|
||||
textwrap::wrap(&msg, opts).join("\n")
|
||||
}
|
||||
|
||||
fn apply_uwu(&self, msg: String) -> String {
|
||||
if self.uwu_enabled.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
uwu!(msg)
|
||||
} else {
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
||||
fn log(&self, msg: String) {
|
||||
let output_type = self.output_type.read();
|
||||
match &*output_type {
|
||||
OutputType::Stdout => println!("{}", msg),
|
||||
OutputType::Stderr => eprintln!("{}", msg),
|
||||
OutputType::MultiProgress(m) => {
|
||||
let _ = m.println(msg);
|
||||
}
|
||||
OutputType::Progress(p) => p.println(msg),
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
|
||||
use tracing_subscriber::Registry;
|
||||
|
||||
mod fmt_layer;
|
||||
use fmt_layer::AmeFormatLayer;
|
||||
|
||||
use crate::internal::uwu_enabled;
|
||||
|
||||
use self::handler::LogHandler;
|
||||
pub mod handler;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Verbosity {
|
||||
#[allow(dead_code)]
|
||||
Error = 0,
|
||||
#[allow(dead_code)]
|
||||
Warning = 1,
|
||||
Info = 2,
|
||||
Debug = 3,
|
||||
Trace = 4,
|
||||
}
|
||||
|
||||
impl From<usize> for Verbosity {
|
||||
fn from(num_verbosity: usize) -> Self {
|
||||
match num_verbosity {
|
||||
0 => Self::Info,
|
||||
1 => Self::Debug,
|
||||
2 => Self::Trace,
|
||||
_ => Self::Info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Verbosity {
|
||||
fn from_level(l: &Level) -> Self {
|
||||
match *l {
|
||||
Level::ERROR => Self::Error,
|
||||
Level::WARN => Self::Warning,
|
||||
Level::INFO => Self::Info,
|
||||
Level::DEBUG => Self::Debug,
|
||||
Level::TRACE => Self::Trace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the tracing logger
|
||||
/// Can be used for debug purposes _or_ verbose output
|
||||
pub fn init_logger(verbosity: Verbosity) {
|
||||
let logger = get_logger();
|
||||
logger.set_verbosity(verbosity);
|
||||
logger.set_uwu_enabled(uwu_enabled());
|
||||
let ame_layer = AmeFormatLayer::new(logger);
|
||||
|
||||
let subscriber = Registry::default().with(ame_layer);
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
}
|
||||
|
||||
/// Returns the global logger instance
|
||||
pub fn get_logger() -> Arc<LogHandler> {
|
||||
lazy_static! {
|
||||
static ref LOGGER: Arc<LogHandler> = Arc::new(LogHandler::default());
|
||||
}
|
||||
|
||||
Arc::clone(&LOGGER)
|
||||
}
|
Loading…
Reference in New Issue