tracing is a free, open source monitoring & observability project written in Rust and released under MIT. It has 6,877 GitHub stars, 932 forks and 887 open issues, and was last pushed 4 months ago. On this registry it ranks #59 of 97 tracked projects in Monitoring & Observability, with 5 head-to-head comparisons available. It gained 3 stars over the last 3 tracked days.

What is tracing?

tracing is a Rust framework for instrumenting programs to collect structured, event-based diagnostic information, and it is aimed at Rust developers and library authors who need observability beyond plain text log lines.

What it is

tracing is a framework for instrumenting Rust programs to collect structured, event-based diagnostic information. It is maintained by the Tokio project, but it does not require the tokio runtime to be used. The crates are published on crates.io, documented on docs.rs, and presented at tracing.rs, with community chat on Discord. Development runs on two branches: main is the default branch from which crates.io releases are cut, formerly the v0.1.x branch, and v0.2.x holds an as-yet unreleased 0.2 version of tracing-core, tracing, and all the other tracing crates that depend on those versions.

The concrete problem it solves is unstructured logging in Rust. Executables record trace events only through a Subscriber implementation compatible with tracing, which defines how trace data is collected, for example by logging it to standard output. Because spans and events carry fields and context rather than formatted strings, the same instrumentation can feed different collection strategies, and any trace events generated outside the context of a subscriber are not collected at all. It occupies the logging-facade and logging-library niche: tracing-subscriber consumes messages emitted by libraries instrumented with the older log crate, so existing log-instrumented modules keep working.

Key capabilities

  • Structured, event-based instrumentation built on spans and events, emitted from application code with macros such as info!.
  • Pluggable Subscriber trait: the executable chooses how trace data is collected, including custom destinations and formats.
  • tracing-subscriber's fmt module supplies a subscriber with reasonable defaults, installed with tracing_subscriber::fmt::init().
  • Compatibility with the log crate: tracing-subscriber can consume messages emitted by log-instrumented libraries and modules.
  • Level filtering through with_max_level(Level::TRACE) or a RUST_LOG-environment-driven configuration in the fmt init path, plus staged builder use with .finish().
  • Scoped, non-global subscribers via tracing::subscriber::with_default(), allowing multiple subscribers to collect trace data in different contexts within one program.
  • Two release tracks: main for crates.io releases, and v0.2.x for the unreleased 0.2 line of tracing-core, tracing, and dependent crates.

Who uses it and how

  • Application authors instrumenting Rust services, who install a global subscriber through set_global_default() or init() so every thread in the process reports through it.
  • Teams migrating gradually from log-instrumented libraries, since tracing-subscriber accepts those messages while new code emits spans and events.
  • Library and crate authors, who add tracing macros as a facade and leave the subscriber choice to the binaries that depend on them.
  • Programs needing different collection contexts at once, using with_default() to override the default subscriber locally rather than globally.
  • The wider Tokio ecosystem, as tracing is maintained by the Tokio project, though no tokio runtime is required; adoption stands at roughly 6,877 stars and 932 forks.

Getting started

Add tracing = "0.1" and tracing-subscriber = "0.3" to Cargo.toml, then install a subscriber in main with tracing_subscriber::fmt::init(), which configures output from the RUST_LOG environment variable. Documentation lives at docs.rs/tracing, tracing.rs, and the linked v0.2.x docs.

How it compares

Among similar tools named in the facts, the closest relative is the log crate, whose logging-facade model tracing follows but extends with spans, structured fields, and subscriber-controlled collection. Rather than replacing that ecosystem outright, tracing-subscriber reads log messages, so the two coexist inside the same binary.

When to use it — and when not to

It fits Rust projects that want structured, contextual diagnostics and are willing to install and configure a subscriber rather than print lines. It is a poor fit for teams that only need unstructured text output, because events emitted with no subscriber in context are silently dropped, and every executable must make a subscriber choice. Worth noting honestly: the README is a usage document with a truncated override example, the 0.2 line remains unreleased, and 887 open issues indicate a project with substantial ongoing churn.

project readme (upstream, from github) — read inline

Tracing — Structured, application-level diagnostics

Crates.io Documentation Documentation (v0.2.x) MIT licensed Build Status Discord chat

Website | Chat

Overview

tracing is a framework for instrumenting Rust programs to collect structured, event-based diagnostic information. tracing is maintained by the Tokio project, but does not require the tokio runtime to be used.

Branch set-up

  • main - Default branch, crates.io releases are done from this branch. This was previously the v0.1.x branch.
  • v0.2.x - Branch containing the as-yet unreleased 0.2 version of tracing-core, tracing, and all the other tracing crates that depend on these versions. This was previously the master branch.

Usage

In Applications

In order to record trace events, executables have to use a Subscriber implementation compatible with tracing. A Subscriber implements a way of collecting trace data, such as by logging it to standard output. tracing-subscriber's fmt module provides a subscriber for logging traces with reasonable defaults. Additionally, tracing-subscriber is able to consume messages emitted by log-instrumented libraries and modules.

To use tracing-subscriber, add the following to your Cargo.toml:

[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"

Then create and install a Subscriber, for example using init():

use tracing::info;
use tracing_subscriber;

fn main() {
    // install global subscriber configured based on RUST_LOG envvar.
    tracing_subscriber::fmt::init();

    let number_of_yaks = 3;
    // this creates a new event, outside of any spans.
    info!(number_of_yaks, "preparing to shave yaks");

    let number_shaved = yak_shave::shave_all(number_of_yaks);
    info!(
        all_yaks_shaved = number_shaved == number_of_yaks,
        "yak shaving completed."
    );
}

Using init() calls set_global_default() so this subscriber will be used as the default in all threads for the remainder of the duration of the program, similar to how loggers work in the log crate.

For more control, a subscriber can be built in stages and not set globally, but instead used to locally override the default subscriber. For example:

use tracing::{info, Level};
use tracing_subscriber;

fn main() {
    let subscriber = tracing_subscriber::fmt()
        // filter spans/events with level TRACE or higher.
        .with_max_level(Level::TRACE)
        // build but do not install the subscriber.
        .finish();

    tracing::subscriber::with_default(subscriber, || {
        info!("This will be logged to stdout");
    });
    info!("This will _not_ be logged to stdout");
}

Any trace events generated outside the context of a subscriber will not be collected.

This approach allows trace data to be collected by multiple subscribers within different contexts in the program. Note that the override only applies to the currently executing thread; other threads will not see the change from with_default.

Once a subscriber has been set, instrumentation points may be added to the executable using the tracing crate's macros.

In Libraries

Libraries should only rely on the tracing crate and use the provided macros and types to collect whatever information might be useful to downstream consumers.

use std::{error::Error, io};
use tracing::{debug, error, info, span, warn, Level};

// the `#[tracing::instrument]` attribute creates and enters a span
// every time the instrumented function is called. The span is named after the
// function or method. Parameters passed to the function are recorded as fields.
#[tracing::instrument]
pub fn shave(yak: usize) -> Result<(), Box<dyn Error + 'static>> {
    // this creates an event at the DEBUG level with two fields:
    // - `excitement`, with the key "excitement" and the value "yay!"
    // - `message`, with the key "message" and the value "hello! I'm gonna shave a yak."
    //
    // unlike other fields, `message`'s shorthand initialization is just the string itself.
    debug!(excitement = "yay!", "hello! I'm gonna shave a yak.");
    if yak == 3 {
        warn!("could not locate yak!");
        // note that this is intended to demonstrate `tracing`'s features, not idiomatic
        // error handling! in a library or application, you should consider returning
        // a dedicated `YakError`. libraries like snafu or thiserror make this easy.
        return Err(io::Error::new(io::ErrorKind::Other, "shaving yak failed!").into());
    } else {
        debug!("yak shaved successfully");
    }
    Ok(())
}

pub fn shave_all(yaks: usize) -> usize {
    // Constructs a new span named "shaving_yaks" at the TRACE level,
    // and a field whose key is "yaks". This is equivalent to writing:
    //
    // let span = span!(Level::TRACE, "shaving_yaks", yaks = yaks);
    //
    // local variables (`yaks`) can be used as field values
    // without an assignment, similar to struct initializers.
    let span = span!(Level::TRACE, "shaving_yaks", yaks);
    let _enter = span.enter();

    info!("shaving yaks");

    let mut yaks_shaved = 0;
    for yak in 1..=yaks {
        let res = shave(yak);
        debug!(yak, shaved = res.is_ok());

        if let Err(ref error) = res {
            // Like spans, events can also use the field initialization shorthand.
            // In this instance, `yak` is the field being initialized.
            error!(yak, error = error.as_ref(), "failed to shave yak!");
        } else {
            yaks_shaved += 1;
        }
        debug!(yaks_shaved);
    }

    yaks_shaved
}
[dependencies]
tracing = "0.1"

Note: Libraries should NOT install a subscriber by using a method that calls set_global_default(), as this will cause conflicts when executables try to set the default later.

In Asynchronous Code

To trace async fns, the preferred method is using the [#[instrument]][instrument] attribute:

use tracing::{info, instrument};
use tokio::{io::AsyncWriteExt, net::TcpStream};
use std::io;

#[instrument]
async fn write(stream: &mut TcpStream) -> io::Result<usize> {
    let result = stream.write(b"hello world\n").await;
    info!("wrote to stream; success={:?}", result.is_ok());
    result
}

Special handling is needed for the general case of code using [std::future::Future][std-future] or blocks with async/await, as the following example will not work:

async {
    let _s = span.enter();
    // ...
}

The span guard _s will not exit until the future generated by the async block is complete. Since futures and spans can be entered and exited multiple times without them completing, the span remains entered for as long as the future exists, rather than being entered only when it is polled, leading to very confusing and incorrect output. For more details, see [the documentation on closing spans][closing].

This problem can be solved using the [Future::instrument] combinator:

use tracing::Instrument;

let my_future = async {
    // ...
};

my_future
    .instrument(tracing::info_span!("my_future"))
    .await

Future::instrument attaches a span to the future, ensuring that the span's lifetime is a

readme truncated — read the full docs on github

Frequently asked questions

Is tracing free to use?

tracing is open source under the MIT licence. There is no licence fee and no seat count — you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does tracing do?

Application level tracing for Rust.

What is tracing written in?

tracing is primarily written in Rust. Its source is publicly available at https://github.com/tokio-rs/tracing, and it has 6,877 GitHub stars.