Expand description

A Rust library to produce Rust-like logs for source code or settings files.

It offers many features, like:

  • easy-to-use building patterns to customize the log entries;
  • annotating source code lines with the Rust-like ^^^ underline;
  • counting warnings and errors for multiple targets;
  • colored output to stderr (requires the console feature);
  • integration with the log API (requires the log feature);
  • integration with the indicatif crate (requires the indicatif feature);
  • color support with the console crate (requires the console feature).

Usage

The simplest usage consists of creating a target to log to and then log entries to it.

Example

// Create a target.
let target = Target::new("my-target");
// Log to the above target.
Task::new("Doing", "some work on `my-target`")
    .log_to_target(&target);
Entry::new_warning("this is a warning")
    .log_to_target(&target);
Entry::new_note("some other information about the above warning")
    .log_to_target(&target);
Task::new("Finish", "job on `my-target`")
    .log_to_target(&target);

The above code will produce the following result:

       Doing some work on `my-target`
warning: this is a warning
note: some other information about the above warning
      Finish job on `my-target`

Entry customization

It is possible to highly customize the log entry by specifying what caused it. To see another example, let’s try to recreate Rust output for the following code in a file called src/main.rs.

#![allow(unused_variables)]
fn main() {
    let mut x = 42;
}

The Rust compiler would complain that the variable x does not need to be mutable; let’s do the same.

// Example available at `examples/doc_entry_customization.rs`.
// Use the feature `console` to emit a colored output.

// Target receiving global messages.
// Note: "" is just a name and has no particular meaning.
let global_target = Target::new("");
// Target receiving messages for `example`.
let target = Target::new("example");

// First, log the fact that we are compiling our example.
Task::new("Compiling", "example v0.1.0")
    .log_to_target(&target);
// Then, emit a warning for the mutable `x`.
Entry::new_warning("variable does not need to be mutable")
    // The warning is for file `src/main.rs`, line 3, position 9.
    .named_source("src/main.rs", 3, 9)
    // Set the source line number (i.e., 3) and the respective source code.
    .new_line(3, "    let mut x = 42;")
    // Annotate the `mut ` string and add an help string.
    .annotate_help(9, 4, "help: remove this `mut`")?
    // Annotate the variable `x` (no help string needed).
    .annotate_warn(13, 1, "")?
    // Add a final note explaining why the user sees this warning.
    .note("`#[warn(unused_mut)]` on by default")
    // Finish the log line and output it.
    .finish()
    .log_to_target(&target)?;
// Then, finish parsing the file `src/main.rs` and output a summary of the warnings.
target.if_errors(|count|
    // This will not be displayed because no errors happened in `target`.
    Entry::new_error(format!("Could not run `example` due to {} previous error{}", count, if count > 1 { "s" } else { "" }))
        .log_to_target(&global_target));
target.if_warnings(|count|
    // This will be displayed because at least one warning was generated in `target`.
    Entry::new_warning(format!("`example` (bin) generated {} warning{}", count, if count > 1 { "s" } else { "" }))
        // However, we don't want to add the "summary warning" to the warning count for `example`,
        // therefore we log it to the global target.
        .log_to_target(&global_target));
// Finally, emit some entry telling that the compilation is finished.
Task::new("Finished", "dev [unoptimized + debuginfo] target(s)")
    .log_to_target(&global_target);

The above code should produce the following output:

      Compiling example v0.1.0
warning: variable does not need to be mutable
 --> src/main.rs:3:9
  |
3 |     let mut x = 42;
  |         ----^
  |         |
  |         help: remove this `mut`
  |
  = note: `#[warn(unused_mut)]` on by default

warning: `example` (bin) generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s)

Multiple entries

Sometimes, log events are made of multiple, related entries. Consider the following code.

// Example available at `examples/doc_multi_entry_first.rs`.
// Use the feature `console` to emit a colored output.

// Target `example`
let target = Target::new("example");

// Emit a warning for missing documentation.
Entry::new_warning("missing documentation for an associated function")
    .named_source("src/lib.rs", 1163, 5)
    .new_line(1163, "    pub fn log_to_target(self, target: &Target) {")
    .annotate_warn(5, 43, "")?
    .finish()
    .log_to_target(&target)?;
// Then, emit a note about the lint level.
Entry::new_note("the lint level is defined here")
    .named_source("src/lib.rs", 112, 9)
    .new_line(112, "#![warn(missing_docs)]")
    .annotate_note(9, 12, "")?
    .finish()
    .log_to_target(&target)?;

This will output the following:

warning: missing documentation for an associated function
    --> src/lib.rs:1163:5
     |
1163 |     pub fn log_to_target(self, target: &Target) {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: the lint level is defined here
   --> src/lib.rs:112:9
    |
112 | #![warn(missing_docs)]
    |         ^^^^^^^^^^^^

Instead, we could “pack together” the entries:

// Example available at `examples/doc_multi_entry_second.rs`.
// Use the feature `console` to emit a colored output.

// Target `example`
let target = Target::new("example");

// Emit a warning for missing documentation.
let warn_missing_doc = Entry::new_warning("missing documentation for an associated function")
    .named_source("src/lib.rs", 1163, 5)
    .new_line(1163, "    pub fn log_to_target(self, target: &Target) {")
    .annotate_warn(5, 43, "")?
    .finish();
// Then, emit a note about the lint level.
let note_lint_level = Entry::new_note("the lint level is defined here")
    .named_source("src/lib.rs", 112, 9)
    .new_line(112, "#![warn(missing_docs)]")
    .annotate_note(9, 12, "")?
    .finish();
// Finally, create the `MultiEntry`, which contains...
MultiEntry::new()
    // ... the first warning entry and...
    .entry(warn_missing_doc)
    // ... the second note entry;
    .entry(note_lint_level)
    // then, log the `MultiEntry`.
    .log_to_target(&target);

Now the output will look like this:

warning: missing documentation for an associated function
    --> src/lib.rs:1163:5
     |
1163 |     pub fn log_to_target(self, target: &Target) {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: the lint level is defined here
    --> src/lib.rs:112:9
     |
112  | #![warn(missing_docs)]
     |         ^^^^^^^^^^^^

Notice that the lines are now aligned and there is no gap between the entries.

Features

console

As introduced before, the console feature will produce colored output. Ideally, you should not use the console feature when outputting to a file; on the other side, the console feature makes the output to stderr more readable.

For instance, the output

warning: missing documentation for an associated function
    --> src/lib.rs:1163:5
     |
1163 |     pub fn log_to_target(self, target: &Target) {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: the lint level is defined here
    --> src/lib.rs:112:9
     |
112  | #![warn(missing_docs)]
     |         ^^^^^^^^^^^^

from the previous example would have the line numbers and bars colored in bright cyan, the warning label and respective ^^^^ annotation colored in bright yellow, and the note label and respective ^^^^ annotation colored in bright green.

indicatif

This crate also integrates with indicatif. In particular, it is possible to insert progress bars into a target or a target list so that the logged output is always above the progress bar.

// Create target `example`.
let target_list = TargetList::new();
let target = target_list.create_target("example")?;
// Create progress bar.
let pb = ProgressBar::new(4);
pb.set_style(ProgressStyle::default_bar());
// Add progress bar to target list.
target_list.add_progress_bar(pb.clone());

// Do stuff
for i in 0..4 {
    // Log to target.
    Task::new("Doing", format!("stuff {}", i + 1))
        .log_to_target(&target)?;
    // Do some intensive stuff.
    std::thread::sleep(std::time::Duration::from_millis(800));
    // Increase progress after finishing.
    pb.inc(1);
}

Modules

Errors returned by bad configuration of log entries or the logger itself.

Structs

A log entry.

Source builder for a log Entry.

A log entry which is given by the composition of multiple instances of Entry.

The prologue logger struct.

Log target containing information about the number of logged warnings/errors.

A list of log targets.

A log entry with no other information than a “verb” and some other text.