Skip to content

RFC: Value/ValueSet ergonomics improvements #697

Open
@hawkw

Description

@hawkw

Feature Request

Crates

  • tracing
  • tracing-core

Motivation

Currently, working with Values and ValueSets is somewhat complex. The only way Values 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 in tracing-core unless it's necessary for them to go there. Since core 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 Values more ergonomic to work with, please leave a comment!

Metadata

Metadata

Assignees

No one assigned

    Labels

    crate/coreRelated to the `tracing-core` cratecrate/tracingRelated to the `tracing` cratekind/rfcA request for comments to discuss future changes

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions