From 5d086345018d66ffce35cbdb84b0ecd31f6c40fd Mon Sep 17 00:00:00 2001 From: xd009642 Date: Fri, 21 Jan 2022 21:29:38 +0000 Subject: [PATCH] core: add initial support for `valuable` field values (#1608) 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 Co-authored-by: David Barsky Co-authored-by: Eliza Weisman --- examples/Cargo.toml | 6 +++ examples/examples/valuable.rs | 60 ++++++++++++++++++++++++ examples/examples/valuable_instrument.rs | 45 ++++++++++++++++++ tracing-core/Cargo.toml | 5 +- tracing-core/src/field.rs | 51 ++++++++++++++++++++ tracing/Cargo.toml | 1 + 6 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 examples/examples/valuable.rs create mode 100644 examples/examples/valuable_instrument.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index d264b66309..f2d7eb515b 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -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"]} diff --git a/examples/examples/valuable.rs b/examples/examples/valuable.rs new file mode 100644 index 0000000000..53913a52c6 --- /dev/null +++ b/examples/examples/valuable.rs @@ -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"); +} diff --git a/examples/examples/valuable_instrument.rs b/examples/examples/valuable_instrument.rs new file mode 100644 index 0000000000..a8f93b5a8e --- /dev/null +++ b/examples/examples/valuable_instrument.rs @@ -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::>(); + + 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"); +} diff --git a/tracing-core/Cargo.toml b/tracing-core/Cargo.toml index 1cc3b3d8f0..1581bb93f6 100644 --- a/tracing-core/Cargo.toml +++ b/tracing-core/Cargo.toml @@ -27,7 +27,7 @@ edition = "2018" rust-version = "1.42.0" [features] -default = ["std"] +default = ["std", "valuable/std"] std = ["lazy_static"] [badges] @@ -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"] diff --git a/tracing-core/src/field.rs b/tracing-core/src/field.rs index 614a7c59c5..26b1006261 100644 --- a/tracing-core/src/field.rs +++ b/tracing-core/src/field.rs @@ -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) @@ -249,6 +258,14 @@ pub struct DisplayValue(T); #[derive(Clone)] pub struct DebugValue(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); + /// Wraps a type implementing `fmt::Display` as a `Value` that can be /// recorded using its `Display` implementation. pub fn display(t: T) -> DisplayValue @@ -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) -> ValuableValue +where + T: valuable::Valuable, +{ + ValuableValue(t) +} + // ===== impl Visit ===== impl<'a, 'b> Visit for fmt::DebugStruct<'a, 'b> { @@ -539,6 +569,27 @@ impl fmt::Debug for DebugValue { } } +// ===== impl ValuableValue ===== + +#[cfg(all(tracing_unstable, feature = "valuable"))] +impl crate::sealed::Sealed for ValuableValue {} + +#[cfg(all(tracing_unstable, feature = "valuable"))] +#[cfg_attr(docsrs, doc(cfg(all(tracing_unstable, feature = "valuable"))))] +impl Value for ValuableValue { + 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 fmt::Debug for ValuableValue { + 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] diff --git a/tracing/Cargo.toml b/tracing/Cargo.toml index 699d79a339..4bd6488daf 100644 --- a/tracing/Cargo.toml +++ b/tracing/Cargo.toml @@ -66,6 +66,7 @@ async-await = [] std = ["tracing-core/std"] log-always = ["log"] attributes = ["tracing-attributes"] +valuable = ["tracing-core/valuable"] [[bench]] name = "subscriber"