Skip to main content

Crate tracing_scribe

Crate tracing_scribe 

Source
Expand description

A tracing-subscriber layer for beautifully printing spans and events to the terminal.

This crate provides a ConsoleLayer that can be used with tracing to format and display log messages in a hierarchical and easy-to-read format. It is designed to make command line applications more pleasant by providing clear visual cues for different log levels, tracking the duration of spans, and indicating success or failure states.

§Features

  • Pretty Printing: Spans and events are displayed with icons, colors, and indentation to create a clear visual hierarchy.
  • Automatic Result Handling: The console! macro automatically logs the outcome of a Result, indicating success or failure.
  • Span Timings: The layer automatically tracks and displays the execution time of each span.
  • Error Propagation: Errors inside spans are propagated to parent spans, marking them as failed.

§API Documentation

Click here!

§Usage

To use ConsoleLayer, add it to your tracing subscriber registry. The console! macro can then be used to log events with different status levels.

§Example

Here is a complete example demonstrating a successful execution with nested spans and #[tracing::instrument].

use tracing_scribe::console;
use tracing::info_span;
use tracing_subscriber::prelude::*;
use tracing_subscriber::registry::Registry;

#[tracing::instrument]
fn outer_task() {
    inner_task();
}

#[tracing::instrument]
fn inner_task() {
    console!(notice, "Generating unicorns");
    std::thread::sleep(std::time::Duration::from_millis(100));
    console!(info, "Unicorns generation completed");
}

let subscriber = Registry::default().with(tracing_scribe::ConsoleLayer::default());
tracing::subscriber::with_default(subscriber, || {
    let _span = info_span!("main").entered();
    outer_task();
});

This will produce the following output:

╭─ ⚙ main
│  ╭─ ⚙ outer_task
│  │  ╭─ ⚙ inner_task
│  │  │  ├─ ! Generating unicorns
│  │  │  ├─ ✔ Unicorns generation completed
│  │  ╰─ ✔ inner_task ⏱ 100.2ms
│  ╰─ ✔ outer_task ⏱ 100.3ms
╰─ ✔ main succeeded ⏱ 100.3ms

§Error Handling

The ConsoleLayer also handles errors gracefully. When a span instrumented with #[tracing::instrument(err)] returns an error, the span and its parents are marked as failed.

use tracing_scribe::console;
use tracing::info_span;
use tracing_subscriber::prelude::*;
use tracing_subscriber::registry::Registry;
use std::error::Error;
use std::fmt;

#[derive(Debug)]
struct MyError;

impl Error for MyError {}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Something went wrong")
    }
}

#[tracing::instrument(err)]
fn failing_task() -> Result<(), MyError> {
    console!(error, "This task is about to fail");
    Err(MyError)
}

let subscriber = Registry::default().with(tracing_scribe::ConsoleLayer::default());
tracing::subscriber::with_default(subscriber, || {
    let _span = info_span!("main").entered();
    failing_task().ok();
});

This will produce the following output:

╭─ ⚙ main
│  ╭─ ⚙ failing_task
│  │  ├─ ⨯ This task is about to fail
│  │  ├─ ⨯ Something went wrong
│  ╰─ ⨯ failing_task ⏱ 63.8µs
╰─ ⨯ main failed ⏱ 111.4µs

§Asynchronous tasks

Asynchronous tasks are handled their own way by tracing. To make sure they end up with the right indentation, you might have to wrap them with a tracing instrumentation call.

use tracing_scribe::console;
use tracing::Instrument;

#[tracing::instrument]
async fn outer_task() {
    tokio::spawn(
        inner_task().instrument(tracing::Span::current())
    );
}

#[tracing::instrument]
async fn inner_task() {
    console!(notice, "Generating async unicorns");
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    console!(info, "Async unicorns generation completed");
}

This will produce the following output:

╭─ ⚙ main
│  ╭─ ⚙ outer_task
│  │  ╭─ ⚙ inner_task
│  │  │  ├─ ! Generating async unicorns
│  │  │  ├─ ✔ Async unicorns generation completed
│  │  ╰─ ✔ inner_task ⏱ 100.2ms
│  ╰─ ✔ outer_task ⏱ 100.3ms
╰─ ✔ main succeeded ⏱ 100.3ms

§Attaching Extra Information

The extra field can be used to attach additional context to a span, which will be displayed alongside the span name.

use tracing_scribe::console;
use tracing::info_span;
use tracing_subscriber::prelude::*;
use tracing_subscriber::registry::Registry;

#[tracing::instrument(fields(extra = "some extra info"))]
fn task_with_extra() {
    console!(info, "This task has extra information");
}

let subscriber = Registry::default().with(tracing_scribe::ConsoleLayer::default());
tracing::subscriber::with_default(subscriber, || {
    let _span = info_span!("main").entered();
    task_with_extra();
});

This will produce the following output:

╭─ ⚙ main
│  ╭─ ⚙ task_with_extra some extra info
│  │  ├─ ✔ This task has extra information
│  ╰─ ✔ task_with_extra some extra info ⏱ 2.8µs
╰─ ✔ main succeeded ⏱ 23.3µs

§Custom Status

The with_status method allows you to customize the icons and colors used for the different statuses.

use tracing_scribe::{console, ConsoleLayer, StatusFactory, Status};
use tracing::info_span;
use tracing_subscriber::prelude::*;
use tracing_subscriber::registry::Registry;
use owo_colors::OwoColorize;
use std::io::{self, Write};

#[derive(Clone, Copy)]
pub struct CustomStatusFactory;

impl StatusFactory<CustomStatus> for CustomStatusFactory {
    fn group(&self) -> CustomStatus {
        CustomStatus::Group
    }
    fn success(&self) -> CustomStatus {
        CustomStatus::Success
    }
    fn failure(&self) -> CustomStatus {
        CustomStatus::Failure
    }
}

#[derive(Debug, Clone, Copy)]
pub enum CustomStatus {
    Success,
    Failure,
    Group,
    Pass,
}

impl Status for CustomStatus {
    fn from_str(s: &str) -> Self {
        match s {
            "success" => Self::Success,
            _ => Self::Failure,
        }
    }

    fn write_icon<W: Write>(&self, mut out: W, colors: &tracing_scribe::ColorScheme) -> io::Result<()> {
        match self {
            Self::Success => write!(out, "{}", colors.success.style("●")),
            Self::Failure => write!(out, "{}", colors.error.style("●")),
            Self::Group => write!(out, "{}", colors.group.style("●")),
            Self::Pass => write!(out, ""),
        }
    }

    fn is_passthrough(&self) -> bool {
        matches!(self, Self::Pass)
    }
}

let subscriber = Registry::default().with(
    ConsoleLayer::default().with_status(CustomStatusFactory)
);
tracing::subscriber::with_default(subscriber, || {
    let _span = info_span!("main").entered();
    console!(warn, "This is a custom status");
});

This will produce the following output:

╭─ ● main
│  ├─ ● This is a custom status
╰─ ● main succeeded

Macros§

console
Creates a tracing event with the required fields for our ConsoleLayer.

Structs§

ColorScheme
A color scheme for customizing the appearance of console output.
ConsoleLayer
A tracing-subscriber layer that formats and prints events to the console.
DefaultStatusFactory
The default status factory.

Enums§

DefaultStatus
The default status implementation.

Traits§

Status
A trait that defines the behavior of a status.
StatusFactory
A trait that is responsible for creating statuses.