The tracing library itself does not display any output or write to any files. It is simply a facade that is designed to allow developers to fire events that are grouped into spans in an agnostic way that is separate from how those events and spans are handled. The library is complicated because there are a multitude of ways to track spans and consume events and they need to be supported in a lightweight infrastructure.
However, as a user you don't need to really worry about any of that. All you need to do is use the trace!
, debug!
, info!
, warn!
, error!
and #[instrument]
macros to describe what your code is doing. Then if you're writing a program that should do something with those events and spans, you should set up a Subscriber
.
The simplest way to get up and running with logs printed to the console is using the basic formatting subscriber from the tracing-subscriber crate, and then you'll see events logged to the console:
use tracing::{info, debug};
#[tracing::instrument(ret)]
fn calculate(data_set: &[i32]) -> i32 {
info!("starting calculations");
let first = data_set.first();
debug!("received {:?} as first element", first);
let last = data_set.last();
debug!("received {:?} as last element", last);
first.unwrap_or(&0) + last.unwrap_or(&0)
}
fn main() {
tracing_subscriber::fmt().init();
// start running your code
calculate(&[1509, 1857, 1736, 1815, 1576]);
}
2022-09-15T16:11:47.830218Z INFO calculate{data_set=[1509, 1857, 1736, 1815, 1576]}: mycrate: starting calculations
2022-09-15T16:11:47.830545Z INFO calculate{data_set=[1509, 1857, 1736, 1815, 1576]}: mycrate: return=3085
So fmt().init()
can get you going in a hurry; it does the job of creating a Subscriber
with a bunch of sensible defaults and registers itself as the global subscriber. It is very flexible too, you may note the output above doesn't include the debug!
events, and you may not care about showing the crate name in every message. That can be configured:
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_target(false)
.init();
2022-09-15T16:18:51.316695Z INFO calculate{data_set=[1509, 1857, 1736, 1815, 1576]}: starting calculations
2022-09-15T16:18:51.316970Z DEBUG calculate{data_set=[1509, 1857, 1736, 1815, 1576]}: received Some(1509) as first element
2022-09-15T16:18:51.317214Z DEBUG calculate{data_set=[1509, 1857, 1736, 1815, 1576]}: received Some(1576) as last element
2022-09-15T16:18:51.317381Z INFO calculate{data_set=[1509, 1857, 1736, 1815, 1576]}: return=3085
Everything is designed to be flexible: If you want to send to a file instead of stdout, there's .with_writer()
. If you want to send logs to multiple locations, you can compose multiple subscribers with a Registry
. If you want to filter only certain events based on their level, source, or content, there's .with_filter
. If you want to log in a JSON format instead of human readable text, you can use .json()
.
A common next step would be to set up EnvFilter
which would allow you to configure what events to log based on an environment variable instead of trying to configure it in-code. It can be as simple as "info"
/"trace"
to set a max level, or it could be complicated like "warn,mycrate=debug"
(log debug events from my crate, only log warnings for anything else).
Beyond that, you're probably looking for something a bit more focused. Like you need to log in a specific format. Or you need to send events directly to an external provider. Or you're simply looking for a different way to view your data. Look in tracing's Related Crates section for other Subscriber
s that will hopefully suit your needs.