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//! ```
20//! use tracing_subscriber::layer::SubscriberExt;
21//! use tracing_subscriber::util::SubscriberInitExt;
22//! use tracing_indicatif::IndicatifLayer;
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
43/// Re-export of [`indicatif`]'s style module for ease of use.
44pub use indicatif::style;
45use indicatif::style::ProgressStyle;
46use indicatif::style::ProgressTracker;
47use indicatif::MultiProgress;
48use indicatif::ProgressBar;
49use tracing_core::span;
50use tracing_core::Subscriber;
51use tracing_subscriber::fmt::format::DefaultFields;
52use tracing_subscriber::fmt::FormatFields;
53use tracing_subscriber::fmt::FormattedFields;
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}
199
200impl IndicatifSpanContext {
201    fn add_keys_to_style(&self, style: ProgressStyle) -> ProgressStyle {
202        style
203            .with_key(
204                "span_name",
205                IndicatifProgressKey {
206                    message: self.span_name.clone(),
207                },
208            )
209            .with_key(
210                "span_fields",
211                IndicatifProgressKey {
212                    message: self.span_fields_formatted.to_owned().unwrap_or_default(),
213                },
214            )
215            .with_key(
216                "span_child_prefix",
217                IndicatifProgressKey {
218                    message: self.span_child_prefix.clone(),
219                },
220            )
221    }
222
223    fn make_progress_bar(&mut self, default_style: &ProgressStyle) {
224        if self.progress_bar.is_none() {
225            let pb = ProgressBar::hidden().with_style(
226                self.pb_init_settings
227                    .style
228                    .take()
229                    .unwrap_or_else(|| self.add_keys_to_style(default_style.clone())),
230            );
231
232            if let Some(len) = self.pb_init_settings.len.take() {
233                pb.set_length(len);
234            }
235
236            if let Some(msg) = self.pb_init_settings.message.take() {
237                pb.set_message(msg);
238            }
239
240            if let Some(pos) = self.pb_init_settings.pos.take() {
241                pb.set_position(pos);
242            }
243
244            self.progress_bar = Some(pb);
245        }
246    }
247
248    fn set_progress_bar_style(&mut self, style: ProgressStyle) {
249        if let Some(ref pb) = self.progress_bar {
250            pb.set_style(self.add_keys_to_style(style));
251        } else {
252            self.pb_init_settings.style = Some(self.add_keys_to_style(style));
253        }
254    }
255
256    fn set_progress_bar_length(&mut self, len: u64) {
257        if let Some(ref pb) = self.progress_bar {
258            pb.set_length(len);
259        } else {
260            self.pb_init_settings.len = Some(len);
261        }
262    }
263
264    fn set_progress_bar_position(&mut self, pos: u64) {
265        if let Some(ref pb) = self.progress_bar {
266            pb.set_position(pos);
267        } else {
268            self.pb_init_settings.pos = Some(pos);
269        }
270    }
271
272    fn set_progress_bar_message(&mut self, msg: String) {
273        if let Some(ref pb) = self.progress_bar {
274            pb.set_message(msg);
275        } else {
276            self.pb_init_settings.message = Some(msg);
277        }
278    }
279
280    fn inc_progress_bar_position(&mut self, pos: u64) {
281        if let Some(ref pb) = self.progress_bar {
282            pb.inc(pos);
283        } else if let Some(ref mut pb_pos) = self.pb_init_settings.pos {
284            *pb_pos += pos;
285        } else {
286            // indicatif defaults position to 0, so copy that behavior.
287            self.pb_init_settings.pos = Some(pos);
288        }
289    }
290
291    fn inc_progress_bar_length(&mut self, len: u64) {
292        if let Some(ref pb) = self.progress_bar {
293            pb.inc_length(len);
294        } else if let Some(ref mut pb_len) = self.pb_init_settings.len {
295            *pb_len += len;
296        }
297    }
298
299    fn progress_bar_tick(&mut self) {
300        if let Some(ref pb) = self.progress_bar {
301            pb.tick()
302        }
303    }
304}
305
306/// The layer that handles creating and managing indicatif progress bars for active spans. This
307/// layer must be registered with your tracing subscriber to have any effect.
308///
309/// This layer performs no filtering on which spans to show progress bars for. It is expected one
310/// attaches [filters to this
311/// layer](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#filtering-with-layers)
312/// to control which spans actually have progress bars generated for them. See
313/// [`filter::IndicatifFilter`] for a rudimentary filter.
314///
315/// Progress bars will be started the very first time a span is [entered](tracing::Span::enter)
316/// or when one of its child spans is entered for the first time, and will finish when the span
317/// is [closed](tracing_subscriber::Layer::on_close) (including all child spans having closed).
318///
319/// Progress bars are emitted to stderr.
320///
321/// Under the hood, this just uses indicatif's [`MultiProgress`] struct to
322/// manage individual [`ProgressBar`] instances per span.
323pub struct IndicatifLayer<S, F = DefaultFields> {
324    pb_manager: Mutex<ProgressBarManager>,
325    // Allows us to fetch the `MultiProgress` without taking a lock.
326    // Do not mutate `mp` directly, always go through `pb_manager`.
327    mp: MultiProgress,
328    span_field_formatter: F,
329    progress_style: ProgressStyle,
330    span_child_prefix_indent: &'static str,
331    span_child_prefix_symbol: &'static str,
332    get_context: WithContext,
333    get_stderr_writer_context: WithStderrWriter,
334    get_stdout_writer_context: WithStdoutWriter,
335    get_multi_progress_context: WithMultiProgress,
336    inner: PhantomData<S>,
337}
338
339impl<S> IndicatifLayer<S>
340where
341    S: Subscriber + for<'a> LookupSpan<'a>,
342{
343    /// Spawns a progress bar for every tracing span that is received by this layer.
344    ///
345    /// The default settings for this layer are 7 progress bars maximum and progress bars in the
346    /// style of:
347    /// ```text
348    /// ⠄ do_work{val=0}
349    /// ⠄ do_work{val=1}
350    /// ⠄ do_work{val=2}
351    ///   ↳ ⠴ do_sub_work{val=2}
352    ///   ↳ ⠴ do_sub_work{val=2}
353    /// ⠄ do_work{val=3}
354    /// ⠄ do_work{val=4}
355    /// ...and 5 more not shown above.
356    /// ```
357    pub fn new() -> Self {
358        Self::default()
359    }
360}
361
362impl<S> Default for IndicatifLayer<S>
363where
364    S: Subscriber + for<'a> LookupSpan<'a>,
365{
366    fn default() -> Self {
367        let pb_manager = ProgressBarManager::new(
368            7,
369            Some(
370                ProgressStyle::with_template(
371                    "...and {pending_progress_bars} more not shown above.",
372                )
373                .unwrap(),
374            ),
375            TickSettings::default(),
376        );
377        let mp = pb_manager.mp.clone();
378
379        Self {
380            pb_manager: Mutex::new(pb_manager),
381            mp,
382            span_field_formatter: DefaultFields::new(),
383            progress_style: ProgressStyle::with_template(
384                "{span_child_prefix}{spinner} {span_name}{{{span_fields}}}",
385            )
386            .unwrap(),
387            span_child_prefix_indent: "  ",
388            span_child_prefix_symbol: "↳ ",
389            get_context: WithContext(Self::get_context),
390            get_stderr_writer_context: WithStderrWriter(Self::get_stderr_writer_context),
391            get_stdout_writer_context: WithStdoutWriter(Self::get_stdout_writer_context),
392            get_multi_progress_context: WithMultiProgress(Self::get_multi_progress_context),
393            inner: PhantomData,
394        }
395    }
396}
397
398// pub methods
399impl<S, F> IndicatifLayer<S, F> {
400    #[deprecated(since = "0.2.3", note = "use get_stderr_writer() instead")]
401    pub fn get_fmt_writer(&self) -> IndicatifWriter<writer::Stderr> {
402        self.get_stderr_writer()
403    }
404
405    /// Returns the a writer for [`std::io::Stderr`] that ensures its output will not be clobbered by
406    /// active progress bars.
407    ///
408    /// Instead of `eprintln!(...)` prefer `writeln!(indicatif_layer.get_stderr_writer(), ...)`
409    /// instead to ensure your output is not clobbered by active progress bars.
410    ///
411    /// If one wishes tracing logs to be output to stderr, this should be passed into
412    /// [`fmt::Layer::with_writer`](tracing_subscriber::fmt::Layer::with_writer).
413    pub fn get_stderr_writer(&self) -> IndicatifWriter<writer::Stderr> {
414        // `MultiProgress` is merely a wrapper over an `Arc`, so we can clone here.
415        IndicatifWriter::new(self.mp.clone())
416    }
417
418    /// Returns the a writer for [`std::io::Stdout`] that ensures its output will not be clobbered by
419    /// active progress bars.
420    ///
421    /// Instead of `println!(...)` prefer `writeln!(indicatif_layer.get_stdout_writer(), ...)`
422    /// instead to ensure your output is not clobbered by active progress bars.
423    ///
424    /// If one wishes tracing logs to be output to stdout, this should be passed into
425    /// [`fmt::Layer::with_writer`](tracing_subscriber::fmt::Layer::with_writer).
426    pub fn get_stdout_writer(&self) -> IndicatifWriter<writer::Stdout> {
427        // `MultiProgress` is merely a wrapper over an `Arc`, so we can clone here.
428        IndicatifWriter::new(self.mp.clone())
429    }
430
431    /// Set the formatter for span fields, the result of which will be available as the
432    /// progress bar template key `span_fields`.
433    ///
434    /// The default is the [`DefaultFields`] formatter.
435    pub fn with_span_field_formatter<F2>(self, formatter: F2) -> IndicatifLayer<S, F2>
436    where
437        F2: for<'writer> FormatFields<'writer> + 'static,
438    {
439        IndicatifLayer {
440            pb_manager: self.pb_manager,
441            mp: self.mp,
442            span_field_formatter: formatter,
443            progress_style: self.progress_style,
444            span_child_prefix_indent: self.span_child_prefix_indent,
445            span_child_prefix_symbol: self.span_child_prefix_symbol,
446            get_context: self.get_context,
447            get_stderr_writer_context: self.get_stderr_writer_context,
448            get_stdout_writer_context: self.get_stdout_writer_context,
449            get_multi_progress_context: self.get_multi_progress_context,
450            inner: self.inner,
451        }
452    }
453
454    /// Override the style used for displayed progress bars.
455    ///
456    /// Two additional keys are available for the progress bar template:
457    /// * `span_fields` - the formatted string of this span's fields
458    /// * `span_name` - the name of the span
459    /// * `span_child_prefix` - a prefix that increase in size according to the number of parents
460    ///   the span has.
461    ///
462    /// The default template is `{span_child_prefix}{spinner} {span_name}{{{span_fields}}}`.
463    pub fn with_progress_style(mut self, style: ProgressStyle) -> Self {
464        self.progress_style = style;
465        self
466    }
467
468    /// Set the indent used to mark the "level" of a given child span's progress bar.
469    ///
470    /// For example, if the given span is two levels deep (iow has two parent spans with progress
471    /// bars), and this is " ", the `{span_child_prefix}` key for this span's progress bar will be
472    /// prefixed with "  ".
473    pub fn with_span_child_prefix_indent(mut self, indent: &'static str) -> Self {
474        self.span_child_prefix_indent = indent;
475        self
476    }
477
478    /// Set the symbol used to denote this is a progress bar from a child span.
479    ///
480    /// This is ultimately concatenated with the child prefix indent to make the
481    /// `span_child_prefix` progress bar key.
482    pub fn with_span_child_prefix_symbol(mut self, symbol: &'static str) -> Self {
483        self.span_child_prefix_symbol = symbol;
484        self
485    }
486
487    /// Set the maximum number of progress bars that will be displayed, and the possible footer
488    /// "progress bar" that displays when there are more progress bars than can be displayed.
489    ///
490    /// `footer_style` dictates the appearance of the footer, and the footer will only appear if
491    /// there are more progress bars than can be displayed. If it is `None`, no footer will be
492    /// displayed. `footer_style` has the following keys available to it:
493    /// * `pending_progress_bars` - the number of progress bars waiting to be shown
494    pub fn with_max_progress_bars(
495        mut self,
496        max_progress_bars: u64,
497        footer_style: Option<ProgressStyle>,
498    ) -> Self {
499        self.pb_manager
500            .get_mut()
501            .unwrap()
502            .set_max_progress_bars(max_progress_bars, footer_style);
503
504        self
505    }
506
507    /// Configures how often progress bars are recalcuated and redrawn to the terminal.
508    pub fn with_tick_settings(mut self, tick_settings: TickSettings) -> Self {
509        self.pb_manager
510            .get_mut()
511            .unwrap()
512            .set_tick_settings(tick_settings);
513        self
514    }
515}
516
517impl<S, F> IndicatifLayer<S, F>
518where
519    S: Subscriber + for<'a> LookupSpan<'a>,
520    F: for<'writer> FormatFields<'writer> + 'static,
521{
522    fn get_context(
523        dispatch: &tracing::Dispatch,
524        id: &span::Id,
525        f: &mut dyn FnMut(&mut IndicatifSpanContext),
526    ) {
527        // The only way `get_context` can be called is if we have an `IndicatifLayer` added to the
528        // expected subscriber, hence why we can `.expect` here.
529        let subscriber = dispatch
530            .downcast_ref::<S>()
531            .expect("subscriber should downcast to expected type; this is a bug!");
532        let span = subscriber
533            .span(id)
534            .expect("Span not found in context, this is a bug");
535
536        let mut ext = span.extensions_mut();
537
538        if let Some(indicatif_ctx) = ext.get_mut::<IndicatifSpanContext>() {
539            f(indicatif_ctx);
540        }
541    }
542
543    fn get_stderr_writer_context(
544        dispatch: &tracing::Dispatch,
545        f: &mut dyn FnMut(IndicatifWriter<writer::Stderr>),
546    ) {
547        let layer = dispatch
548            .downcast_ref::<IndicatifLayer<S, F>>()
549            .expect("subscriber should downcast to expected type; this is a bug!");
550
551        f(layer.get_stderr_writer())
552    }
553
554    fn get_stdout_writer_context(
555        dispatch: &tracing::Dispatch,
556        f: &mut dyn FnMut(IndicatifWriter<writer::Stdout>),
557    ) {
558        let layer = dispatch
559            .downcast_ref::<IndicatifLayer<S, F>>()
560            .expect("subscriber should downcast to expected type; this is a bug!");
561
562        f(layer.get_stdout_writer())
563    }
564
565    fn get_multi_progress_context(dispatch: &tracing::Dispatch, f: &mut dyn FnMut(MultiProgress)) {
566        let layer = dispatch
567            .downcast_ref::<IndicatifLayer<S, F>>()
568            .expect("subscriber should downcast to expected type; this is a bug!");
569
570        f(layer.mp.clone())
571    }
572}
573
574impl<S, F> layer::Layer<S> for IndicatifLayer<S, F>
575where
576    S: Subscriber + for<'a> LookupSpan<'a>,
577    F: for<'writer> FormatFields<'writer> + 'static,
578{
579    fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: layer::Context<'_, S>) {
580        let span = ctx
581            .span(id)
582            .expect("Span not found in context, this is a bug");
583        let mut ext = span.extensions_mut();
584
585        let mut fields = FormattedFields::<F>::new(String::new());
586        let _ = self
587            .span_field_formatter
588            .format_fields(fields.as_writer(), attrs);
589
590        // Get the next parent span with a progress bar.
591        let parent_span = ctx.span_scope(id).and_then(|scope| {
592            scope.skip(1).find(|span| {
593                let ext = span.extensions();
594
595                ext.get::<IndicatifSpanContext>().is_some()
596            })
597        });
598        let parent_span_id = parent_span.as_ref().map(|span| span.id());
599        let parent_span_ext = parent_span.as_ref().map(|span| span.extensions());
600        let parent_indicatif_ctx = parent_span_ext
601            .as_ref()
602            .map(|ext| ext.get::<IndicatifSpanContext>().unwrap());
603
604        let (span_child_prefix, level) = match parent_indicatif_ctx {
605            Some(v) => {
606                let level = v.level + 1;
607
608                (
609                    format!(
610                        "{}{}",
611                        self.span_child_prefix_indent.repeat(level.into()),
612                        self.span_child_prefix_symbol
613                    ),
614                    level,
615                )
616            }
617            None => (String::new(), 0),
618        };
619
620        ext.insert(IndicatifSpanContext {
621            progress_bar: None,
622            pb_init_settings: ProgressBarInitSettings::default(),
623            parent_progress_bar: None,
624            parent_span: parent_span_id,
625            span_fields_formatted: Some(fields.fields),
626            span_name: span.name().to_string(),
627            span_child_prefix,
628            level,
629        });
630    }
631
632    fn on_enter(&self, id: &span::Id, ctx: layer::Context<'_, S>) {
633        let mut pb_manager_lock = self.pb_manager.lock().unwrap();
634
635        let span = ctx
636            .span(id)
637            .expect("Span not found in context, this is a bug");
638        let mut ext = span.extensions_mut();
639
640        if let Some(indicatif_ctx) = ext.get_mut::<IndicatifSpanContext>() {
641            // Start the progress bar when we enter the span for the first time.
642            if indicatif_ctx.progress_bar.is_none() {
643                indicatif_ctx.make_progress_bar(&self.progress_style);
644
645                if let Some(ref parent_span_with_pb) = indicatif_ctx.parent_span {
646                    let parent_span = ctx
647                        .span(parent_span_with_pb)
648                        .expect("Parent span not found in context, this is a bug");
649                    let mut parent_span_ext = parent_span.extensions_mut();
650                    let parent_indicatif_ctx = parent_span_ext
651                        .get_mut::<IndicatifSpanContext>()
652                        .expect(
653                        "IndicatifSpanContext not found in parent span extensions, this is a bug",
654                    );
655
656                    // If the parent span has not been entered once, start the parent progress bar
657                    // for it. We are guaranteed that the parent span has not yet closed because a
658                    // child span for the parent is still open.
659                    //
660                    // NOTE: there's a bug here. if the parent of the parent hasn't started their
661                    // PB, we don't start the parent of the parent's PB. It'd be pretty bad to have
662                    // to iterate up the whole span scope to fix this, so I'm hoping this is a non
663                    // issue for the most part. :(
664                    if parent_indicatif_ctx.progress_bar.is_none() {
665                        parent_indicatif_ctx.make_progress_bar(&self.progress_style);
666
667                        pb_manager_lock.show_progress_bar(parent_indicatif_ctx, id);
668                    }
669
670                    // We can safely unwrap here now since we know a parent progress bar exists.
671                    indicatif_ctx.parent_progress_bar =
672                        Some(parent_indicatif_ctx.progress_bar.to_owned().unwrap());
673                }
674
675                pb_manager_lock.show_progress_bar(indicatif_ctx, id);
676            }
677        }
678    }
679
680    fn on_close(&self, id: span::Id, ctx: layer::Context<'_, S>) {
681        let mut pb_manager_lock = self.pb_manager.lock().unwrap();
682
683        let span = ctx
684            .span(&id)
685            .expect("Span not found in context, this is a bug");
686        let mut ext = span.extensions_mut();
687
688        // Clear the progress bar only when the span has closed completely.
689        if let Some(indicatif_ctx) = ext.get_mut::<IndicatifSpanContext>() {
690            pb_manager_lock.finish_progress_bar(indicatif_ctx, &ctx);
691        }
692    }
693
694    // See comments on [WithContext] for why we have this.
695    //
696    // SAFETY: this is safe because the `WithContext` function pointer is valid
697    // for the lifetime of `&self`.
698    unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
699        match id {
700            id if id == TypeId::of::<Self>() => Some(self as *const _ as *const ()),
701            id if id == TypeId::of::<WithContext>() => {
702                Some(&self.get_context as *const _ as *const ())
703            }
704            id if id == TypeId::of::<WithStderrWriter>() => {
705                Some(&self.get_stderr_writer_context as *const _ as *const ())
706            }
707            id if id == TypeId::of::<WithStdoutWriter>() => {
708                Some(&self.get_stdout_writer_context as *const _ as *const ())
709            }
710            id if id == TypeId::of::<WithMultiProgress>() => {
711                Some(&self.get_multi_progress_context as *const _ as *const ())
712            }
713            _ => None,
714        }
715    }
716}
717
718/// Hide all progress bars managed by [`IndicatifLayer`] (if it exists), executes `f`, then redraws
719/// the progress bars. Identical to [`indicatif::MultiProgress::suspend`].
720///
721/// Executes `f` even if there is no default tracing subscriber or if a `IndicatifLayer` has not
722/// been registered to that subscriber.
723///
724/// NOTE: this does not suspend stdout/stderr prints from other threads, including things like
725/// `tracing::info!`. This only suspends the drawing of progress bars.
726///
727/// WARNING: this holds an internal lock within `MultiProgress`. Calling methods like
728/// `writeln!(get_indicatif_stderr_writer(), "foobar")` or calling this method inside of `f` will
729/// result in a deadlock.
730pub fn suspend_tracing_indicatif<F: FnOnce() -> R, R>(f: F) -> R {
731    let mut mp: Option<MultiProgress> = None;
732
733    tracing::dispatcher::get_default(|dispatch| {
734        if let Some(ctx) = dispatch.downcast_ref::<WithMultiProgress>() {
735            ctx.with_context(dispatch, |fetched_mp| {
736                mp = Some(fetched_mp);
737            })
738        }
739    });
740
741    if let Some(mp) = mp {
742        mp.suspend(f)
743    } else {
744        f()
745    }
746}
747
748/// Helper macro that allows you to print to stdout without interfering with the progress bars
749/// created by tracing-indicatif.
750///
751/// Args are directly forwarded to `writeln!`. Do not call this macro inside of
752/// `suspend_tracing_indicatif` or you will trigger a deadlock.
753#[macro_export]
754macro_rules! indicatif_println {
755    ($($arg:tt)*) => {
756        {
757            use std::io::Write;
758
759            if let Some(mut writer) = $crate::writer::get_indicatif_stdout_writer() {
760                writeln!(writer, $($arg)*).unwrap();
761            } else {
762                #[allow(clippy::explicit_write)]
763                writeln!(std::io::stdout(), $($arg)*).unwrap();
764            }
765        }
766    }
767}
768
769/// Helper macro that allows you to print to stderr without interfering with the progress bars
770/// created by tracing-indicatif.
771///
772/// Args are directly forwarded to `writeln!`. Do not call this macro inside of
773/// `suspend_tracing_indicatif` or you will trigger a deadlock.
774#[macro_export]
775macro_rules! indicatif_eprintln {
776    ($($arg:tt)*) => {
777        {
778            use std::io::Write;
779
780            if let Some(mut writer) = $crate::writer::get_indicatif_stderr_writer() {
781                writeln!(writer, $($arg)*).unwrap();
782            } else {
783                #[allow(clippy::explicit_write)]
784                writeln!(std::io::stderr(), $($arg)*).unwrap();
785            }
786        }
787    }
788}
789
790#[cfg(test)]
791mod tests;