use crate::{JsonExtension, WriteFn};
use chrono::Local;
use owo_colors::{colors::CustomColor, FgColorDisplay, OwoColorize, Stream};
use std::{borrow::Cow, fmt::Write};
use tracing::{Event, Level, Metadata};
use tracing_subscriber::registry::{LookupSpan, SpanRef};
mod visitor;
const DEFAULT_TIMESTAMP: &str = "%B %d, %G - %H:%M:%S %p";
pub struct Writer {
pub print_timestamp: bool,
pub timestamp_fmt: Cow<'static, str>,
pub print_module: bool,
pub print_thread: bool,
pub print_level: bool,
pub emit_spans: bool,
pub stream: Stream,
pub colors: bool,
}
impl Default for Writer {
fn default() -> Self {
Self {
print_timestamp: true,
timestamp_fmt: Cow::Borrowed(DEFAULT_TIMESTAMP),
print_thread: true,
print_module: true,
print_level: true,
emit_spans: true,
stream: Stream::Stdout,
colors: true,
}
}
}
impl Writer {
pub fn with_timestamp(mut self, yes: bool) -> Self {
self.print_timestamp = yes;
self
}
pub fn with_thread_name(mut self, yes: bool) -> Self {
self.print_thread = yes;
self
}
pub fn with_module(mut self, yes: bool) -> Self {
self.print_module = yes;
self
}
pub fn with_level(mut self, yes: bool) -> Self {
self.print_level = yes;
self
}
pub fn emit_spans(mut self, yes: bool) -> Self {
self.emit_spans = yes;
self
}
pub fn with_stream<I: Into<Stream>>(mut self, stream: I) -> Self {
self.stream = stream.into();
self
}
pub fn with_colors(mut self, yes: bool) -> Self {
self.colors = yes;
self
}
pub fn with_timestamp_fmt<S: Into<Cow<'static, str>>>(mut self, fmt: S) -> Self {
self.timestamp_fmt = fmt.into();
self
}
}
impl<L: for<'a> LookupSpan<'a>> WriteFn<L> for Writer {
fn buffer(&self, event: &Event, metadata: &Metadata, spans: Vec<SpanRef<'_, L>>) -> String {
self.write(event, metadata, spans)
}
}
impl Writer {
pub(crate) fn write<L: for<'a> LookupSpan<'a>>(
&self,
event: &Event<'_>,
metadata: &Metadata<'_>,
spans: Vec<SpanRef<'_, L>>,
) -> String {
let mut buf = String::new();
if self.print_timestamp {
self.write_timestamp(&mut buf);
let _ = write!(buf, " ");
}
if self.print_level {
self.write_level(metadata, &mut buf);
let _ = write!(buf, " ");
}
if self.print_module || self.print_thread {
self.print_metadata(metadata, &mut buf);
let _ = write!(buf, " ");
}
if self.emit_spans && !spans.is_empty() {
self.print_spans(&spans, &mut buf);
}
let mut visitor = visitor::Visitor {
result: Ok(()),
writer: &mut buf,
colors: self.colors,
stream: self.stream,
};
event.record(&mut visitor);
buf
}
pub(crate) fn write_timestamp<W: Write>(&self, buf: &mut W) {
let now = Local::now().format(&self.timestamp_fmt);
if self.colors {
let _ = write!(
buf,
"{}",
now.if_supports_color(self.stream, |txt| txt.fg_rgb::<134, 134, 134>())
);
return;
}
let _ = write!(buf, "{now}");
}
pub(crate) fn write_level<W: Write>(&self, metadata: &Metadata<'_>, buf: &mut W) {
if self.colors {
let level = metadata.level();
let level = match *level {
Level::TRACE => format!("{:<5}", level.as_str())
.if_supports_color(self.stream, |txt| txt.fg_rgb::<163, 182, 138>())
.bold()
.to_string(),
Level::DEBUG => format!("{:<5}", level.as_str())
.if_supports_color(self.stream, |txt| txt.fg_rgb::<148, 224, 232>())
.bold()
.to_string(),
Level::ERROR => format!("{:<5}", level.as_str())
.if_supports_color(self.stream, |txt| txt.fg_rgb::<153, 75, 104>())
.bold()
.to_string(),
Level::WARN => format!("{:<5}", level.as_str())
.if_supports_color(self.stream, |txt| txt.fg_rgb::<243, 243, 134>())
.bold()
.to_string(),
Level::INFO => format!("{:<5}", level.as_str())
.if_supports_color(self.stream, |txt| txt.fg_rgb::<178, 157, 243>())
.bold()
.to_string(),
};
let _ = write!(buf, "{level}");
return;
}
let _ = write!(buf, "{}", metadata.level());
}
pub(crate) fn print_metadata<W: Write>(&self, metadata: &Metadata<'_>, buf: &mut W) {
let module = metadata.module_path().unwrap_or("unknown");
let thread = std::thread::current();
let name = thread.name().unwrap_or("main");
if self.colors {
if self.print_module {
let _ = write!(
buf,
"{}",
module.if_supports_color(self.stream, |txt| txt.fg_rgb::<72, 61, 139>())
);
}
let (b1, b2) = (
"ยซ".if_supports_color(self.stream, gray_fg),
"ยป".if_supports_color(self.stream, gray_fg),
);
if self.print_module {
let _ = write!(buf, " ");
}
if self.print_thread {
let _ = write!(
buf,
"{b1}{}{b2}",
name.if_supports_color(self.stream, |txt| txt.fg_rgb::<244, 181, 213>())
);
}
return;
}
if self.print_module {
let _ = write!(buf, "{module}");
let _ = write!(buf, " ");
}
if self.print_thread {
let _ = write!(buf, "ยซ{name}ยป");
}
}
pub(crate) fn print_spans<L: for<'a> LookupSpan<'a>, W: Write>(&self, spans: &[SpanRef<'_, L>], buf: &mut W) {
for span in spans {
if self.colors {
let _ = write!(buf, "{}", "{".if_supports_color(self.stream, |txt| txt.bold()));
} else {
let _ = write!(buf, "{{");
}
let extensions = span.extensions();
let json = extensions.get::<JsonExtension>().unwrap();
let _ = write!(buf, "{}", span.name());
let mut first = true;
for (key, value) in &json.0 {
if first {
let _ = write!(buf, ": {key}={value}");
first = false;
continue;
}
let _ = write!(buf, " {key}={value}");
}
if self.colors {
let _ = write!(buf, "{}", "}".if_supports_color(self.stream, |txt| txt.bold()));
} else {
let _ = write!(buf, "}}");
}
}
let _ = write!(buf, ": ");
}
}
fn gray_fg<'a>(x: &'a &'a str) -> FgColorDisplay<'a, CustomColor<134, 134, 134>, &'a str> {
x.fg_rgb::<134, 134, 134>()
}
#[cfg(test)]
mod tests {
use super::Writer;
use crate::WriteLayer;
use std::io;
use tracing::{debug, error, error_span, info, info_span, trace, warn};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[test]
fn test_out_default_formatter() {
let _guard = tracing_subscriber::registry()
.with(WriteLayer::new_with(std::io::stdout(), Writer::default()))
.set_default();
trace!("Hello, world!");
debug!(hello = "world", "No.");
let span = info_span!("heck", hello.world = true, abc = "d", num = 42);
span.in_scope(|| {
info!("hi... (in hello scope)");
warn!("woah...");
let err = <io::ErrorKind as Into<io::Error>>::into(io::ErrorKind::InvalidData);
error!(%err, "we are so done for");
});
let span2 = error_span!("hello");
span2.in_scope(|| {
error!("in `hello` span scope");
});
info!("no longer in `hello` span scope");
}
}