-
Notifications
You must be signed in to change notification settings - Fork 749
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: Fix EnvFilter
to match targets
#368
Conversation
When a target and span are provided to filter upon, `EnvFilter` would let events that existed outside the target to be not filtered. This change makes it so that when you provide a target and span, the only events that are not filtered out are those that exist within both the target and the span. Signed-off-by: Lucio Franco <[email protected]>
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'd like to propose an alternative implementation that might be more efficient. Rather than checking targets in enabled
, what do you think of changing the scope
to be a Vec<(Option<String>, LevelFilter)>
, and for span directives with a target, pushing both the level and the target to scope
, rather than just the level. Then, when we iterate over scope
, only test against each level in scope
if the target for that level matches the filtered metadata's target, or if the target is None
.
If we're going to do this, we would probably want to start representing targets as Arc<String>
rather than as String
s, so we don't have to copy the whole string when pushing it to the stack.
@@ -227,7 +227,9 @@ impl<S: Subscriber> Layer<S> for EnvFilter { | |||
.with(|scope| { | |||
for filter in scope.iter() { | |||
if filter >= level { | |||
return true; | |||
if self.dynamics.match_target(metadata) { |
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 don't think this is implementing the correct behavior. It looks like this will return true
only if all the dynamic directives in the filter match the metadata's target?
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 think this is the right approach! I noticed some places where the behaviour is inconsistent with the rest of the filter code, these should probably be changed.
I also commented on some potential style and refactoring changes, but these are not blockers.
@@ -692,6 +710,13 @@ impl SpanMatcher { | |||
.unwrap_or_else(|| self.base_level.clone()) | |||
} | |||
|
|||
pub(crate) fn target(&self) -> Option<Arc<str>> { | |||
self.field_matches |
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.
Why not just put the target
field on SpanMatcher
?
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 not sure what is right here. I went with the first target, which for me will work.
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.
Sorry if that was unclear — what I was saying is, it doesn't really seem necessary for every SpanMatch
to have a target field, when we can just clone the CallsiteMatch
's target when constructing a new SpanMatcher
, here:
tracing/tracing-subscriber/src/filter/env/directive.rs
Lines 673 to 687 in 019d61e
pub(crate) fn to_span_match(&self, attrs: &span::Attributes<'_>) -> SpanMatcher { | |
let field_matches = self | |
.field_matches | |
.iter() | |
.map(|m| { | |
let m = m.to_span_match(); | |
attrs.record(&mut m.visitor()); | |
m | |
}) | |
.collect(); | |
SpanMatcher { | |
field_matches, | |
base_level: self.base_level.clone(), | |
} | |
} |
We could change that function to:
pub(crate) fn to_span_match(&self, attrs: &span::Attributes<'_>) -> SpanMatcher {
let field_matches = ...;
SpanMatcher {
field_matches,
base_level: self.base_level.clone(),
target: self.target.clone();
}
}
and remove the target
field from SpanMatch
.
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 looks like the loop over scope
in enabled
will still return early if a filter doesn't match, which is incorrect. This needs to be changed.
Beyond that, my other comments are style nits that are not blockers.
Also, I think you need to run rustfmt
to get CI to pass?
@@ -692,6 +710,13 @@ impl SpanMatcher { | |||
.unwrap_or_else(|| self.base_level.clone()) | |||
} | |||
|
|||
pub(crate) fn target(&self) -> Option<Arc<str>> { | |||
self.field_matches |
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.
Sorry if that was unclear — what I was saying is, it doesn't really seem necessary for every SpanMatch
to have a target field, when we can just clone the CallsiteMatch
's target when constructing a new SpanMatcher
, here:
tracing/tracing-subscriber/src/filter/env/directive.rs
Lines 673 to 687 in 019d61e
pub(crate) fn to_span_match(&self, attrs: &span::Attributes<'_>) -> SpanMatcher { | |
let field_matches = self | |
.field_matches | |
.iter() | |
.map(|m| { | |
let m = m.to_span_match(); | |
attrs.record(&mut m.visitor()); | |
m | |
}) | |
.collect(); | |
SpanMatcher { | |
field_matches, | |
base_level: self.base_level.clone(), | |
} | |
} |
We could change that function to:
pub(crate) fn to_span_match(&self, attrs: &span::Attributes<'_>) -> SpanMatcher {
let field_matches = ...;
SpanMatcher {
field_matches,
base_level: self.base_level.clone(),
target: self.target.clone();
}
}
and remove the target
field from SpanMatch
.
Also, it would be great to have documentation for the new behavior. But, since the old behavior was not really well-documented, I'm fine with merging this and adding docs in a follow-up... |
if filter >= level { | ||
return true; | ||
if &filter.level >= level { | ||
if filter.target.matches(&target) { |
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 occurs to me that if we think the nested if
is ugly or unclear, this could be simplified to
if &filter.level >= level && filter.target.matches(&target) {
return true;
}
Happy to merge it regardless, though.
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.
Let me do this.
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 good to me now, thanks!
Don't forget to add that this will close #367! |
Signed-off-by: Lucio Franco <[email protected]>
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 think that if we're moving forwards with this behavior (29fedee) we really need to document that, because it's different from the rules described in these docs...
Co-Authored-By: Eliza Weisman <[email protected]>
Signed-off-by: Lucio Franco <[email protected]>
…nvfilter-target-matching Signed-off-by: Lucio Franco <[email protected]>
@hawkw I think I've fixed up the docs per your comments. |
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 still think the rules around how targets are matched when multiple span directives match (choosing the first target, rather than selecting the target of the specific directive that was matched) needs to be documented, as I mentioned in #368 (review). That behaviour has the potential to be very surprising, and the documentation doesn't mention it at all.
If we're not able to document this behavior, I think we need to spend some more time implementing the correct behavior instead (probably requiring some more internal refactoring).
/// from the value. Examples, `1`, `\"some_string\"`, etc. | ||
/// - `level` sets a maximum verbosity level accepted by this directive | ||
/// | ||
/// The portion of the synatx that is included within the square brackets is `tracing` specific. |
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.
Can we put this at the top where we discuss the relationship with env_logger
?
/// Example, `[span{field=\"value\"}]=debug`, `[{field}]=trace`, etc. | ||
/// - `value` matches the _output_ of the span's value. If a value is a numeric literal or a bool, | ||
// it will match that value only. Otherwise, it's a regex that matches the `std::fmt::Debug` output | ||
/// from the value. Examples, `1`, `\"some_string\"`, etc. |
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.
/// from the value. Examples, `1`, `\"some_string\"`, etc. | |
/// from the value. For example: `1`, `\"some_string\"`, etc. |
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.
can we also add an example with a regex here?
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.
Could you provide an example of a good regex for this?
Co-Authored-By: Eliza Weisman <[email protected]>
Co-Authored-By: Eliza Weisman <[email protected]>
Co-Authored-By: Eliza Weisman <[email protected]>
Co-Authored-By: Eliza Weisman <[email protected]>
Co-Authored-By: Eliza Weisman <[email protected]>
Co-Authored-By: Eliza Weisman <[email protected]>
Co-Authored-By: Eliza Weisman <[email protected]>
Co-Authored-By: Eliza Weisman <[email protected]>
Docs look great thanks @davidbarsky <3 |
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.
@davidbarsky I think the docs changes you've made look great! However, I don't think the comments I made in #368 (review) have been addressed:
I still think the rules around how targets are matched when multiple span directives match (choosing the first target, rather than selecting the target of the specific directive that was matched) needs to be documented, as I mentioned in #368 (review). That behaviour has the potential to be very surprising, and the documentation doesn't mention it at all.
If we're not able to document this behavior, I think we need to spend some more time implementing the correct behavior instead (probably requiring some more internal refactoring).
Again, it's specifically the new behavior introduced in this PR that I think needs to be documented before we merge this change.
Thanks for working on this!
This PR is extracted from #368 and introduces additional documentation for the filter directives in tracing-subscriber. Signed-off-by: David Barsky <[email protected]> Co-authored-by: Lucio Franco <[email protected]> Co-authored-by: Eliza Weisman <[email protected]>
When a target and span are provided to filter upon,
EnvFilter
wouldlet events that existed outside the target to be not filtered. This
change makes it so that when you provide a target and span, the only
events that are not filtered out are those that exist within both
the target and the span.
Signed-off-by: Lucio Franco [email protected]