How to log structured JSON in Rust

September 1, 2025

✏️ Edit on GitHub

TLDR: 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.

Terminal output showing basic JSON logging with tracing crate - displays a simple 'Hi!' message with timestamp and level fields in JSON format

Yay! We have some logs! In JSON too! Let's add some data..

Terminal output showing problematic JSON logging with Debug formatting - user data appears as escaped strings instead of proper JSON objects

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..

Code snippet showing workaround attempt using serde_json::Value and Display formatting with % sigil - still results in nested objects being stringified

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 Solution

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.

Terminal output from 'cargo add valuable' command showing the valuable crate being added to dependencies with version 0.1.1

Enable the derive feature flag on valuable, and the valuable feature flag on tracing and tracing-subscriber:

Cargo.toml file showing feature flag configuration - valuable crate with derive feature, and tracing/tracing-subscriber with valuable features enabled

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:

Cargo config.toml file showing rustflags configuration with '--cfg tracing_unstable' flag to enable experimental valuable support

Now add #[derive(Valuable)] to each struct, and call it using as_value():

Rust source code showing structs with #[derive(Debug, Serialize, Valuable)] attributes and logging call using user.as_value() method

Note: Enums are a little funky with the current implementation:

Terminal output demonstrating enum serialization issue - shows how Transmission::Manual and Transmission::Automatic enums are represented in JSON logs

I've put together an example repo showing the valuable crate setup. I hope you found this helpful!