I’m still on the noob side of Rust and I’m reading about the available logging crates. Something about the log
crate and its different implementations looks like magic to me. Take for example this snippet (taken from this article):
use log::debug;
use log::error;
use log::info;
use log::warn;
fn main() {
env_logger::init();
debug!("Mary has a little lamb");
error!("{}", "Its fleece was white as snow");
info!("{:?}", "And every where that Mary went");
warn!("{:#?}", "The lamb was sure to go");
}
env_logger
is initialized, then the macros in log
can be used immediately. How does log
know that there is a logger defined and where to find it? What happens if there are several of them defined at the same time, how is it going to choose? In the case of env_logger
, calling env_logger::init()
a second time makes the thread panic, but is it a general rule?
Under the hood, specific logger implementations will call log::set_logger
(or one of the other functions log
provides) to install the logger globally. log
has a global (static) &dyn Log
variable that holds a reference to the current logger.
What happens if there are several of them defined at the same time, how is it going to choose?
log
doesn’t expose a way to replace the global logger once it is installed; the family of functions that install a logger all fail when called a second time. The scenario you describe of having multiple active loggers is therefore impossible.
The log
crate maintains a global logger (&'static dyn Log
) that is uninitialized and unregistered by default.
When you initialize a logging backend with env_logger::init()
, that backend registers itself as the global logger by calling log::set_logger()
. Trying to initialize a second backend will usually panic, because log
wants to avoid confusion about how and where to send messages, and it will assume the second backend is an unintended error.
If you don’t initialize a logger backend at all, then the macros will still work, but they will have no global logger to forward the messages to.
You can include log
macros in your lib crate, which will allow the user to choose and set their own global logger (if they want to).
For example, say your crate mister_t
has a function:
// lib.rs
pub fn foo() {
log::info!("I pity the foo!")
}
Whoever uses mister_t
can then access your logging like this:
// main.rs
fn main() {
env_logger::init();
mister_t::foo();
}
Or they can run it silently, like this:
fn main() {
mister_t::foo();
}
The first will print the info, and the latter will log nothing but still run the rest of foo
.
It’s designed to be flexible and intuitive, but single-track. If you need more complex log handling, I suggest you look at tracing
.