tracing-indicatif

repository·main·Indexed 42 days ago

https://github.com/emersonford/tracing-indicatif

A Rust library providing a tracing layer that automatically creates and manages indicatif progress bars based on the lifecycle of tracing spans. It features hierarchical span support, customizable styles via indicatif::ProgressStyle, and tools like IndicatifFilter and IndicatifWriter to prevent logs from clobbering progress bars in CLI applications. Version 0.3.14.

Tokens
3.6K
Snippets
10
Records
16
Agent score
53%

What's inside tracing-indicatif

  1. Key features of tracing-indicatif

    main

    tracing-indicatif provides several capabilities for managing CLI progress:

    • Customizable Styles: Use the standard indicatif::ProgressStyle API to customize the appearance of your progress bars.
    • Hierarchical Spans: Supports displaying parent-child relationships between progress bars, mirroring the structure of your tracing spans.
    • Display Limits: You can limit the number of progress bars visible on the terminal at once.
    • Log Protection: Prevents progress bars from clobbering (interfering with) your tracing logs.
  2. What is tracing-indicatif?

    main

    tracing-indicatif is a tracing layer designed to automatically create and manage indicatif progress bars for active spans.

    Instead of manually managing progress bar instances within your library code—which can be invasive and difficult to track—you can tie progress bars directly to tracing spans. This allows you to enable a smooth progress bar experience for your CLI with minimal code changes (as few as 3 lines) if you are already using tracing spans.

  3. Use IndicatifWriter to prevent log clobbering

    main

    IndicatifWriter is a wrapper around std::io::stdout() or std::io::stderr() designed to ensure that tracing log entries do not clobber active indicatif progress bars. It achieves this by calling MultiProgress::suspend whenever a write occurs, temporarily hiding the progress bars so the log line can be printed cleanly.

    While you can construct one manually using IndicatifWriter::new(mp), it is highly recommended to use the helper methods provided by IndicatifLayer (e.g., get_stderr_writer or get_stdout_writer) to ensure the writer is correctly integrated with your existing tracing subscriber.

    // Recommended usage pattern (via IndicatifLayer):
    // let writer = indicatif_layer.get_stderr_writer();
    
    // Manual construction (less recommended):
    // let writer = IndicatifWriter::<Stderr>::new(multi_progress_instance);
  4. How IndicatifLayer works

    main

    The IndicatifLayer automatically manages indicatif progress bars by tying them to the lifetime of tracing spans.

    • Lifecycle: A progress bar is started the first time a span is entered (or when one of its child spans is entered) and is finished when the span is closed (after all child spans have closed).
    • Display: Progress bars are emitted to stderr by default. The layer uses indicatif::MultiProgress under the hood to manage multiple active bars.
    • Filtering: The layer itself performs no filtering. You should attach tracing_subscriber filters to the layer to control which spans trigger progress bars.
    • Hierarchy: The layer supports nested spans, using indentation and symbols to denote the hierarchy in the terminal.
  5. Quick start with IndicatifLayer

    main

    To enable progress bars for your CLI using tracing-indicatif, create an IndicatifLayer and register it with your tracing_subscriber. It is highly recommended to pass indicatif_layer.get_stderr_writer() (or get_stdout_writer()) to your fmt::layer() to prevent progress bars from being clobbered by log output.

    use tracing_indicatif::IndicatifLayer;
    use tracing_subscriber::layer::SubscriberExt;
    use tracing_subscriber::util::SubscriberInitExt;
    
    let indicatif_layer = IndicatifLayer::new();
    
    tracing_subscriber::registry()
        .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer()))
        .with(indicatif_layer)
        .init();
  6. Configure progress bar tick settings with `TickSettings`

    main

    Use the TickSettings struct to control the frequency of terminal redraws and the interval at which progress bar states are recalculated. This is useful for optimizing performance or adjusting the visual smoothness of progress bars and spinners.

    Key fields:

    • term_draw_hz: The rate (in Hz) at which the terminal is redrawn. For example, 20 means the terminal state is updated 20 times per second (every 50ms).
    • default_tick_interval: The interval passed to enable_steady_tick for new progress bars. This controls how often the progress bar state is recalculated. Use None to disable steady ticks.
    • footer_tick_interval: The interval for the footer progress bar. Use None unless you have a spinner in your footer, as the footer is otherwise redrawn manually on changes.

    Note: term_draw_hz controls the actual terminal redraw frequency, while default_tick_interval controls the internal state recalculation frequency.

    # use tracing_indicatif::TickSettings;
    # use std::time::Duration;
    
    TickSettings {
        term_draw_hz: 20,
        default_tick_interval: None,
        footer_tick_interval: None,
        ..Default::default()
    }
  7. Use the IndicatifSpanExt trait to control progress bars via tracing spans

    main

    The IndicatifSpanExt trait provides utilities to modify the indicatif progress bar associated with a tracing::Span.

    Key Behaviors

    • Graceful Degradation: These methods will silently have no effect if an IndicatifLayer was not registered with the tracing subscriber or if the span was filtered out. This makes them safe to call even in non-CLI contexts.
    • Initialization: If a span has not yet been entered, calling pb_set_style will set the style used when the span is first entered. If it has already been entered, it updates the existing progress bar.
    • Starting a Bar: Use pb_start() to briefly enter the span, which starts the progress bar if it hasn't been entered before.

    Important Warnings

    • Positioning: You should call pb_set_length at least once before calling pb_set_position or pb_inc, otherwise the progress bar may behave incorrectly.
    • Length Incrementing: pb_inc_length has no effect unless pb_set_length has been called at least once.
    use indicatif::ProgressStyle;
    use tracing_indicatif::span_ext::IndicatifSpanExt;
    
    // Example: Setting the style of the current span's progress bar
    tracing::Span::current().pb_set_style(&ProgressStyle::default_spinner());
  8. Configure IndicatifLayer appearance and behavior

    main

    You can customize the IndicatifLayer using a builder-like pattern before registering it with your subscriber:

    • with_span_field_formatter(formatter): Set a custom formatter for span fields (used in the span_fields template key).
    • with_progress_style(style): Override the indicatif::ProgressStyle used for bars. Available keys: span_fields, span_name, and span_child_prefix.
    • with_span_child_prefix_indent(indent): Set the string used for indentation of child spans.
    • with_span_child_prefix_symbol(symbol): Set the symbol (e.g., ↳ ) used to denote child spans.
    • with_max_progress_bars(max, footer_style): Limit the number of visible bars. If exceeded, a footer showing pending_progress_bars is displayed.
    • with_tick_settings(tick_settings): Configure how often bars are redrawn.
  9. Suspend progress bars with suspend_tracing_indicatif

    main

    The suspend_tracing_indicatif function allows you to execute a closure while the progress bars are temporarily hidden. This is useful for performing operations that need to print directly to the terminal without the indicatif management interfering.

    WARNING: This holds an internal lock within MultiProgress. Calling writeln!(get_indicatif_stderr_writer(), ...) or calling suspend_tracing_indicatif recursively inside the closure will result in a deadlock.

    suspend_tracing_indicatif(|| {
        // Code here runs while progress bars are suspended
    });
  10. Use IndicatifFilter to selectively enable progress bars

    main

    IndicatifFilter allows you to control which spans trigger a progress bar based on the presence of specific fields. This is useful for preventing progress bars from appearing on every single span in a large application.

    Filtering Logic

    • indicatif.pb_show: If this field is present on a span, the progress bar will be shown.
    • indicatif.pb_hide: If this field is present on a span (and indicatif.pb_show is not), the progress bar will not be shown.
    • Default Behavior: If neither field is present, the filter falls back to the show_progress_bars_by_default value provided during construction.

    Note: The value of these fields is ignored. It is recommended to set them to tracing::field::Empty to avoid confusion.

    If both fields are present, indicatif.pb_show takes precedence and the progress bar is shown.

    use tracing_indicatif::IndicatifLayer;
    use tracing_indicatif::filter::IndicatifFilter;
    use tracing_indicatif::filter::hide_indicatif_span_fields;
    use tracing_subscriber::fmt::format::DefaultFields;
    use tracing_subscriber::layer::Layer;
    use tracing_subscriber::layer::SubscriberExt;
    use tracing_subscriber::util::SubscriberInitExt;
    
    let indicatif_layer = IndicatifLayer::new()
        .with_span_field_formatter(hide_indicatif_span_fields(DefaultFields::new()));
    
    tracing_subscriber::registry()
        .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer()))
        .with(indicatif_layer.with_filter(IndicatifFilter::new(false))) // false = don't show progress bars by default
        .init();
  11. Use indicatif_println! and indicatif_eprintln! macros

    main

    These macros allow you to print to stdout or stderr without interfering with the progress bars managed by IndicatifLayer.

    • indicatif_println!(...): Prints to stdout using the IndicatifLayer's stdout writer if available.
    • indicatif_eprintln!(...): Prints to stderr using the IndicatifLayer's stderr writer if available.

    WARNING: Do not call these macros inside a suspend_tracing_indicatif block, as this will cause a deadlock.

  12. Hide indicatif control fields from logs with hide_indicatif_span_fields

    main

    When using IndicatifFilter to control progress bars via span fields, those fields (indicatif.pb_show and indicatif.pb_hide) will appear in your log output by default. To keep your logs clean, use hide_indicatif_span_fields to wrap your field formatter. This function returns a FilteredFormatFields that ignores these specific control fields.

    use tracing_indicatif::filter::hide_indicatif_span_fields;
    use tracing_subscriber::fmt::format::DefaultFields;
    
    // Wrap your existing formatter to hide the control fields
    let formatter = hide_indicatif_span_fields(DefaultFields::new());