use std::fmt::Display;
use tokio::sync::mpsc::UnboundedSender;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Level {
Error,
Warn,
Info,
Debug,
Trace,
}
impl Level {
pub fn name(self) -> &'static str {
match self {
Level::Error => "ERROR",
Level::Warn => "WARN",
Level::Info => "INFO",
Level::Debug => "DEBUG",
Level::Trace => "TRACE",
}
}
pub fn tui_prefix(self) -> String {
format!("[{}]", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Sink {
FileOnly,
Both,
}
impl Sink {
pub fn reaches_tui(self) -> bool {
matches!(self, Sink::Both)
}
}
pub fn default_sink(level: Level) -> Sink {
match level {
Level::Error | Level::Warn | Level::Info => Sink::Both,
Level::Debug | Level::Trace => Sink::FileOnly,
}
}
pub struct Log<'a> {
status_tx: Option<&'a UnboundedSender<String>>,
payloads: bool,
}
impl<'a> Log<'a> {
pub fn new(status_tx: Option<&'a UnboundedSender<String>>) -> Self {
Self {
status_tx,
payloads: true,
}
}
pub fn with_payloads(mut self, enabled: bool) -> Self {
self.payloads = enabled;
self
}
pub fn emit(&self, level: Level, sink: Sink, msg: impl Display) {
if !self.payloads && matches!(level, Level::Debug | Level::Trace) {
return;
}
let text = crate::utils::sanitize::multiline(&msg.to_string());
match level {
Level::Error => tracing::error!("{}", text),
Level::Warn => tracing::warn!("{}", text),
Level::Info => tracing::info!("{}", text),
Level::Debug => tracing::debug!("{}", text),
Level::Trace => tracing::trace!("{}", text),
}
if sink.reaches_tui() {
if let Some(tx) = self.status_tx {
let _ = tx.send(format!("{} {}", level.tui_prefix(), text));
}
}
}
pub fn error(&self, msg: impl Display) {
self.emit(Level::Error, Sink::Both, msg);
}
pub fn warn(&self, msg: impl Display) {
self.emit(Level::Warn, Sink::Both, msg);
}
pub fn info(&self, msg: impl Display) {
self.emit(Level::Info, default_sink(Level::Info), msg);
}
pub fn info_to(&self, sink: Sink, msg: impl Display) {
self.emit(Level::Info, sink, msg);
}
pub fn debug(&self, msg: impl Display) {
self.emit(Level::Debug, default_sink(Level::Debug), msg);
}
pub fn debug_to(&self, sink: Sink, msg: impl Display) {
self.emit(Level::Debug, sink, msg);
}
pub fn trace(&self, msg: impl Display) {
self.emit(Level::Trace, default_sink(Level::Trace), msg);
}
pub fn trace_to(&self, sink: Sink, msg: impl Display) {
self.emit(Level::Trace, sink, msg);
}
pub fn payload(&self, label: &str, body: &str, max_bytes: usize) {
self.emit(
Level::Trace,
Sink::FileOnly,
format!(
"{}: {}",
label,
crate::utils::truncate_for_log(body, max_bytes)
),
);
}
}