Tracing
Application level tracing for Rust.
Install / Use
/learn @tokio-rs/TracingREADME
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 thev0.1.xbranch.v0.2.x- Branch containing the as-yet unreleased 0.2 version oftracing-core,tracing, and all the other tracing crates that depend on these versions. This was previously themasterbranch.
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] 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 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.
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 as long as the future's.
Under the hood, the #[instrument] macro performs the same explicit span
attachment that Future::instrument does.
Supported Rust Versions
Tracing is built against the latest stable release. The minimum supported version is 1.65. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version.
Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable compiler version is 1.69, the minimum suppo
Related Skills
himalaya
336.5kCLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).
node-connect
336.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
82.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
coding-agent
336.5kDelegate coding tasks to Codex, Claude Code, or Pi agents via background process
