yaymukund’s weblog

Wrapping Rust's log crate

Rust’s log crate crate is a thin macro interface for logging that can plug in to many different backends. Its functions can be called just like println!:

let you = "cow".to_string();
let name = "moo".to_string();

// There's also log::log, log::warn, and so on.
log::info!("Hello {animal}, {} to you", greeting);

I wanted to wrap it to add a prefix [farm] so the output looks like:

[farm] Hello cow, moo to you

Easy, right? 😅

My first attempt was a macro that tried to concat! the prefix:

macro_rules! warn {
    ($str:literal) => (::log::warn!(concat!("[farm] ", $str)));
    ($str:literal, $($args:tt)*) => (::log::warn!(concat!("[farm] ", $str), $($args)*))
}

But it doesn’t work:

error: there is no argument named `animal`
  --> src/log.rs:3:51
   |
3  |     ($str:literal, $($args:tt)*) => (::log::warn!(concat!("[farm] ", $str), $(...
   |                                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
  ::: src/main.rs:46:5
   |
46 |     warn!("Hello {animal}");
   |     -------------------- in this macro invocation

Keep in mind that animal was previously defined with a let-statement as in the example code, so it should be in the calling context. Fortunately, there’s a helpful note:

= note: did you intend to capture a variable `animal` from the surrounding scope?
= note: to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro

The solution is straightforward. We just call format_args! ourselves!

// It works!
macro_rules! warn {
    ($str:literal) => (::log::warn!("[farm] {}", format_args!($str)));
    ($str:literal, $($args:tt)*) => (::log::warn!("[farm] {}", format_args!($str, $($args)*)))
}