Skip to content

Commit

Permalink
core: add initial support for valuable field values (tokio-rs#1608)
Browse files Browse the repository at this point in the history
This branch adds initial support for using the [`valuable`] crate as an
opt-in `Value` type in `tracing`. `valuable` provides a mechanism for
defining custom ways to record user-implemented types, as well as
structured recording of standard library data structures such as maps,
arrays, and sets.

For more details, see the tracking issue tokio-rs#1570.

In `tracing` v0.2, the intent is for `valuable` to replace the existing
`tracing_core::field::Value` trait. However, in v0.1.x, `valuable`
support must be added in a backwards-compatible way, so recording types
that implement `valuable::Valueable` currently requires opting in using
a `field::valuable` wrapper function.

Since this is the first release of `valuable` and the API is still
stabilizing, we don't want to tie `tracing-core`'s stability to
`valuable`'s. Therefore, the valuable dependency is feature-flagged
*and* also requires `RUSTFLAGS="--cfg tracing_unstable"`.

[`valuable`]: https://github.com/tokio-rs/valuable

Co-authored-by: Daniel McKenna <[email protected]>
Co-authored-by: David Barsky <[email protected]>
Co-authored-by: Eliza Weisman <[email protected]>
  • Loading branch information
4 people authored and kaffarell committed May 22, 2024
1 parent 4792044 commit f49a0b6
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 0 deletions.
60 changes: 60 additions & 0 deletions examples/examples/valuable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#![allow(dead_code)]
//! This example shows how a field value may be recorded using the `valuable`
//! crate (https://crates.io/crates/valuable).
//!
//! `valuable` provides a lightweight but flexible way to record structured data, allowing
//! visitors to extract individual fields or elements of structs, maps, arrays, and other
//! nested structures.
//!
//! `tracing`'s support for `valuable` is currently feature flagged. Additionally, `valuable`
//! support is considered an *unstable feature*: in order to use `valuable` with `tracing`,
//! the project must be built with `RUSTFLAGS="--cfg tracing_unstable`.
//!
//! Therefore, when `valuable` support is not enabled, this example falls back to using
//! `fmt::Debug` to record fields that implement `valuable::Valuable`.
#[cfg(tracing_unstable)]
use tracing::field::valuable;
use tracing::{info, info_span};
use valuable::Valuable;

#[derive(Clone, Debug, Valuable)]
struct User {
name: String,
age: u32,
address: Address,
}

#[derive(Clone, Debug, Valuable)]
struct Address {
country: String,
city: String,
street: String,
}

fn main() {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
.init();

let user = User {
name: "Arwen Undomiel".to_string(),
age: 3000,
address: Address {
country: "Middle Earth".to_string(),
city: "Rivendell".to_string(),
street: "leafy lane".to_string(),
},
};

// If the `valuable` feature is enabled, record `user` using its'
// `valuable::Valuable` implementation:
#[cfg(tracing_unstable)]
let span = info_span!("Processing", user = valuable(&user));

// Otherwise, record `user` using its `fmt::Debug` implementation:
#[cfg(not(tracing_unstable))]
let span = info_span!("Processing", user = ?user);

let _handle = span.enter();
info!("Nothing to do");
}
45 changes: 45 additions & 0 deletions examples/examples/valuable_instrument.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#[cfg(tracing_unstable)]
mod app {
use std::collections::HashMap;
use tracing::field::valuable;
use tracing::{info, info_span, instrument};
use valuable::Valuable;

#[derive(Valuable)]
struct Headers<'a> {
headers: HashMap<&'a str, &'a str>,
}

// Current there's no way to automatically apply valuable to a type, so we need to make use of
// the fields argument for instrument
#[instrument(fields(headers=valuable(&headers)))]
fn process(headers: Headers) {
info!("Handle request")
}

pub fn run() {
let headers = [
("content-type", "application/json"),
("content-length", "568"),
("server", "github.com"),
]
.iter()
.cloned()
.collect::<HashMap<_, _>>();

let http_headers = Headers { headers };

process(http_headers);
}
}

fn main() {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
.init();

#[cfg(tracing_unstable)]
app::run();
#[cfg(not(tracing_unstable))]
println!("Nothing to do, this example needs --cfg=tracing_unstable to run");
}
3 changes: 3 additions & 0 deletions tracing-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ maintenance = { status = "actively-developed" }
[dependencies]
once_cell = { version = "1.13.0", optional = true }

[target.'cfg(tracing_unstable)'.dependencies]
valuable = { version = "0.1.0", optional = true, default_features = false }

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
38 changes: 38 additions & 0 deletions tracing-core/src/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,15 @@ pub struct Iter {
/// [set of `Value`s added to a `Span`]: super::collect::Collect::record
/// [`Event`]: super::event::Event
pub trait Visit {
/// Visits an arbitrary type implementing the [`valuable`] crate's `Valuable` trait.
///
/// [`valuable`]: https://docs.rs/valuable
#[cfg(all(tracing_unstable, feature = "valuable"))]
#[cfg_attr(docsrs, doc(cfg(all(tracing_unstable, feature = "valuable"))))]
fn record_value(&mut self, field: &Field, value: &dyn valuable::Valuable) {
self.record_debug(field, &value)
}

/// Visit a double-precision floating point value.
fn record_f64(&mut self, field: &Field, value: f64) {
self.record_debug(field, &value)
Expand Down Expand Up @@ -270,6 +279,14 @@ pub struct DisplayValue<T: fmt::Display>(T);
#[derive(Clone)]
pub struct DebugValue<T: fmt::Debug>(T);

/// A `Value` which serializes using [`Valuable`].
///
/// [`Valuable`]: https://docs.rs/valuable/latest/valuable/trait.Valuable.html
#[derive(Clone)]
#[cfg(all(tracing_unstable, feature = "valuable"))]
#[cfg_attr(docsrs, doc(cfg(all(tracing_unstable, feature = "valuable"))))]
pub struct ValuableValue<T: valuable::Valuable>(T);

/// Wraps a type implementing `fmt::Display` as a `Value` that can be
/// recorded using its `Display` implementation.
pub fn display<T>(t: T) -> DisplayValue<T>
Expand Down Expand Up @@ -658,6 +675,27 @@ impl<T: fmt::Debug> fmt::Debug for DebugValue<T> {
}
}

// ===== impl ValuableValue =====

#[cfg(all(tracing_unstable, feature = "valuable"))]
impl<T: valuable::Valuable> crate::sealed::Sealed for ValuableValue<T> {}

#[cfg(all(tracing_unstable, feature = "valuable"))]
#[cfg_attr(docsrs, doc(cfg(all(tracing_unstable, feature = "valuable"))))]
impl<T: valuable::Valuable> Value for ValuableValue<T> {
fn record(&self, key: &Field, visitor: &mut dyn Visit) {
visitor.record_value(key, &self.0)
}
}

#[cfg(all(tracing_unstable, feature = "valuable"))]
#[cfg_attr(docsrs, doc(cfg(all(tracing_unstable, feature = "valuable"))))]
impl<T: valuable::Valuable> fmt::Debug for ValuableValue<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", &self.0 as &dyn valuable::Valuable)
}
}

impl crate::sealed::Sealed for Empty {}
impl Value for Empty {
#[inline]
Expand Down
1 change: 1 addition & 0 deletions tracing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ alloc = ["tracing-core/alloc"]
std = ["tracing-core/std", "alloc"]
log-always = ["log"]
attributes = ["tracing-attributes"]
valuable = ["tracing-core/valuable"]

[[bench]]
name = "baseline"
Expand Down

0 comments on commit f49a0b6

Please sign in to comment.