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 <daniel@emotech.co>
Co-authored-by: David Barsky <me@davidbarsky.com>
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
This commit is contained in:
xd009642 2022-01-21 21:29:38 +00:00 committed by GitHub
parent 71fc562f32
commit 5d08634501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 167 additions and 1 deletions

View File

@ -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"]}

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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"]

View File

@ -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: 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>
@ -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> {
@ -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]

View File

@ -66,6 +66,7 @@ async-await = []
std = ["tracing-core/std"]
log-always = ["log"]
attributes = ["tracing-attributes"]
valuable = ["tracing-core/valuable"]
[[bench]]
name = "subscriber"