Description
Feature Request
Crates
tracing
tracing-core
Motivation
Currently, working with Value
s and ValueSet
s is somewhat complex. The only way Value
s may be consumed is by implementing the Visit
trait. Although Visit
is a very powerful interface — it allows consuming dynamically-typed values as typed primitives, and allows graceful fallback from one typed primitive to another — it requires a lot of boilerplate to use in the most common cases (e.g. "I just want to format everything with fmt::Debug
). Ideally, the complexity of the core value system would be layered in gradually rather than exposed all at once — it would be nice if a basic Subscriber
that only wants to record Debug
values didn't have to write code like
use tracing::field;
use std::fmt;
struct MyVisitor<'a> {
f: &'a mut fmt::Formatter<'a>,
result: fmt::Result,
}
impl field::Visit for MyVisitor<'_> {
fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) {
self.result = write!(self.f, "{}={:?}", field, value);
}
}
or similar, which is kind of a lot.
Similarly, we could have significantly better ergonomics for subscribers that only care about some fields. Right now, the way a subscriber would record only a subset of field names involves something like this:
use tracing::field;
struct MyVisitor<'a> {
target_field: &'a field::Field, // or &str or something
// ...
}
impl field::Visit for MyVisitor<'_> {
fn record_u64(&mut self, field: &field::Field, value: u64) {
if field == self.target_field {
// ... actually record the field ...
}
}
// ... record_${TYPE} methods as appropriate ...
fn record_debug(&mut self, field: &field::Field, value: &dyn std::fmt::Debug) {
if field == self.target_field {
// ... actually record the field ...
}
}
}
which is also pretty verbose, especially if multiple primitive types are recorded (since the logic for checking the field is duplicated in every record
fn).
It would be nice to improve the ergonomics of the Value
system for common use-cases.
Constraints
There are some constraints we need to consider before proposing changes to the value system:
- Forwards-compatibility: Currently, the
Value
trait is sealed, preventing downstream implementations. This is largely in order to allow additions to the value system in the future without running the risk of user value implementations — eventually, it would be good for the value system to support more fully-featured serialization/recording (e.g. recording structures composed of multiple values, or containers of values). I think now is probably a good time to start thinking about the future of the value system and how to add these features, but that should be discussed separately. However, we must try to avoid making changes here that limit our ability to extend the value system in the future. - Keeping
core
minimal: Whenever possible, we should try to avoid having higher level interfaces intracing-core
unless it's necessary for them to go there. Sincecore
has the strictest semver requirements, we want to keep it as small as possible — every piece of API surface is something that might, in the future, potentially require a breaking change.
I'll post some proposals of my own next, but I'm also interested in input from other folks — if anyone has suggestions for how we can make Value
s more ergonomic to work with, please leave a comment!