-
Notifications
You must be signed in to change notification settings - Fork 729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
subscriber: add an abstraction for building visitors #241
Conversation
} | ||
|
||
#[derive(Debug)] | ||
pub struct VisitDelimited<D, V> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if I am understanding this correctly, this would mean that you'd simply have to instantiate a VisitDelimited
for a new visitor, without having to wrap the Visitor construction in a separate type?
|
||
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { | ||
self.delimit(); | ||
self.inner.record_debug(field, value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are the errors from the delegation to inner
's being caught? Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The record_$TYPE
functions don't return errors, so if the inner visitor is also fallible, it's expected to capture any errors that might occur. The wrapper's finish
method will call finish
on the inner visitor, so if the inner visitor also tracks an error, it will be returned if the wrapper hasn't also failed.
7c7f741
to
81acb32
Compare
I've spent some time playing around with this, and I think it's about ready for review. Please let me know what you think! |
tracing-fmt/src/format.rs
Outdated
pub trait FormatFields<'writer> { | ||
fn format_fields<R: RecordFields>( | ||
&self, | ||
writer: &'writer mut dyn fmt::Write, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to be generic over the fields but not over the writer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the writer is the MakeVisitor
trait's target type, which the trait is generic over. This means that we would have to make the FormatFields
trait (rather than the function) generic over a writer, and thus also make the FmtSubscriber
itself generic over a writer. This won't work since we will need to pass multiple writer types in (sometimes a String
, and sometimes an io stream), so we need to use a trait object to erase the concrete type. On the other hand, the RecordFields
is only a parameter of the method, so implementations of FormatFields
can't implement the trait only for specific implementors of RecordFields
. I thought it was better to avoid the overhead of a trait object or that since it's not strictly required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could make the method generic over both a writer and a RecordFields
, but the writer will eventually become a trait object if we use it to create a visitor. That might, actually, be better, as implementations that don't use the blanket impl for MakeVisitor
could avoid a trait object...
In the near future, the `tracing-subscriber` crate should be ready for a stable 0.1 release. Before we can do that, we should do some pre-release polish, such as denying warnings, updating idioms, and adding missing documentation. This branch performs most of the necessary polish for `tracing-subscriber`. Please note the following: * The next release will not be version 0.1, as PR #241 would be a breaking change, and I'd like to land that first. We'll probably do an alpha.4 first. * I didn't add `deny(warnings)`, as that will also deny deprecation warnings, and I've had bad experiences with deprecations breaking CI in the past. Instead, I've denied most other warnings explicitly. The downside to this is a *very* long list of deny attributes. * The docs added in this branch are pretty rough. It would be good to expand them some more in the future, hopefully before 0.1. Signed-off-by: Eliza Weisman <[email protected]>
780898d
to
5000d31
Compare
fn delimit(&mut self) | ||
where | ||
V: VisitFmt, | ||
D: AsRef<str>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really a review comment, but more a Rust question, but why not lift these bounds to the impl block instead of specifying it on the method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! I left some general nits. Mostly around things that I felt weren't really clear.
} | ||
|
||
/// A visitor wrapper that inserts a delimiter after the wrapped visitor formats | ||
/// a field value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe expand on this comment saying something along the lines of "The delimiter is inserted after it is visited the first time"?
Right now you will have to separately see the impl of the delimit
method and where it's being called from to understand how it fits together.
where | ||
V: Visit, | ||
{ | ||
#[inline] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again more a rust question than anything, but won't llvm automatically inline such small methods when at opt-level=2 ?
} | ||
/// Extension trait implemented by visitors to indicate that they write to a | ||
/// `fmt::Write` instance, and allow access to that writer. | ||
pub trait VisitFmt: VisitOutput<fmt::Result> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was initially confused by the VisitFmt
name as the trait only has the writer
method on it. But goddamn do I love how this abstraction separates the two writers and still brings it all together. It's awesome!
/// .finish(); | ||
/// # drop(subscriber) | ||
/// ``` | ||
pub fn fmt_fields<N2>(self, fmt_fields: N2) -> Builder<N2, E, F, W> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know naming is a hard problem but maybe have a different variable for the generic? N
and N2
are a little confusing as the NewVisitor
type has been removed right?
} | ||
/// Extension trait implemented by visitors to indicate that they write to a | ||
/// `fmt::Write` instance, and allow access to that writer. | ||
pub trait VisitFmt: VisitOutput<fmt::Result> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something I'm a bit on the fence about that I'd love to get some opinions on: We could make the VisitFmt
traits trait aliases for VisitOutput<fmt::Result> + AsMut<dyn fmt::Write>
; that way, any type implementing AsMut
could also get a blanket implementation for free. On the other hand, it means introducing a trait alias.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that the trait alias for VisitOutput<fmt::Result> + AsMut<dyn fmt::Write>
could be added in non-backwards incompatible change, so I wouldn't gate on this.
This looks good to me; the build is failing on missing docs + missing debug impls: https://dev.azure.com/tracing/tracing/_build/results?buildId=721&view=logs&j=84c7964f-8fe4-5ae5-6e5b-8951524b2943&t=a6204e26-b351-5262-402a-ed02abe9e163&l=443. I think with that + resolved conflicts, this could merged? |
ed365c3
to
3d2ff32
Compare
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
a30fc0f
to
6bf2d24
Compare
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
@davidbarsky @LucioFranco this is just about ready to merge, if either of you wanted to take a final look first. |
@hawkw no issues with merging this as is. |
Motivation
The
tracing-fmt
crate crate currently uses aNewVisitor
trait toabstract over constructing field visitors of different types or with
different configurations:
tracing/tracing-fmt/src/lib.rs
Lines 192 to 196 in d51d4d6
There have been other use-cases where having this abstraction seems
valuable. For example, issue #73 is a proposal for adding utilities for
wrapping a visitor to skip a set of excluded field names. A
MakeVisitor
abstraction would allow us to provide much more ergonomic APIs for
composing this kind of visitor behaviour.
Additionally, the current trait in
tracing-fmt
is tied pretty closelyto `tracing-fmt- specific behaviour. A generalized version of this trait
should be generic over the input type used to build the visitor.
Solution
This PR adds a new
MakeVisitor
trait intracing-subscriber
.MakeVisitor
generalizes theNewVisitor
trait intracing-fmt
torepresent constructing visitors for arbitrary targets. In addition, I've
added some additional traits representing common patterns for visitors,
such as those that format to a
fmt::Write
instance or write to an IOstream, or those that produce output once a set of fields have been
visited. I've also added some extension traits & combinators to make
composing visitors easier, and to demonstrate that the trait in this
branch can be used for implementing this kind of wrapper.
I've also rewritten the
tracing-fmt
crate to use the newtracing-subscriber::MakeVisitor
trait rather than its homegrownfmt
-specifc version.Closes: #240
Signed-off-by: Eliza Weisman [email protected]