Library / SDK
tokio-rs/tracing avatar
tokio-rs/tracing

tokio-rs/tracing: structured diagnostics for Rust programs

Application level tracing for Rust.

6,895 stars940 forksRustMIT

At a glance

What is it?
tracing is a Rust instrumentation framework with a subscriber-based collection model. It fits applications and libraries that need spans and structured fields, not a plain log line, and it ships from crates.io under MIT.
Who is it for?
Adopt tracing if your Rust code needs spans that nest, fields that carry typed values, or a library that should emit diagnostics without choosing an output backend. Do not adopt it expecting a drop-in replacement for a logging facade: without an installed Subscriber, events are not collected at all.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 123 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What tracing solves that println and log do not

A log line is a string with a severity attached. That is enough when you want to know that something happened. It is not enough when you want to know which request it happened inside, how long the enclosing operation took, or what the value of a specific variable was at that point. tracing was built for that second case. The README describes it as "a framework for instrumenting Rust programs to collect structured, event-based diagnostic information". The two words that matter are structured and event-based: fields are recorded with keys, and spans give events a context they can be attributed to.

The audience is split. Application authors get spans that wrap work, plus subscribers that decide where the data goes. Library authors get macros they can call without committing to an output format, which is the reason a crate can emit tracing events and still let the binary decide whether they become JSON, human-readable lines, or nothing. The README makes that division explicit: libraries should rely on the tracing crate, while executables install a Subscriber.

It is maintained by the Tokio project. The README is careful to note that it does not require the tokio runtime, which matters if you are writing synchronous code and only want the instrumentation.

Spans, events and the Subscriber that collects them

The mechanism is a separation between emission and collection. Instrumentation points in your code emit events and enter spans. A Subscriber implementation collects them. Nothing else happens. The README states this directly: "Any trace events generated outside the context of a subscriber will not be collected."

A span is created with the span! macro or the #[tracing::instrument] attribute, and entering it makes it current for the duration of a guard. The README's example builds a span named "shaving_yaks" at TRACE level with a field whose key is "yaks", then enters it. Events emitted inside that scope carry the span as context.

The #[tracing::instrument] attribute is the shortcut. It creates and enters a span every time the instrumented function is called, names the span after the function, and records the passed parameters as fields. So a function taking a yak: usize produces a span with a yak field without any manual span! call.

Fields are not formatted into a string at the call site. They are recorded as key-value pairs, which is what lets a subscriber serialize them, filter on them, or drop them. The message field is special: unlike other fields its shorthand initialization is just the string itself.

The workspace itself is the other half of the picture. The root Cargo.toml lists fifteen members, including tracing-core, tracing-attributes, tracing-subscriber, tracing-log, tracing-serde, tracing-appender, tracing-flame, tracing-error, tracing-journald, tracing-mock and tracing-test. tracing-core holds the interfaces, tracing-attributes holds the procedural macros, and the rest are integrations: a log compatibility layer, serde serialization, a rolling file appender, flamegraph output, journald output, and test helpers. You do not install the workspace. You depend on the pieces you need.

Installing tracing and getting the first event out

The README gives the dependency block for an application. Both crates come from crates.io, and the versions shown are tracing 0.1 and tracing-subscriber 0.3. Add them to your Cargo.toml:

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

Then install a global subscriber and emit an event. The README's example calls tracing_subscriber::fmt::init(), which installs a subscriber that logs traces to standard output with reasonable defaults and reads the RUST_LOG environment variable for filtering:

rust
use tracing::info;
use tracing_subscriber;

fn main() {
    tracing_subscriber::fmt::init();

    let number_of_yaks = 3;
    info!(number_of_yaks, "preparing to shave yaks");
}

Run the binary with RUST_LOG set and you should see the event on stdout. Without RUST_LOG set, the fmt subscriber's default filter applies, so lowering the level for a module is a matter of the environment variable rather than a code change.

init() calls set_global_default() under the hood, so the subscriber applies to all threads for the rest of the program, in the same way loggers work in the log crate. That is convenient and also one-way: once set, it is the default.

If you want a subscriber for one scope instead, the README shows building it in stages with fmt(), calling with_max_level(Level::TRACE), and then finish() to build without installing. Passing that to tracing::subscriber::with_default runs the closure under it. The README warns that the override applies only to the currently executing thread; other threads will not see the change.

Where the model works against you

The first failure mode is silence. A program that emits tracing events but never installs a subscriber produces no output. There is no warning and no fallback to stderr. If you are porting a codebase from a logging facade, that difference will bite the first time a binary forgets its subscriber setup.

The second is the thread-local scope of with_default. It is a useful tool for tests and for isolating one code path, but it is not a process-wide switch, and the README says so plainly. Code that spawns work onto other threads will not inherit the override.

The third is the split between the main branch and the v0.2.x branch. According to the README, main is the default branch and crates.io releases are done from it, and it was previously the v0.1.x branch. The v0.2.x branch holds the as-yet unreleased 0.2 version of tracing-core, tracing, and the crates that depend on them. That means the 0.2 line is not what you get from crates.io today, and a project that needs it is building from a branch.

Finally, the workspace shape is a cost in itself. tracing-core is the shared interface, so a dependency graph that pins it in two places is a real constraint. The root Cargo.toml also carries workspace lint configuration with check-cfg entries for cfg(flaky_tests), cfg(tracing_unstable) and cfg(unsound_local_offset), which tells you the crates have conditional compilation paths that are not part of the ordinary build.

tracing versus the log crate

The obvious alternative is log, the older Rust logging facade, and tracing-subscriber can consume messages emitted by log-instrumented libraries and modules through the tracing-log crate. That compatibility is the honest way to compare them, because it means the choice is not all-or-nothing: a binary using tracing can still surface output from dependencies that only know about log.

The difference in approach is what the two crates can express. log carries a level and a formatted message. tracing carries a level, a message, and named fields, and it adds spans that events can be attributed to. If your diagnostic need is a line of text with a severity, log is smaller and has no subscriber to install. If your need is to correlate events with the operation that produced them, or to record values as data rather than as text, log has nowhere to put that information and tracing does.

The cost of that extra expressiveness is the subscriber. log needs a logger installed too, but the tracing model adds a second decision layer: which subscriber, at what level, writing where. The workspace reflects that with separate crates for appender, journald, flame and serde output. You are choosing an output pipeline, not just a level.

Maintenance, releases and the MIT licence

The repository is not archived. The last push was on 2026-05-30, which is inside the six-month window. The most recent release listed is tracing-appender 0.2.5 on 2026-04-17, followed by tracing-subscriber 0.3.23 on 2026-03-13 and tracing-core 0.1.36 on 2025-12-18. Those are separate crates with separate version numbers, so an upgrade is per-crate rather than a single workspace bump.

That versioning is the practical upgrade cost. tracing, tracing-core and tracing-subscriber move on their own schedules, and the 0.2 line lives on a branch rather than on crates.io. Before upgrading, check which of the three you actually depend on directly and which arrive transitively, because a minor bump in tracing-core can ripple through everything that implements a Subscriber.

The licence is MIT, stated in the README badge and present as a LICENSE file at the repository root. MIT is permissive: it allows use in closed-source products provided the copyright notice and permission notice are preserved. That is a description of the licence text, not legal advice, and the obligations that apply to your distribution model are a question for your own counsel.

The repository also carries a SECURITY.md and a CONTRIBUTING.md at the top level, so there is a stated path for reporting issues and for contributing, though the README does not document a support window or a deprecation policy for the 0.1 line.

Editorial conclusion

Adopt tracing if your Rust code needs spans that nest, fields that carry typed values, or a library that should emit diagnostics without choosing an output backend. Do not adopt it expecting a drop-in replacement for a logging facade: without an installed Subscriber, events are not collected at all. Verify first that your crate graph does not already pin tracing-core to a version your chosen subscriber build cannot use, and read the workspace Cargo.toml before adding crates, since it lists fifteen members including tracing-journald and tracing-appender.

Frequently asked questions

Does tracing require the tokio runtime?

No. The README states that tracing is maintained by the Tokio project but does not require the tokio runtime to be used.

Why do my tracing events produce no output?

Because no Subscriber has been installed. The README states that any trace events generated outside the context of a subscriber will not be collected, so an executable has to install one, for example with tracing_subscriber::fmt::init().

Can tracing read messages from libraries that use the log crate?

Yes. The README says tracing-subscriber is able to consume messages emitted by log-instrumented libraries and modules, which is provided through the tracing-log crate in the workspace.

Does with_default change the subscriber for every thread?

No. The README notes that the override applies only to the currently executing thread, and other threads will not see the change from with_default.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. tokio-rs/tracing on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tokio-rs-tracing.svg)](https://hysenlabs.com/projects/tokio-rs-tracing)