1use std::any::TypeId;
40use std::marker::PhantomData;
41use std::sync::Mutex;
42
43pub 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#[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 progress_bar: Option<ProgressBar>,
182 pb_init_settings: ProgressBarInitSettings,
184 parent_progress_bar: Option<ProgressBar>,
189 parent_span: Option<span::Id>,
191 span_fields_formatted: Option<String>,
193 span_name: String,
194 span_child_prefix: String,
195 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 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
306pub struct IndicatifLayer<S, F = DefaultFields> {
324 pb_manager: Mutex<ProgressBarManager>,
325 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 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
398impl<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 pub fn get_stderr_writer(&self) -> IndicatifWriter<writer::Stderr> {
414 IndicatifWriter::new(self.mp.clone())
416 }
417
418 pub fn get_stdout_writer(&self) -> IndicatifWriter<writer::Stdout> {
427 IndicatifWriter::new(self.mp.clone())
429 }
430
431 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 pub fn with_progress_style(mut self, style: ProgressStyle) -> Self {
464 self.progress_style = style;
465 self
466 }
467
468 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 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 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 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 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 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 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 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 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 if let Some(indicatif_ctx) = ext.get_mut::<IndicatifSpanContext>() {
690 pb_manager_lock.finish_progress_bar(indicatif_ctx, &ctx);
691 }
692 }
693
694 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
718pub 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#[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#[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;