use std::fmt::Result; use ansi_term::Color; use time::format_description::FormatItem; use time::macros::format_description; use time::OffsetDateTime; use tracing::field::Field; use tracing::{Event, Level, Metadata, Subscriber}; use tracing_error::ErrorLayer; use tracing_subscriber::filter::FilterFn; use tracing_subscriber::fmt::format::{debug_fn, Writer}; use tracing_subscriber::fmt::{self, FmtContext, FormatEvent, FormatFields}; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::prelude::*; use tracing_subscriber::registry::LookupSpan; use tracing_subscriber::EnvFilter; pub const TIME_FORMAT: &[FormatItem] = format_description!("[hour]:[minute]:[second]"); pub fn format_fields(w: &mut Writer<'_>, field: &Field, val: &dyn std::fmt::Debug) -> Result { if field.name() == "message" { write!(w, "{:?}", val) } else { Ok(()) } } pub fn filter_fields(metadata: &Metadata<'_>) -> bool { metadata .fields() .iter() .any(|field| field.name() == "message") } pub struct Formatter; impl FormatEvent for Formatter where S: Subscriber + for<'a> LookupSpan<'a>, N: for<'a> FormatFields<'a> + 'static, { fn format_event( &self, ctx: &FmtContext<'_, S, N>, mut writer: Writer<'_>, event: &Event<'_>, ) -> Result { let meta = event.metadata(); let time = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); let time = time.format(TIME_FORMAT).map_err(|_| std::fmt::Error)?; let level = meta.level(); // Sadly, tracing's `Level` is a struct, not an enum, so we can't properly `match` it. let color = if *level == Level::TRACE { Color::Purple } else if *level == Level::DEBUG { Color::Blue } else if *level == Level::INFO { Color::Green } else if *level == Level::WARN { Color::Yellow } else if *level == Level::ERROR { Color::Red } else { unreachable!() }; write!( writer, "[{}] [{:>5}] ", time, color.bold().paint(format!("{}", level)) )?; ctx.field_format().format_fields(writer.by_ref(), event)?; writeln!(writer) } } pub fn create_tracing_subscriber() { let env_layer = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::try_new("info").unwrap()); let (dev_stdout_layer, prod_stdout_layer, filter_layer) = if cfg!(debug_assertions) { let fmt_layer = fmt::layer().pretty().with_writer(std::io::stderr); (Some(fmt_layer), None, None) } else { // Creates a layer that // - only prints events that contain a message // - does not print fields // - does not print spans/targets // - only prints time, not date let fmt_layer = fmt::layer() .with_writer(std::io::stderr) .event_format(Formatter) .fmt_fields(debug_fn(format_fields)); (None, Some(fmt_layer), Some(FilterFn::new(filter_fields))) }; tracing_subscriber::registry() .with(filter_layer) .with(env_layer) .with(dev_stdout_layer) .with(prod_stdout_layer) .with(ErrorLayer::new(fmt::format::Pretty::default())) .init(); }