use std::collections::VecDeque;
use std::io;
use std::marker::PhantomData;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use indicatif::style::ProgressStyle;
use indicatif::style::ProgressTracker;
use indicatif::ProgressDrawTarget;
use indicatif::ProgressState;
use indicatif::{MultiProgress, ProgressBar};
use tracing_core::span;
use tracing_core::Subscriber;
use tracing_subscriber::fmt::format::DefaultFields;
use tracing_subscriber::fmt::FormatFields;
use tracing_subscriber::fmt::FormattedFields;
use tracing_subscriber::fmt::MakeWriter;
use tracing_subscriber::layer;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::registry::SpanRef;
#[derive(Clone)]
struct IndicatifProgressKey {
message: String,
}
impl ProgressTracker for IndicatifProgressKey {
fn clone_box(&self) -> Box<dyn ProgressTracker> {
Box::new(self.clone())
}
fn tick(&mut self, _: &indicatif::ProgressState, _: std::time::Instant) {}
fn reset(&mut self, _: &indicatif::ProgressState, _: std::time::Instant) {}
fn write(&self, _: &indicatif::ProgressState, w: &mut dyn std::fmt::Write) {
let _ = w.write_str(&self.message);
}
}
#[derive(Clone)]
pub struct IndicatifWriter {
progress_bars: MultiProgress,
}
impl io::Write for IndicatifWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.progress_bars.suspend(|| io::stderr().write(buf))
}
fn flush(&mut self) -> io::Result<()> {
self.progress_bars.suspend(|| io::stderr().flush())
}
}
impl<'a> MakeWriter<'a> for IndicatifWriter {
type Writer = IndicatifWriter;
fn make_writer(&'a self) -> Self::Writer {
self.clone()
}
}
struct IndicatifSpanContext {
progress_bar: Option<ProgressBar>,
parent_progress_bar: Option<ProgressBar>,
span_fields_formatted: Option<String>,
level: u16,
}
struct ProgressBarManager {
mp: MultiProgress,
active_progress_bars: u64,
max_progress_bars: u64,
pending_progress_bars: Arc<AtomicU64>,
pending_spans: VecDeque<span::Id>,
footer_pb: Option<ProgressBar>,
}
impl ProgressBarManager {
fn new(max_progress_bars: u64, footer_progress_style: Option<ProgressStyle>) -> Self {
let pending_progress_bars = Arc::new(AtomicU64::new(0));
Self {
mp: {
let mp = MultiProgress::new();
mp.set_draw_target(ProgressDrawTarget::stderr_with_hz(20));
mp
},
active_progress_bars: 0,
max_progress_bars,
pending_progress_bars: pending_progress_bars.clone(),
pending_spans: VecDeque::new(),
footer_pb: footer_progress_style.map(|style| {
ProgressBar::hidden().with_style(style.with_key(
"pending_progress_bars",
move |_: &ProgressState, writer: &mut dyn std::fmt::Write| {
let _ = write!(
writer,
"{}",
pending_progress_bars.load(std::sync::atomic::Ordering::SeqCst)
);
},
))
}),
}
}
fn decrement_pending_pb(&mut self) {
let prev_val = self
.pending_progress_bars
.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
if prev_val == 1 {
debug_assert!(
self.footer_pb
.as_ref()
.map(|pb| !pb.is_hidden())
.unwrap_or(true),
"footer progress bar was hidden despite there being pending progress bars"
);
if let Some(footer_pb) = self.footer_pb.as_ref() {
footer_pb.finish_and_clear();
self.mp.remove(footer_pb);
footer_pb.disable_steady_tick();
self.mp.set_move_cursor(false);
}
}
}
fn add_pending_pb(&mut self, span_id: &span::Id) {
let prev_val = self
.pending_progress_bars
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
self.pending_spans.push_back(span_id.clone());
if prev_val == 0 {
debug_assert!(
self.footer_pb
.as_ref()
.map(|pb| pb.is_hidden())
.unwrap_or(true),
"footer progress bar was not hidden despite there being no pending progress bars"
);
if let Some(footer_pb) = self.footer_pb.take() {
let pb = self.mp.add(footer_pb);
pb.enable_steady_tick(Duration::from_millis(100));
self.mp.set_move_cursor(true);
self.footer_pb = Some(pb);
}
}
}
fn show_progress_bar(&mut self, pb_span_ctx: &mut IndicatifSpanContext, span_id: &span::Id) {
if self.active_progress_bars < self.max_progress_bars {
let pb = match pb_span_ctx.parent_progress_bar {
Some(ref parent_pb) => self
.mp
.insert_after(parent_pb, pb_span_ctx.progress_bar.take().unwrap()),
None => {
if self
.footer_pb
.as_ref()
.map(|footer_pb| !footer_pb.is_hidden())
.unwrap_or(false)
{
self.mp
.insert_from_back(1, pb_span_ctx.progress_bar.take().unwrap())
} else {
self.mp.add(pb_span_ctx.progress_bar.take().unwrap())
}
}
};
self.active_progress_bars += 1;
pb.enable_steady_tick(Duration::from_millis(100));
pb_span_ctx.progress_bar = Some(pb);
} else {
self.add_pending_pb(span_id);
}
}
fn finish_progress_bar<S>(
&mut self,
pb_span_ctx: &mut IndicatifSpanContext,
ctx: &layer::Context<'_, S>,
) where
S: Subscriber + for<'a> LookupSpan<'a>,
{
let Some(pb) = pb_span_ctx.progress_bar.take() else {
return;
};
if pb.is_hidden() {
self.decrement_pending_pb();
return;
}
pb.finish_and_clear();
self.mp.remove(&pb);
self.active_progress_bars -= 1;
let maybe_next_eligible_span: Option<(span::Id, SpanRef<S>)> = loop {
let Some(span_id) = self.pending_spans.pop_front() else {
break None;
};
match ctx.span(&span_id) {
Some(v) => {
break Some((span_id, v));
}
None => {
continue;
}
}
};
let Some((span_id, next_eligible_span)) = maybe_next_eligible_span else {
return;
};
let mut ext = next_eligible_span.extensions_mut();
let indicatif_span_ctx = ext
.get_mut::<IndicatifSpanContext>()
.expect("No IndicatifSpanContext found; this is a bug");
self.decrement_pending_pb();
self.show_progress_bar(indicatif_span_ctx, &span_id);
}
}
pub struct IndicatifLayer<S, F = DefaultFields> {
pb_manager: Mutex<ProgressBarManager>,
span_field_formatter: F,
progress_style: ProgressStyle,
span_child_prefix_indent: &'static str,
span_child_prefix_symbol: &'static str,
inner: PhantomData<S>,
}
impl<S> IndicatifLayer<S> {
pub fn new() -> Self {
Self::default()
}
}
impl<S> Default for IndicatifLayer<S> {
fn default() -> Self {
Self {
pb_manager: Mutex::new(ProgressBarManager::new(
7,
Some(
ProgressStyle::with_template(
"...and {pending_progress_bars} more not shown above.",
)
.unwrap(),
),
)),
span_field_formatter: DefaultFields::new(),
progress_style: ProgressStyle::with_template(
"{span_child_prefix}{spinner} {span_name}{{{span_fields}}}",
)
.unwrap(),
span_child_prefix_indent: " ",
span_child_prefix_symbol: "↳ ",
inner: PhantomData,
}
}
}
impl<S, F> IndicatifLayer<S, F> {
pub fn get_fmt_writer(&self) -> IndicatifWriter {
IndicatifWriter {
progress_bars: self.pb_manager.lock().unwrap().mp.clone(),
}
}
pub fn with_span_field_formatter<F2>(self, formatter: F2) -> IndicatifLayer<S, F2>
where
F2: for<'writer> FormatFields<'writer> + 'static,
{
IndicatifLayer {
pb_manager: self.pb_manager,
span_field_formatter: formatter,
progress_style: self.progress_style,
span_child_prefix_indent: self.span_child_prefix_indent,
span_child_prefix_symbol: self.span_child_prefix_symbol,
inner: self.inner,
}
}
pub fn with_progress_style(mut self, style: ProgressStyle) -> Self {
self.progress_style = style;
self
}
pub fn with_span_child_prefix_indent(mut self, indent: &'static str) -> Self {
self.span_child_prefix_indent = indent;
self
}
pub fn with_span_child_prefix_symbol(mut self, symbol: &'static str) -> Self {
self.span_child_prefix_symbol = symbol;
self
}
pub fn with_max_progress_bars(
mut self,
max_progress_bars: u64,
footer_style: Option<ProgressStyle>,
) -> Self {
self.pb_manager = Mutex::new(ProgressBarManager::new(max_progress_bars, footer_style));
self
}
}
impl<S, F> layer::Layer<S> for IndicatifLayer<S, F>
where
S: Subscriber + for<'a> LookupSpan<'a>,
F: for<'writer> FormatFields<'writer> + 'static,
{
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: layer::Context<'_, S>) {
let span = ctx
.span(id)
.expect("Span not found in context, this is a bug");
let mut ext = span.extensions_mut();
let mut fields = FormattedFields::<F>::new(String::new());
let _ = self
.span_field_formatter
.format_fields(fields.as_writer(), attrs);
ext.insert(IndicatifSpanContext {
progress_bar: None,
parent_progress_bar: None,
span_fields_formatted: Some(fields.fields),
level: 0,
});
}
fn on_enter(&self, id: &span::Id, ctx: layer::Context<'_, S>) {
let span = ctx
.span(id)
.expect("Span not found in context, this is a bug");
let mut ext = span.extensions_mut();
if let Some(indicatif_ctx) = ext.get_mut::<IndicatifSpanContext>() {
let parent_span = ctx.span_scope(id).and_then(|scope| {
scope.skip(1).find(|span| {
let ext = span.extensions();
ext.get::<IndicatifSpanContext>().is_some()
})
});
let parent_span_ext = parent_span.as_ref().map(|span| span.extensions());
let parent_indicatif_ctx = parent_span_ext
.as_ref()
.map(|ext| ext.get::<IndicatifSpanContext>().unwrap());
let span_name = span.name().to_string();
let span_fields_formatted = indicatif_ctx
.span_fields_formatted
.to_owned()
.unwrap_or_default();
if indicatif_ctx.progress_bar.is_none() {
let span_child_prefix = match parent_indicatif_ctx {
Some(v) => {
indicatif_ctx.level = v.level + 1;
format!(
"{}{}",
self.span_child_prefix_indent
.repeat(indicatif_ctx.level.into()),
self.span_child_prefix_symbol
)
}
None => String::new(),
};
indicatif_ctx.progress_bar = Some(
ProgressBar::hidden().with_style(
self.progress_style
.clone()
.with_key("span_name", IndicatifProgressKey { message: span_name })
.with_key(
"span_fields",
IndicatifProgressKey {
message: span_fields_formatted,
},
)
.with_key(
"span_child_prefix",
IndicatifProgressKey {
message: span_child_prefix,
},
),
),
);
if let Some(parent_indicatif_ctx) = parent_indicatif_ctx {
indicatif_ctx.parent_progress_bar =
Some(parent_indicatif_ctx.progress_bar.to_owned().unwrap());
}
self.pb_manager
.lock()
.unwrap()
.show_progress_bar(indicatif_ctx, id);
}
}
}
fn on_close(&self, id: span::Id, ctx: layer::Context<'_, S>) {
let span = ctx
.span(&id)
.expect("Span not found in context, this is a bug");
let mut ext = span.extensions_mut();
if let Some(indicatif_ctx) = ext.get_mut::<IndicatifSpanContext>() {
self.pb_manager
.lock()
.unwrap()
.finish_progress_bar(indicatif_ctx, &ctx);
}
}
}