Skip to content

Commit

Permalink
core: add initial support for valuable field values (#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 #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 <daniel@emotech.co>
Co-authored-by: David Barsky <me@davidbarsky.com>
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
  • Loading branch information
4 people authored Jan 21, 2022
1 parent 71fc562 commit 5d08634
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 1 deletion.
6 changes: 6 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,9 @@ opentelemetry-jaeger = "0.15"
# fmt examples
snafu = "0.6.10"
thiserror = "1.0.26"

# valuable examples
valuable = { version = "0.1.0", features = ["derive"] }

[target.'cfg(tracing_unstable)'.dependencies]
tracing-core = { path = "../tracing-core", version = "0.1", features = ["valuable"]}
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");
}
5 changes: 4 additions & 1 deletion tracing-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ edition = "2018"
rust-version = "1.42.0"

[features]
default = ["std"]
default = ["std", "valuable/std"]
std = ["lazy_static"]

[badges]
Expand All @@ -36,6 +36,9 @@ maintenance = { status = "actively-developed" }
[dependencies]
lazy_static = { version = "1", 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"]
51 changes: 51 additions & 0 deletions tracing-core/src/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,15 @@ pub struct Iter {
/// [`Event`]: ../event/struct.Event.html
/// [`ValueSet`]: struct.ValueSet.html
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 @@ -249,6 +258,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 All @@ -267,6 +284,19 @@ where
DebugValue(t)
}

/// Wraps a type implementing [`Valuable`] as a `Value` that
/// can be recorded using its `Valuable` implementation.
///
/// [`Valuable`]: https://docs.rs/valuable/latest/valuable/trait.Valuable.html
#[cfg(all(tracing_unstable, feature = "valuable"))]
#[cfg_attr(docsrs, doc(cfg(all(tracing_unstable, feature = "valuable"))))]
pub fn valuable<T>(t: T) -> ValuableValue<T>
where
T: valuable::Valuable,
{
ValuableValue(t)
}

// ===== impl Visit =====

impl<'a, 'b> Visit for fmt::DebugStruct<'a, 'b> {
Expand Down Expand Up @@ -539,6 +569,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 @@ async-await = []
std = ["tracing-core/std"]
log-always = ["log"]
attributes = ["tracing-attributes"]
valuable = ["tracing-core/valuable"]

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

0 comments on commit 5d08634

Please sign in to comment.