Skip to content

Commit 95cdfc5

Browse files
committed
subscriber: add Targets filter, a lighter-weight EnvFilter (#1550)
This branch adds a new `Targets` filter to `tracing_subscriber`. The `Targets` filter is very similar to `EnvFilter`, but it _only_ consists of filtering directives consisting of a target and level. Because it doesn't support filtering on field names, span contexts, or field values, the implementation is *much* simpler, and it doesn't require the `env_filter` feature flag. Also, `Targets` can easily implement the `Filter` trait for per-layer filtering, while adding a `Filter` implementation for `EnvFilter` will require additional effort. Because the `Targets` filter doesn't allow specifiyng span or field-value filters, the syntax for parsing one from a string is significantly simpler than `EnvFilter`'s. Therefore, it can have a very simple handwritten parser implementation that doesn't require the `regex` crate. This should be useful for users who are concerned about the number of dependencies required by `EnvFilter`. The new implementation is quite small, as it mostly uses the same code as the static filter subset of `EnvFilter`. This code was factored out into a shared module for use in both `EnvFilter` and `Targets`. The code required for _dynamic_ filtering with `EnvFilter` (i.e. on fields and spans) is still in the `filter::env` module and is only enabled by the `env-filter` feature flag. I'm open to renaming the new type; I thought `filter::Targets` seemed good, but would also be willing to go with `TargetFilter` or something. Signed-off-by: Eliza Weisman <eliza@buoyant.io>
1 parent 6433c64 commit 95cdfc5

File tree

9 files changed

+899
-307
lines changed

9 files changed

+899
-307
lines changed
Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
use crate::filter::level::{self, LevelFilter};
2+
use std::{cmp::Ordering, error::Error, fmt, iter::FromIterator, str::FromStr};
3+
use tracing_core::Metadata;
4+
/// Indicates that a string could not be parsed as a filtering directive.
5+
#[derive(Debug)]
6+
pub struct DirectiveParseError {
7+
kind: ParseErrorKind,
8+
}
9+
10+
/// A directive which will statically enable or disable a given callsite.
11+
///
12+
/// Unlike a dynamic directive, this can be cached by the callsite.
13+
#[derive(Debug, PartialEq, Eq, Clone)]
14+
pub(crate) struct StaticDirective {
15+
pub(in crate::filter) target: Option<String>,
16+
pub(in crate::filter) field_names: FilterVec<String>,
17+
pub(in crate::filter) level: LevelFilter,
18+
}
19+
20+
#[cfg(feature = "smallvec")]
21+
pub(in crate::filter) type FilterVec<T> = smallvec::SmallVec<[T; 8]>;
22+
#[cfg(not(feature = "smallvec"))]
23+
pub(in crate::filter) type FilterVec<T> = Vec<T>;
24+
25+
#[derive(Debug, PartialEq, Clone)]
26+
pub(in crate::filter) struct DirectiveSet<T> {
27+
directives: FilterVec<T>,
28+
pub(in crate::filter) max_level: LevelFilter,
29+
}
30+
31+
pub(in crate::filter) trait Match {
32+
fn cares_about(&self, meta: &Metadata<'_>) -> bool;
33+
fn level(&self) -> &LevelFilter;
34+
}
35+
36+
#[derive(Debug)]
37+
enum ParseErrorKind {
38+
Field(Box<dyn Error + Send + Sync>),
39+
Level(level::ParseError),
40+
Other(Option<&'static str>),
41+
}
42+
43+
// === impl DirectiveSet ===
44+
45+
impl<T> DirectiveSet<T> {
46+
pub(crate) fn is_empty(&self) -> bool {
47+
self.directives.is_empty()
48+
}
49+
50+
pub(crate) fn iter(&self) -> std::slice::Iter<'_, T> {
51+
self.directives.iter()
52+
}
53+
}
54+
55+
impl<T: Ord> Default for DirectiveSet<T> {
56+
fn default() -> Self {
57+
Self {
58+
directives: FilterVec::new(),
59+
max_level: LevelFilter::OFF,
60+
}
61+
}
62+
}
63+
64+
impl<T: Match + Ord> DirectiveSet<T> {
65+
pub(crate) fn directives(&self) -> impl Iterator<Item = &T> {
66+
self.directives.iter()
67+
}
68+
69+
pub(crate) fn directives_for<'a>(
70+
&'a self,
71+
metadata: &'a Metadata<'a>,
72+
) -> impl Iterator<Item = &'a T> + 'a {
73+
self.directives().filter(move |d| d.cares_about(metadata))
74+
}
75+
76+
pub(crate) fn add(&mut self, directive: T) {
77+
// does this directive enable a more verbose level than the current
78+
// max? if so, update the max level.
79+
let level = *directive.level();
80+
if level > self.max_level {
81+
self.max_level = level;
82+
}
83+
// insert the directive into the vec of directives, ordered by
84+
// specificity (length of target + number of field filters). this
85+
// ensures that, when finding a directive to match a span or event, we
86+
// search the directive set in most specific first order.
87+
match self.directives.binary_search(&directive) {
88+
Ok(i) => self.directives[i] = directive,
89+
Err(i) => self.directives.insert(i, directive),
90+
}
91+
}
92+
93+
#[cfg(test)]
94+
pub(in crate::filter) fn into_vec(self) -> FilterVec<T> {
95+
self.directives
96+
}
97+
}
98+
99+
impl<T: Match + Ord> FromIterator<T> for DirectiveSet<T> {
100+
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
101+
let mut this = Self::default();
102+
this.extend(iter);
103+
this
104+
}
105+
}
106+
107+
impl<T: Match + Ord> Extend<T> for DirectiveSet<T> {
108+
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
109+
for directive in iter.into_iter() {
110+
self.add(directive);
111+
}
112+
}
113+
}
114+
115+
// === impl Statics ===
116+
117+
impl DirectiveSet<StaticDirective> {
118+
pub(crate) fn enabled(&self, meta: &Metadata<'_>) -> bool {
119+
let level = meta.level();
120+
match self.directives_for(meta).next() {
121+
Some(d) => d.level >= *level,
122+
None => false,
123+
}
124+
}
125+
}
126+
127+
// === impl StaticDirective ===
128+
129+
impl StaticDirective {
130+
pub(in crate::filter) fn new(
131+
target: Option<String>,
132+
field_names: FilterVec<String>,
133+
level: LevelFilter,
134+
) -> Self {
135+
Self {
136+
target,
137+
field_names,
138+
level,
139+
}
140+
}
141+
}
142+
143+
impl Ord for StaticDirective {
144+
fn cmp(&self, other: &StaticDirective) -> Ordering {
145+
// We attempt to order directives by how "specific" they are. This
146+
// ensures that we try the most specific directives first when
147+
// attempting to match a piece of metadata.
148+
149+
// First, we compare based on whether a target is specified, and the
150+
// lengths of those targets if both have targets.
151+
let ordering = self
152+
.target
153+
.as_ref()
154+
.map(String::len)
155+
.cmp(&other.target.as_ref().map(String::len))
156+
// Then we compare how many field names are matched by each directive.
157+
.then_with(|| self.field_names.len().cmp(&other.field_names.len()))
158+
// Finally, we fall back to lexicographical ordering if the directives are
159+
// equally specific. Although this is no longer semantically important,
160+
// we need to define a total ordering to determine the directive's place
161+
// in the BTreeMap.
162+
.then_with(|| {
163+
self.target
164+
.cmp(&other.target)
165+
.then_with(|| self.field_names[..].cmp(&other.field_names[..]))
166+
})
167+
.reverse();
168+
169+
#[cfg(debug_assertions)]
170+
{
171+
if ordering == Ordering::Equal {
172+
debug_assert_eq!(
173+
self.target, other.target,
174+
"invariant violated: Ordering::Equal must imply a.target == b.target"
175+
);
176+
debug_assert_eq!(
177+
self.field_names, other.field_names,
178+
"invariant violated: Ordering::Equal must imply a.field_names == b.field_names"
179+
);
180+
}
181+
}
182+
183+
ordering
184+
}
185+
}
186+
187+
impl PartialOrd for StaticDirective {
188+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
189+
Some(self.cmp(other))
190+
}
191+
}
192+
193+
impl Match for StaticDirective {
194+
fn cares_about(&self, meta: &Metadata<'_>) -> bool {
195+
// Does this directive have a target filter, and does it match the
196+
// metadata's target?
197+
if let Some(ref target) = self.target {
198+
if !meta.target().starts_with(&target[..]) {
199+
return false;
200+
}
201+
}
202+
203+
if meta.is_event() && !self.field_names.is_empty() {
204+
let fields = meta.fields();
205+
for name in &self.field_names {
206+
if fields.field(name).is_none() {
207+
return false;
208+
}
209+
}
210+
}
211+
212+
true
213+
}
214+
215+
fn level(&self) -> &LevelFilter {
216+
&self.level
217+
}
218+
}
219+
220+
impl Default for StaticDirective {
221+
fn default() -> Self {
222+
StaticDirective {
223+
target: None,
224+
field_names: FilterVec::new(),
225+
level: LevelFilter::ERROR,
226+
}
227+
}
228+
}
229+
230+
impl fmt::Display for StaticDirective {
231+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232+
let mut wrote_any = false;
233+
if let Some(ref target) = self.target {
234+
fmt::Display::fmt(target, f)?;
235+
wrote_any = true;
236+
}
237+
238+
if !self.field_names.is_empty() {
239+
f.write_str("[")?;
240+
241+
let mut fields = self.field_names.iter();
242+
if let Some(field) = fields.next() {
243+
write!(f, "{{{}", field)?;
244+
for field in fields {
245+
write!(f, ",{}", field)?;
246+
}
247+
f.write_str("}")?;
248+
}
249+
250+
f.write_str("]")?;
251+
wrote_any = true;
252+
}
253+
254+
if wrote_any {
255+
f.write_str("=")?;
256+
}
257+
258+
fmt::Display::fmt(&self.level, f)
259+
}
260+
}
261+
262+
impl FromStr for StaticDirective {
263+
type Err = DirectiveParseError;
264+
265+
fn from_str(s: &str) -> Result<Self, Self::Err> {
266+
// This method parses a filtering directive in one of the following
267+
// forms:
268+
//
269+
// * `foo=trace` (TARGET=LEVEL)
270+
// * `foo[{bar,baz}]=info` (TARGET[{FIELD,+}]=LEVEL)
271+
// * `trace` (bare LEVEL)
272+
// * `foo` (bare TARGET)
273+
let mut split = s.split('=');
274+
let part0 = split
275+
.next()
276+
.ok_or_else(|| DirectiveParseError::msg("string must not be empty"))?;
277+
278+
// Directive includes an `=`:
279+
// * `foo=trace`
280+
// * `foo[{bar}]=trace`
281+
// * `foo[{bar,baz}]=trace`
282+
if let Some(part1) = split.next() {
283+
if split.next().is_some() {
284+
return Err(DirectiveParseError::msg(
285+
"too many '=' in filter directive, expected 0 or 1",
286+
));
287+
}
288+
289+
let mut split = part0.split("[{");
290+
let target = split.next().map(String::from);
291+
let mut field_names = FilterVec::new();
292+
// Directive includes fields:
293+
// * `foo[{bar}]=trace`
294+
// * `foo[{bar,baz}]=trace`
295+
if let Some(maybe_fields) = split.next() {
296+
if split.next().is_some() {
297+
return Err(DirectiveParseError::msg(
298+
"too many '[{' in filter directive, expected 0 or 1",
299+
));
300+
}
301+
302+
let fields = maybe_fields.strip_suffix("}]").ok_or_else(|| {
303+
DirectiveParseError::msg("expected fields list to end with '}]'")
304+
})?;
305+
field_names.extend(fields.split(',').filter_map(|s| {
306+
if s.is_empty() {
307+
None
308+
} else {
309+
Some(String::from(s))
310+
}
311+
}));
312+
};
313+
let level = part1.parse()?;
314+
return Ok(Self {
315+
level,
316+
field_names,
317+
target,
318+
});
319+
}
320+
321+
// Okay, the part after the `=` was empty, the directive is either a
322+
// bare level or a bare target.
323+
// * `foo`
324+
// * `info`
325+
Ok(match part0.parse::<LevelFilter>() {
326+
Ok(level) => Self {
327+
level,
328+
target: None,
329+
field_names: FilterVec::new(),
330+
},
331+
Err(_) => Self {
332+
target: Some(String::from(part0)),
333+
level: LevelFilter::TRACE,
334+
field_names: FilterVec::new(),
335+
},
336+
})
337+
}
338+
}
339+
340+
// === impl ParseError ===
341+
342+
impl DirectiveParseError {
343+
pub(crate) fn new() -> Self {
344+
DirectiveParseError {
345+
kind: ParseErrorKind::Other(None),
346+
}
347+
}
348+
349+
pub(crate) fn msg(s: &'static str) -> Self {
350+
DirectiveParseError {
351+
kind: ParseErrorKind::Other(Some(s)),
352+
}
353+
}
354+
}
355+
356+
impl fmt::Display for DirectiveParseError {
357+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358+
match self.kind {
359+
ParseErrorKind::Other(None) => f.pad("invalid filter directive"),
360+
ParseErrorKind::Other(Some(msg)) => write!(f, "invalid filter directive: {}", msg),
361+
ParseErrorKind::Level(ref l) => l.fmt(f),
362+
ParseErrorKind::Field(ref e) => write!(f, "invalid field filter: {}", e),
363+
}
364+
}
365+
}
366+
367+
impl Error for DirectiveParseError {
368+
fn description(&self) -> &str {
369+
"invalid filter directive"
370+
}
371+
372+
fn source(&self) -> Option<&(dyn Error + 'static)> {
373+
match self.kind {
374+
ParseErrorKind::Other(_) => None,
375+
ParseErrorKind::Level(ref l) => Some(l),
376+
ParseErrorKind::Field(ref n) => Some(n.as_ref()),
377+
}
378+
}
379+
}
380+
381+
impl From<Box<dyn Error + Send + Sync>> for DirectiveParseError {
382+
fn from(e: Box<dyn Error + Send + Sync>) -> Self {
383+
Self {
384+
kind: ParseErrorKind::Field(e),
385+
}
386+
}
387+
}
388+
389+
impl From<level::ParseError> for DirectiveParseError {
390+
fn from(l: level::ParseError) -> Self {
391+
Self {
392+
kind: ParseErrorKind::Level(l),
393+
}
394+
}
395+
}

0 commit comments

Comments
 (0)