Ideally, I would prefer the usual split per logging level, but that seems to be somewhat complex with `tracing_subscriber`, so this simply switches everything over to stderr, so that some of the experiment commands can write results to stdout.
110 lines
3.3 KiB
Rust
110 lines
3.3 KiB
Rust
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<S, N> FormatEvent<S, N> 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();
|
|
}
|