September 1, 2025
✏️ Edit on GitHubTLDR: Use the valuable
crate. Jump to solution →
If you look around the Rust ecosystem on how to "do logging", you'll be recommended the tracing crate pretty much everywhere you go[1][2][3][4][5]. You look at the docs and it says something about events, spans, and OpenTelemetry, but you don't really have time for that you just want to laaaaaaaawg.
You setup the example given and see that you can .json()
on the subscriber.. cool, lets try that.
Yay! We have some logs! In JSON too! Let's add some data..
Eww.. why does it look like that?
It's because we added the ?
sigil which tells the tracing subscriber to format it using its Debug
implementation. We don't really want that so.. what can we do? A lot of comments and LLMs might suggest to move the fields that you want to the top or even convert it to a serde_json::Value
first, and use the %
sigil for the Display
implementation..
Except... sometimes you don't know what those fields will be... and it's also extremely tedious. You also end up with the same problem on nested structs or arrays where they're still strings...
The tracing crate has an experimental feature flag since February 2022 which adds support for another crate called valuable. This crate + feature flag allows us to get the proper JSON formatted logs that we're looking for. Here is how to set it up:
First, add the valuable crate with cargo add valuable
.
Enable the derive
feature flag on valuable
, and the valuable
feature flag on tracing
and tracing-subscriber
:
During your cargo build
, enable unstable flags with RUSTFLAGS="--cfg tracing_unstable"
or, alternatively, create a .cargo/config.toml
file and add the Rust flags:
Now add #[derive(Valuable)]
to each struct, and call it using as_value()
:
Note: Enums are a little funky with the current implementation:
I've put together an example repo showing the valuable crate setup. I hope you found this helpful!