tracing_indicatif/
lib.rs

1//! A [tracing](https://docs.rs/tracing/latest/tracing/) layer that automatically creates and manages [indicatif](https://docs.rs/indicatif/latest/indicatif/index.html) progress bars for active spans.
2//!
3//! Progress bars are a great way to make your CLIs feel more responsive. However,
4//! adding and managing progress bars in your libraries can be invasive, unergonomic,
5//! and difficult to keep track of.
6//!
7//! This library aims to make it easy to show progress bars for your CLI by tying
8//! progress bars to [tracing spans](https://docs.rs/tracing/latest/tracing/#spans).
9//! For CLIs/libraries already using tracing spans, this allow for a dead simple (3
10//! line) code change to enable a smooth progress bar experience for your program.
11//! This eliminates having to have code in your libraries to manually manage
12//! progress bar instances.
13//!
14//! This ends up working quite well as progress bars are fundamentally tracking the
15//! lifetime of some "span" (whether that "span" is defined explicitly or implicitly),
16//! so might as well make that relationship explicit.
17//!
18//! An easy quick start for this crate is:
19//! ```rust
20//! use tracing_indicatif::IndicatifLayer;
21//! use tracing_subscriber::layer::SubscriberExt;
22//! use tracing_subscriber::util::SubscriberInitExt;
23//!
24//! let indicatif_layer = IndicatifLayer::new();
25//!
26//! tracing_subscriber::registry()
27//!     .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer()))
28//!     .with(indicatif_layer)
29//!     .init();
30//! ```
31//! See [`IndicatifLayer`] for additional documentation.
32//!
33//! See the [`examples`](https://github.com/emersonford/tracing-indicatif/tree/main/examples) folder for examples of how to customize the layer / progress bar
34//! appearance.
35//!
36//! Note: it is highly recommended you pass `indicatif_layer.get_stderr_writer()` or
37//! `indicatif_layer.get_stdout_writer()` to your `fmt::layer()` (depending on where you want to
38//! emit tracing logs) to prevent progress bars from clobbering any console logs.
39use std::any::TypeId;
40use std::marker::PhantomData;
41use std::sync::Mutex;
42
43use indicatif::MultiProgress;
44use indicatif::ProgressBar;
45/// Re-export of [`indicatif`]'s style module for ease of use.
46pub use indicatif::style;
47use indicatif::style::ProgressStyle;
48use indicatif::style::ProgressTracker;
49use tracing_core::Subscriber;
50use tracing_core::span;
51use tracing_subscriber::fmt::FormatFields;
52use tracing_subscriber::fmt::FormattedFields;
53use tracing_subscriber::fmt::format::DefaultFields;
54use tracing_subscriber::layer;
55use tracing_subscriber::registry::LookupSpan;
56
57pub mod filter;
58mod pb_manager;
59pub mod span_ext;
60pub mod util;
61pub mod writer;
62
63use pb_manager::ProgressBarManager;
64pub use pb_manager::TickSettings;
65#[doc(inline)]
66pub use writer::IndicatifWriter;
67
68#[derive(Clone)]
69struct IndicatifProgressKey {
70    message: String,
71}
72
73impl ProgressTracker for IndicatifProgressKey {
74    fn clone_box(&self) -> Box<dyn ProgressTracker> {
75        Box::new(self.clone())
76    }
77
78    fn tick(&mut self, _: &indicatif::ProgressState, _: std::time::Instant) {}
79
80    fn reset(&mut self, _: &indicatif::ProgressState, _: std::time::Instant) {}
81
82    fn write(&self, _: &indicatif::ProgressState, w: &mut dyn std::fmt::Write) {
83        let _ = w.write_str(&self.message);
84    }
85}
86
87// Suppose we have a [Span] (maybe gotten via [Span::current]) and want access to our
88// [IndicatifLayer] instance from it. The way to do this would be something like
89// ```
90// span.with_subscriber(|(id, subscriber)| {
91//   let maybe_layer = subscriber.downcast_ref::<IndicatifLayer<S, F>>();
92//   ...
93// });
94// ```
95// but this has the problem that, because `IndicatifLayer` has generic params, we need to pass
96// a concrete type `S` and `F` to that `downcast_ref` call. And the callsite doesn't know what
97// those concrete types are.
98//
99// Therefore, we use this `WithContext` struct (along with the defined `downcast_raw` method) to do
100// a form of indirection to something that does already know (or "remembers") what those concrete
101// types `S` and `F` are, so the callsite doesn't need to care about it.
102//
103// This doesn't actually return a reference to our [IndicatifLayer] instance as we only care about
104// the associated span data, so we just pass that to the corresponding `fn`.
105//
106// See:
107// * https://github.com/tokio-rs/tracing/blob/a0126b2e2d465e8e6d514acdf128fcef5b863d27/tracing-error/src/subscriber.rs#L32
108// * https://github.com/tokio-rs/tracing/blob/a0126b2e2d465e8e6d514acdf128fcef5b863d27/tracing-opentelemetry/src/subscriber.rs#L74
109#[allow(clippy::type_complexity)]
110pub(crate) struct WithContext(
111    fn(&tracing::Dispatch, &span::Id, f: &mut dyn FnMut(&mut IndicatifSpanContext)),
112);
113
114#[allow(clippy::type_complexity)]
115pub(crate) struct WithStderrWriter(
116    fn(&tracing::Dispatch, f: &mut dyn FnMut(IndicatifWriter<writer::Stderr>)),
117);
118
119#[allow(clippy::type_complexity)]
120pub(crate) struct WithStdoutWriter(
121    fn(&tracing::Dispatch, f: &mut dyn FnMut(IndicatifWriter<writer::Stdout>)),
122);
123
124#[allow(clippy::type_complexity)]
125pub(crate) struct WithMultiProgress(fn(&tracing::Dispatch, f: &mut dyn FnMut(MultiProgress)));
126
127impl WithContext {
128    pub(crate) fn with_context(
129        &self,
130        dispatch: &tracing::Dispatch,
131        id: &span::Id,
132        mut f: impl FnMut(&mut IndicatifSpanContext),
133    ) {
134        (self.0)(dispatch, id, &mut f)
135    }
136}
137
138impl WithStderrWriter {
139    pub(crate) fn with_context(
140        &self,
141        dispatch: &tracing::Dispatch,
142        mut f: impl FnMut(IndicatifWriter<writer::Stderr>),
143    ) {
144        (self.0)(dispatch, &mut f)
145    }
146}
147
148impl WithStdoutWriter {
149    pub(crate) fn with_context(
150        &self,
151        dispatch: &tracing::Dispatch,
152        mut f: impl FnMut(IndicatifWriter<writer::Stdout>),
153    ) {
154        (self.0)(dispatch, &mut f)
155    }
156}
157
158impl WithMultiProgress {
159    pub(crate) fn with_context(
160        &self,
161        dispatch: &tracing::Dispatch,
162        mut f: impl FnMut(MultiProgress),
163    ) {
164        (self.0)(dispatch, &mut f)
165    }
166}
167
168#[derive(Default)]
169struct ProgressBarInitSettings {
170    style: Option<ProgressStyle>,
171    len: Option<u64>,
172    pos: Option<u64>,
173    message: Option<String>,
174}
175
176struct IndicatifSpanContext {
177    // If this progress bar is `Some(pb)` and `pb.is_hidden`, it means the progress bar is queued.
178    // We start the progress bar in hidden mode so things like `elapsed` are accurate.
179    //
180    // If this progress bar is `None`, it means the span has not yet been entered.
181    progress_bar: Option<ProgressBar>,
182    // If `Some`, the progress bar will use this style when the span is entered for the first time.
183    pb_init_settings: ProgressBarInitSettings,
184    // Notes:
185    // * A parent span cannot close before its child spans, so if a parent span has a progress bar,
186    //   that parent progress bar's lifetime will be greater than this span's progress bar.
187    // * The ProgressBar is just a wrapper around `Arc`, so cloning and tracking it here is fine.
188    parent_progress_bar: Option<ProgressBar>,
189    // This is only `Some` if we have some parent with a progress bar.
190    parent_span: Option<span::Id>,
191    // Fields to be passed to the progress bar as keys.
192    span_fields_formatted: Option<String>,
193    span_name: String,
194    span_child_prefix: String,
195    // Used to quickly compute a child span's prefix without having to traverse up the entire span
196    // scope.
197    level: u16,
198    // If `Some`, this is the message that will be displayed when the progress bar is finished.
199    finish_message: Option<String>,
200}
201
202impl IndicatifSpanContext {
203    fn add_keys_to_style(&self, style: ProgressStyle) -> ProgressStyle {
204        style
205            .with_key(
206                "span_name",
207                IndicatifProgressKey {
208                    message: self.span_name.clone(),
209                },
210            )
211            .with_key(
212                "span_fields",
213                IndicatifProgressKey {
214                    message: self.span_fields_formatted.to_owned().unwrap_or_default(),
215                },
216            )
217            .with_key(
218                "span_child_prefix",
219                IndicatifProgressKey {
220                    message: self.span_child_prefix.clone(),
221                },
222            )
223    }
224
225    fn make_progress_bar(&mut self, default_style: &ProgressStyle) {
226        if self.progress_bar.is_none() {
227            let pb = ProgressBar::hidden().with_style(
228                self.pb_init_settings
229                    .style
230                    .take()
231                    .unwrap_or_else(|| self.add_keys_to_style(default_style.clone())),
232            );
233
234            if let Some(len) = self.pb_init_settings.len.take() {
235                pb.set_length(len);
236            }
237
238            if let Some(msg) = self.pb_init_settings.message.take() {
239                pb.set_message(msg);
240            }
241
242            if let Some(pos) = self.pb_init_settings.pos.take() {
243                pb.set_position(pos);
244            }
245
246            self.progress_bar = Some(pb);
247        }
248    }
249
250    fn set_progress_bar_style(&mut self, style: ProgressStyle) {
251        if let Some(ref pb) = self.progress_bar {
252            pb.set_style(self.add_keys_to_style(style));
253        } else {
254            self.pb_init_settings.style = Some(self.add_keys_to_style(style));
255        }
256    }
257
258    fn set_progress_bar_length(&mut self, len: u64) {
259        if let Some(ref pb) = self.progress_bar {
260            pb.set_length(len);
261        } else {
262            self.pb_init_settings.len = Some(len);
263        }
264    }
265
266    fn set_progress_bar_position(&mut self, pos: u64) {
267        if let Some(ref pb) = self.progress_bar {
268            pb.set_position(pos);
269        } else {
270            self.pb_init_settings.pos = Some(pos);
271        }
272    }
273
274    fn set_progress_bar_message(&mut self, msg: String) {
275        if let Some(ref pb) = self.progress_bar {
276            pb.set_message(msg);
277        } else {
278            self.pb_init_settings.message = Some(msg);
279        }
280    }
281
282    fn inc_progress_bar_position(&mut self, pos: u64) {
283        if let Some(ref pb) = self.progress_bar {
284            pb.inc(pos);
285        } else if let Some(ref mut pb_pos) = self.pb_init_settings.pos {
286            *pb_pos += pos;
287        } else {
288            // indicatif defaults position to 0, so copy that behavior.
289            self.pb_init_settings.pos = Some(pos);
290        }
291    }
292
293    fn inc_progress_bar_length(&mut self, len: u64) {
294        if let Some(ref pb) = self.progress_bar {
295            pb.inc_length(len);
296        } else if let Some(ref mut pb_len) = self.pb_init_settings.len {
297            *pb_len += len;
298        }
299    }
300
301    fn progress_bar_tick(&mut self) {
302        if let Some(ref pb) = self.progress_bar {
303            pb.tick()
304        }
305    }
306
307    fn reset_progress_bar(&mut self) {
308        if let Some(ref pb) = self.progress_bar {
309            pb.reset();
310        }
311    }
312
313    fn reset_progress_bar_elapsed(&mut self) {
314        if let Some(ref pb) = self.progress_bar {
315            pb.reset_elapsed();
316        }
317    }
318
319    fn reset_progress_bar_eta(&mut self) {
320        if let Some(ref pb) = self.progress_bar {
321            pb.reset_eta();
322        }
323    }
324
325    fn set_progress_bar_finish_message(&mut self, msg: String) {
326        self.finish_message = Some(msg);
327    }
328}
329
330/// The layer that handles creating and managing indicatif progress bars for active spans. This
331/// layer must be registered with your tracing subscriber to have any effect.
332///
333/// This layer performs no filtering on which spans to show progress bars for. It is expected one
334/// attaches [filters to this
335/// layer](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#filtering-with-layers)
336/// to control which spans actually have progress bars generated for them. See
337/// [`filter::IndicatifFilter`] for a rudimentary filter.
338///
339/// Progress bars will be started the very first time a span is [entered](tracing::Span::enter)
340/// or when one of its child spans is entered for the first time, and will finish when the span
341/// is [closed](tracing_subscriber::Layer::on_close) (including all child spans having closed).
342///
343/// Progress bars are emitted to stderr.
344///
345/// Under the hood, this just uses indicatif's [`MultiProgress`] struct to
346/// manage individual [`ProgressBar`] instances per span.
347pub struct IndicatifLayer<S, F = DefaultFields> {
348    pb_manager: Mutex<ProgressBarManager>,
349    // Allows us to fetch the `MultiProgress` without taking a lock.
350    // Do not mutate `mp` directly, always go through `pb_manager`.
351    mp: MultiProgress,
352    span_field_formatter: F,
353    progress_style: ProgressStyle,
354    span_child_prefix_indent: &'static str,
355    span_child_prefix_symbol: &'static str,
356    get_context: WithContext,
357    get_stderr_writer_context: WithStderrWriter,
358    get_stdout_writer_context: WithStdoutWriter,
359    get_multi_progress_context: WithMultiProgress,
360    inner: PhantomData<S>,
361}
362
363impl<S> IndicatifLayer<S>
364where
365    S: Subscriber + for<'a> LookupSpan<'a>,
366{
367    /// Spawns a progress bar for every tracing span that is received by this layer.
368    ///
369    /// The default settings for this layer are 7 progress bars maximum and progress bars in the
370    /// style of:
371    /// ```text
372    /// ⠄ do_work{val=0}
373    /// ⠄ do_work{val=1}
374    /// ⠄ do_work{val=2}
375    ///   ↳ ⠴ do_sub_work{val=2}
376    ///   ↳ ⠴ do_sub_work{val=2}
377    /// ⠄ do_work{val=3}
378    /// ⠄ do_work{val=4}
379    /// ...and 5 more not shown above.
380    /// ```
381    pub fn new() -> Self {
382        Self::default()
383    }
384}
385
386impl<S> Default for IndicatifLayer<S>
387where
388    S: Subscriber + for<'a> LookupSpan<'a>,
389{
390    fn default() -> Self {
391        let pb_manager = ProgressBarManager::new(
392            7,
393            Some(
394                ProgressStyle::with_template(
395                    "...and {pending_progress_bars} more not shown above.",
396                )
397                .expect("valid template"),
398            ),
399            TickSettings::default(),
400        );
401        let mp = pb_manager.mp.clone();
402
403        Self {
404            pb_manager: Mutex::new(pb_manager),
405            mp,
406            span_field_formatter: DefaultFields::new(),
407            progress_style: ProgressStyle::with_template(
408                "{span_child_prefix}{spinner} {span_name}{{{span_fields}}}",
409            )
410            .expect("valid template"),
411            span_child_prefix_indent: "  ",
412            span_child_prefix_symbol: "↳ ",
413            get_context: WithContext(Self::get_context),
414            get_stderr_writer_context: WithStderrWriter(Self::get_stderr_writer_context),
415            get_stdout_writer_context: WithStdoutWriter(Self::get_stdout_writer_context),
416            get_multi_progress_context: WithMultiProgress(Self::get_multi_progress_context),
417            inner: PhantomData,
418        }
419    }
420}
421
422// pub methods
423impl<S, F> IndicatifLayer<S, F>
424where
425    S: Subscriber + for<'a> LookupSpan<'a>,
426{
427    #[deprecated(since = "0.2.3", note = "use get_stderr_writer() instead")]
428    pub fn get_fmt_writer(&self) -> IndicatifWriter<writer::Stderr> {
429        self.get_stderr_writer()
430    }
431
432    /// Returns the a writer for [`std::io::Stderr`] that ensures its output will not be clobbered by
433    /// active progress bars.
434    ///
435    /// Instead of `eprintln!(...)` prefer `writeln!(indicatif_layer.get_stderr_writer(), ...)`
436    /// instead to ensure your output is not clobbered by active progress bars.
437    ///
438    /// If one wishes tracing logs to be output to stderr, this should be passed into
439    /// [`fmt::Layer::with_writer`](tracing_subscriber::fmt::Layer::with_writer).
440    pub fn get_stderr_writer(&self) -> IndicatifWriter<writer::Stderr> {
441        // `MultiProgress` is merely a wrapper over an `Arc`, so we can clone here.
442        IndicatifWriter::new(self.mp.clone())
443    }
444
445    /// Returns the a writer for [`std::io::Stdout`] that ensures its output will not be clobbered by
446    /// active progress bars.
447    ///
448    /// Instead of `println!(...)` prefer `writeln!(indicatif_layer.get_stdout_writer(), ...)`
449    /// instead to ensure your output is not clobbered by active progress bars.
450    ///
451    /// If one wishes tracing logs to be output to stdout, this should be passed into
452    /// [`fmt::Layer::with_writer`](tracing_subscriber::fmt::Layer::with_writer).
453    pub fn get_stdout_writer(&self) -> IndicatifWriter<writer::Stdout> {
454        // `MultiProgress` is merely a wrapper over an `Arc`, so we can clone here.
455        IndicatifWriter::new(self.mp.clone())
456    }
457
458    /// Set the formatter for span fields, the result of which will be available as the
459    /// progress bar template key `span_fields`.
460    ///
461    /// The default is the [`DefaultFields`] formatter.
462    pub fn with_span_field_formatter<F2>(self, formatter: F2) -> IndicatifLayer<S, F2>
463    where
464        F2: for<'writer> FormatFields<'writer> + 'static,
465    {
466        IndicatifLayer {
467            pb_manager: self.pb_manager,
468            mp: self.mp,
469            span_field_formatter: formatter,
470            progress_style: self.progress_style,
471            span_child_prefix_indent: self.span_child_prefix_indent,
472            span_child_prefix_symbol: self.span_child_prefix_symbol,
473            get_context: WithContext(IndicatifLayer::<S, F2>::get_context),
474            get_stderr_writer_context: WithStderrWriter(
475                IndicatifLayer::<S, F2>::get_stderr_writer_context,
476            ),
477            get_stdout_writer_context: WithStdoutWriter(
478                IndicatifLayer::<S, F2>::get_stdout_writer_context,
479            ),
480            get_multi_progress_context: WithMultiProgress(
481                IndicatifLayer::<S, F2>::get_multi_progress_context,
482            ),
483            inner: self.inner,
484        }
485    }
486
487    /// Override the style used for displayed progress bars.
488    ///
489    /// Two additional keys are available for the progress bar template:
490    /// * `span_fields` - the formatted string of this span's fields
491    /// * `span_name` - the name of the span
492    /// * `span_child_prefix` - a prefix that increase in size according to the number of parents
493    ///   the span has.
494    ///
495    /// The default template is `{span_child_prefix}{spinner} {span_name}{{{span_fields}}}`.
496    pub fn with_progress_style(mut self, style: ProgressStyle) -> Self {
497        self.progress_style = style;
498        self
499    }
500
501    /// Set the indent used to mark the "level" of a given child span's progress bar.
502    ///
503    /// For example, if the given span is two levels deep (iow has two parent spans with progress
504    /// bars), and this is " ", the `{span_child_prefix}` key for this span's progress bar will be
505    /// prefixed with "  ".
506    pub fn with_span_child_prefix_indent(mut self, indent: &'static str) -> Self {
507        self.span_child_prefix_indent = indent;
508        self
509    }
510
511    /// Set the symbol used to denote this is a progress bar from a child span.
512    ///
513    /// This is ultimately concatenated with the child prefix indent to make the
514    /// `span_child_prefix` progress bar key.
515    pub fn with_span_child_prefix_symbol(mut self, symbol: &'static str) -> Self {
516        self.span_child_prefix_symbol = symbol;
517        self
518    }
519
520    /// Set the maximum number of progress bars that will be displayed, and the possible footer
521    /// "progress bar" that displays when there are more progress bars than can be displayed.
522    ///
523    /// `footer_style` dictates the appearance of the footer, and the footer will only appear if
524    /// there are more progress bars than can be displayed. If it is `None`, no footer will be
525    /// displayed. `footer_style` has the following keys available to it:
526    /// * `pending_progress_bars` - the number of progress bars waiting to be shown
527    pub fn with_max_progress_bars(
528        mut self,
529        max_progress_bars: u64,
530        footer_style: Option<ProgressStyle>,
531    ) -> Self {
532        if let Ok(pb_manager) = self.pb_manager.get_mut() {
533            pb_manager.set_max_progress_bars(max_progress_bars, footer_style);
534        }
535
536        self
537    }
538
539    /// Configures how often progress bars are recalcuated and redrawn to the terminal.
540    pub fn with_tick_settings(mut self, tick_settings: TickSettings) -> Self {
541        if let Ok(pb_manager) = self.pb_manager.get_mut() {
542            pb_manager.set_tick_settings(tick_settings);
543        }
544
545        self
546    }
547}
548
549impl<S, F> IndicatifLayer<S, F>
550where
551    S: Subscriber + for<'a> LookupSpan<'a>,
552    F: for<'writer> FormatFields<'writer> + 'static,
553{
554    fn get_context(
555        dispatch: &tracing::Dispatch,
556        id: &span::Id,
557        f: &mut dyn FnMut(&mut IndicatifSpanContext),
558    ) {
559        // The only way `get_context` can be called is if we have an `IndicatifLayer` added to the
560        // expected subscriber, hence why we can `.expect` here.
561        let subscriber = dispatch
562            .downcast_ref::<S>()
563            .expect("subscriber should downcast to expected type; this is a bug!");
564        let span = subscriber
565            .span(id)
566            .expect("Span not found in context, this is a bug");
567
568        let mut ext = span.extensions_mut();
569
570        if let Some(indicatif_ctx) = ext.get_mut::<IndicatifSpanContext>() {
571            f(indicatif_ctx);
572        }
573    }
574
575    fn get_stderr_writer_context(
576        dispatch: &tracing::Dispatch,
577        f: &mut dyn FnMut(IndicatifWriter<writer::Stderr>),
578    ) {
579        let layer = dispatch
580            .downcast_ref::<IndicatifLayer<S, F>>()
581            .expect("subscriber should downcast to expected type; this is a bug!");
582
583        f(layer.get_stderr_writer())
584    }
585
586    fn get_stdout_writer_context(
587        dispatch: &tracing::Dispatch,
588        f: &mut dyn FnMut(IndicatifWriter<writer::Stdout>),
589    ) {
590        let layer = dispatch
591            .downcast_ref::<IndicatifLayer<S, F>>()
592            .expect("subscriber should downcast to expected type; this is a bug!");
593
594        f(layer.get_stdout_writer())
595    }
596
597    fn get_multi_progress_context(dispatch: &tracing::Dispatch, f: &mut dyn FnMut(MultiProgress)) {
598        let layer = dispatch
599            .downcast_ref::<IndicatifLayer<S, F>>()
600            .expect("subscriber should downcast to expected type; this is a bug!");
601
602        f(layer.mp.clone())
603    }
604
605    fn handle_on_enter(
606        &self,
607        pb_manager: &mut ProgressBarManager,
608        id: &span::Id,
609        ctx: &layer::Context<'_, S>,
610    ) -> Option<ProgressBar> {
611        let span = ctx
612            .span(id)
613            .expect("Span not found in context, this is a bug");
614        let mut ext = span.extensions_mut();
615
616        if let Some(indicatif_ctx) = ext.get_mut::<IndicatifSpanContext>() {
617            // Start the progress bar when we enter the span for the first time.
618            if indicatif_ctx.progress_bar.is_none() {
619                indicatif_ctx.make_progress_bar(&self.progress_style);
620
621                if let Some(ref parent_span_with_pb) = indicatif_ctx.parent_span {
622                    // Recursively start parent PBs if parent spans have not been entered yet.
623                    let parent_pb = self.handle_on_enter(pb_manager, parent_span_with_pb, ctx);
624
625                    indicatif_ctx.parent_progress_bar = parent_pb;
626                }
627
628                pb_manager.show_progress_bar(indicatif_ctx, id);
629            }
630
631            return indicatif_ctx.progress_bar.to_owned();
632        }
633
634        None
635    }
636}
637
638impl<S, F> layer::Layer<S> for IndicatifLayer<S, F>
639where
640    S: Subscriber + for<'a> LookupSpan<'a>,
641    F: for<'writer> FormatFields<'writer> + 'static,
642{
643    fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: layer::Context<'_, S>) {
644        let span = ctx
645            .span(id)
646            .expect("Span not found in context, this is a bug");
647        let mut ext = span.extensions_mut();
648
649        let mut fields = FormattedFields::<F>::new(String::new());
650        let _ = self
651            .span_field_formatter
652            .format_fields(fields.as_writer(), attrs);
653
654        // Get the next parent span with a progress bar.
655        let parent_span = ctx.span_scope(id).and_then(|scope| {
656            scope.skip(1).find(|span| {
657                let ext = span.extensions();
658
659                ext.get::<IndicatifSpanContext>().is_some()
660            })
661        });
662        let parent_span_id = parent_span.as_ref().map(|span| span.id());
663        let parent_span_ext = parent_span.as_ref().map(|span| span.extensions());
664        let parent_indicatif_ctx = parent_span_ext.as_ref().map(|ext| {
665            ext.get::<IndicatifSpanContext>()
666                .expect("validated it exists prior")
667        });
668
669        let (span_child_prefix, level) = match parent_indicatif_ctx {
670            Some(v) => {
671                let level = v.level + 1;
672
673                (
674                    format!(
675                        "{}{}",
676                        self.span_child_prefix_indent.repeat(level.into()),
677                        self.span_child_prefix_symbol
678                    ),
679                    level,
680                )
681            }
682            None => (String::new(), 0),
683        };
684
685        ext.insert(IndicatifSpanContext {
686            progress_bar: None,
687            pb_init_settings: ProgressBarInitSettings::default(),
688            parent_progress_bar: None,
689            parent_span: parent_span_id,
690            span_fields_formatted: Some(fields.fields),
691            span_name: span.name().to_string(),
692            span_child_prefix,
693            level,
694            finish_message: None,
695        });
696    }
697
698    fn on_enter(&self, id: &span::Id, ctx: layer::Context<'_, S>) {
699        if let Ok(mut pb_manager_lock) = self.pb_manager.lock() {
700            self.handle_on_enter(&mut pb_manager_lock, id, &ctx);
701        }
702    }
703
704    fn on_close(&self, id: span::Id, ctx: layer::Context<'_, S>) {
705        if let Ok(mut pb_manager_lock) = self.pb_manager.lock() {
706            let span = ctx
707                .span(&id)
708                .expect("Span not found in context, this is a bug");
709            let mut ext = span.extensions_mut();
710
711            // Clear the progress bar only when the span has closed completely.
712            if let Some(indicatif_ctx) = ext.get_mut::<IndicatifSpanContext>() {
713                pb_manager_lock.finish_progress_bar(indicatif_ctx, &ctx);
714            }
715        }
716    }
717
718    // See comments on [WithContext] for why we have this.
719    //
720    // SAFETY: this is safe because the `WithContext` function pointer is valid
721    // for the lifetime of `&self`.
722    unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
723        match id {
724            id if id == TypeId::of::<Self>() => Some(self as *const _ as *const ()),
725            id if id == TypeId::of::<WithContext>() => {
726                Some(&self.get_context as *const _ as *const ())
727            }
728            id if id == TypeId::of::<WithStderrWriter>() => {
729                Some(&self.get_stderr_writer_context as *const _ as *const ())
730            }
731            id if id == TypeId::of::<WithStdoutWriter>() => {
732                Some(&self.get_stdout_writer_context as *const _ as *const ())
733            }
734            id if id == TypeId::of::<WithMultiProgress>() => {
735                Some(&self.get_multi_progress_context as *const _ as *const ())
736            }
737            _ => None,
738        }
739    }
740}
741
742/// Hide all progress bars managed by [`IndicatifLayer`] (if it exists), executes `f`, then redraws
743/// the progress bars. Identical to [`indicatif::MultiProgress::suspend`].
744///
745/// Executes `f` even if there is no default tracing subscriber or if a `IndicatifLayer` has not
746/// been registered to that subscriber.
747///
748/// NOTE: this does not suspend stdout/stderr prints from other threads, including things like
749/// `tracing::info!`. This only suspends the drawing of progress bars.
750///
751/// WARNING: this holds an internal lock within `MultiProgress`. Calling methods like
752/// `writeln!(get_indicatif_stderr_writer(), "foobar")` or calling this method inside of `f` will
753/// result in a deadlock.
754pub fn suspend_tracing_indicatif<F: FnOnce() -> R, R>(f: F) -> R {
755    let mut mp: Option<MultiProgress> = None;
756
757    tracing::dispatcher::get_default(|dispatch| {
758        if let Some(ctx) = dispatch.downcast_ref::<WithMultiProgress>() {
759            ctx.with_context(dispatch, |fetched_mp| {
760                mp = Some(fetched_mp);
761            })
762        }
763    });
764
765    if let Some(mp) = mp {
766        mp.suspend(f)
767    } else {
768        f()
769    }
770}
771
772/// Helper macro that allows you to print to stdout without interfering with the progress bars
773/// created by tracing-indicatif.
774///
775/// Args are directly forwarded to `writeln!`. Do not call this macro inside of
776/// `suspend_tracing_indicatif` or you will trigger a deadlock.
777#[macro_export]
778macro_rules! indicatif_println {
779    ($($arg:tt)*) => {
780        {
781            use std::io::Write;
782
783            if let Some(mut writer) = $crate::writer::get_indicatif_stdout_writer() {
784                writeln!(writer, $($arg)*).unwrap();
785            } else {
786                #[allow(clippy::explicit_write)]
787                writeln!(std::io::stdout(), $($arg)*).unwrap();
788            }
789        }
790    }
791}
792
793/// Helper macro that allows you to print to stderr without interfering with the progress bars
794/// created by tracing-indicatif.
795///
796/// Args are directly forwarded to `writeln!`. Do not call this macro inside of
797/// `suspend_tracing_indicatif` or you will trigger a deadlock.
798#[macro_export]
799macro_rules! indicatif_eprintln {
800    ($($arg:tt)*) => {
801        {
802            use std::io::Write;
803
804            if let Some(mut writer) = $crate::writer::get_indicatif_stderr_writer() {
805                writeln!(writer, $($arg)*).unwrap();
806            } else {
807                #[allow(clippy::explicit_write)]
808                writeln!(std::io::stderr(), $($arg)*).unwrap();
809            }
810        }
811    }
812}
813
814#[cfg(test)]
815mod tests;