Skip to content

Commit bab5257

Browse files
committed
Use state machine to parse directives
1 parent c54aa4e commit bab5257

File tree

2 files changed

+110
-92
lines changed

2 files changed

+110
-92
lines changed

tracing-subscriber/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ rust-version = "1.63.0"
2727
default = ["smallvec", "fmt", "ansi", "tracing-log", "std"]
2828
alloc = ["tracing-core/alloc", "portable-atomic-util?/alloc"]
2929
std = ["alloc", "tracing-core/std"]
30-
env-filter = ["matchers", "regex", "once_cell", "tracing", "std", "thread_local"]
30+
env-filter = ["matchers", "once_cell", "tracing", "std", "thread_local"]
3131
fmt = ["registry", "std"]
3232
ansi = ["fmt", "nu-ansi-term"]
3333
registry = ["sharded-slab", "thread_local", "std"]
@@ -39,14 +39,15 @@ critical-section = ["tracing-core/critical-section", "tracing?/critical-section"
3939
# formatters.
4040
local-time = ["time/local-offset"]
4141
nu-ansi-term = ["dep:nu-ansi-term"]
42+
# For backwards compatibility only
43+
regex = []
4244

4345
[dependencies]
4446
tracing-core = { path = "../tracing-core", version = "0.2", default-features = false }
4547

4648
# only required by the `env-filter` feature
4749
tracing = { optional = true, path = "../tracing", version = "0.2", default-features = false }
4850
matchers = { optional = true, version = "0.1.0" }
49-
regex = { optional = true, version = "1.6.0", default-features = false, features = ["std", "unicode-case", "unicode-perl"] }
5051
smallvec = { optional = true, version = "1.9.0" }
5152
once_cell = { optional = true, version = "1.13.0" }
5253

tracing-subscriber/src/filter/env/directive.rs

Lines changed: 107 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ use crate::filter::{
44
env::{field, FieldMap},
55
level::LevelFilter,
66
};
7-
use once_cell::sync::Lazy;
8-
use regex::Regex;
97
use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr};
108
use tracing_core::{span, Level, Metadata};
119

@@ -120,99 +118,118 @@ impl Directive {
120118
}
121119

122120
pub(super) fn parse(from: &str, regex: bool) -> Result<Self, ParseError> {
123-
static DIRECTIVE_RE: Lazy<Regex> = Lazy::new(|| {
124-
Regex::new(
125-
r"(?x)
126-
^(?P<global_level>(?i:trace|debug|info|warn|error|off|[0-5]))$ |
127-
# ^^^.
128-
# `note: we match log level names case-insensitively
129-
^
130-
(?: # target name or span name
131-
(?P<target>[\w:-]+)|(?P<span>\[[^\]]*\])
132-
){1,2}
133-
(?: # level or nothing
134-
=(?P<level>(?i:trace|debug|info|warn|error|off|[0-5]))?
135-
# ^^^.
136-
# `note: we match log level names case-insensitively
137-
)?
138-
$
139-
",
140-
)
141-
.unwrap()
142-
});
143-
static SPAN_PART_RE: Lazy<Regex> =
144-
Lazy::new(|| Regex::new(r"(?P<name>[^\]\{]+)?(?:\{(?P<fields>[^\}]*)\})?").unwrap());
145-
static FIELD_FILTER_RE: Lazy<Regex> =
146-
// TODO(eliza): this doesn't _currently_ handle value matchers that include comma
147-
// characters. We should fix that.
148-
Lazy::new(|| {
149-
Regex::new(
150-
r"(?x)
151-
(
152-
# field name
153-
[[:word:]][[[:word:]]\.]*
154-
# value part (optional)
155-
(?:=[^,]+)?
156-
)
157-
# trailing comma or EOS
158-
(?:,\s?|$)
159-
",
160-
)
161-
.unwrap()
162-
});
163-
164-
let caps = DIRECTIVE_RE.captures(from).ok_or_else(ParseError::new)?;
121+
let mut cur = Self {
122+
level: LevelFilter::TRACE,
123+
target: None,
124+
in_span: None,
125+
fields: Vec::new(),
126+
};
127+
128+
#[derive(Debug)]
129+
enum ParseState {
130+
Start,
131+
LevelOrTarget { start: usize },
132+
Span { span_start: usize },
133+
Field { field_start: usize },
134+
Fields,
135+
Target,
136+
Level { level_start: usize },
137+
Complete,
138+
}
165139

166-
if let Some(level) = caps
167-
.name("global_level")
168-
.and_then(|s| s.as_str().parse().ok())
169-
{
170-
return Ok(Directive {
171-
level,
172-
..Default::default()
173-
});
140+
use ParseState::*;
141+
let mut state = Start;
142+
for (i, c) in from.char_indices() {
143+
state = match (state, c) {
144+
(Start, '[') => Span { span_start: i + 1 },
145+
(Start, _) => LevelOrTarget { start: i },
146+
(LevelOrTarget { start }, '=') => {
147+
cur.target = Some(from[start..i].to_owned());
148+
Level { level_start: i + 1 }
149+
}
150+
(LevelOrTarget { start }, '[') => {
151+
cur.target = Some(from[start..i].to_owned());
152+
Span { span_start: i + 1 }
153+
}
154+
(LevelOrTarget { start }, ',') => {
155+
let (level, target) = match &from[start..] {
156+
"" => (LevelFilter::TRACE, None),
157+
level_or_target => match LevelFilter::from_str(level_or_target) {
158+
Ok(level) => (level, None),
159+
Err(_) => (LevelFilter::TRACE, Some(level_or_target.to_owned())),
160+
},
161+
};
162+
163+
cur.level = level;
164+
cur.target = target;
165+
Complete
166+
}
167+
(state @ LevelOrTarget { .. }, _) => state,
168+
(Target, '=') => Level { level_start: i + 1 },
169+
(Span { span_start }, ']') => {
170+
cur.in_span = Some(from[span_start..i].to_owned());
171+
Target
172+
}
173+
(Span { span_start }, '{') => {
174+
cur.in_span = match &from[span_start..i] {
175+
"" => None,
176+
_ => Some(from[span_start..i].to_owned()),
177+
};
178+
Field { field_start: i + 1 }
179+
}
180+
(state @ Span { .. }, _) => state,
181+
(Field { field_start }, '}') => {
182+
cur.fields.push(match &from[field_start..i] {
183+
"" => return Err(ParseError::new()),
184+
field => field::Match::parse(field, regex)?,
185+
});
186+
Fields
187+
}
188+
(Field { field_start }, ',') => {
189+
cur.fields.push(match &from[field_start..i] {
190+
"" => return Err(ParseError::new()),
191+
field => field::Match::parse(field, regex)?,
192+
});
193+
Field { field_start: i + 1 }
194+
}
195+
(state @ Field { .. }, _) => state,
196+
(Fields, ']') => Target,
197+
(Level { level_start }, ',') => {
198+
cur.level = match &from[level_start..i] {
199+
"" => LevelFilter::TRACE,
200+
level => LevelFilter::from_str(level)?,
201+
};
202+
Complete
203+
}
204+
(state @ Level { .. }, _) => state,
205+
_ => return Err(ParseError::new()),
206+
};
174207
}
175208

176-
let target = caps.name("target").and_then(|c| {
177-
let s = c.as_str();
178-
if s.parse::<LevelFilter>().is_ok() {
179-
None
180-
} else {
181-
Some(s.to_owned())
209+
match state {
210+
LevelOrTarget { start } => {
211+
let (level, target) = match &from[start..] {
212+
"" => (LevelFilter::TRACE, None),
213+
level_or_target => match LevelFilter::from_str(level_or_target) {
214+
Ok(level) => (level, None),
215+
Err(_) => (LevelFilter::TRACE, Some(level_or_target.to_owned())),
216+
},
217+
};
218+
219+
cur.level = level;
220+
cur.target = target;
182221
}
183-
});
184-
185-
let (in_span, fields) = caps
186-
.name("span")
187-
.and_then(|cap| {
188-
let cap = cap.as_str().trim_matches(|c| c == '[' || c == ']');
189-
let caps = SPAN_PART_RE.captures(cap)?;
190-
let span = caps.name("name").map(|c| c.as_str().to_owned());
191-
let fields = caps
192-
.name("fields")
193-
.map(|c| {
194-
FIELD_FILTER_RE
195-
.find_iter(c.as_str())
196-
.map(|c| field::Match::parse(c.as_str(), regex))
197-
.collect::<Result<Vec<_>, _>>()
198-
})
199-
.unwrap_or_else(|| Ok(Vec::new()));
200-
Some((span, fields))
201-
})
202-
.unwrap_or_else(|| (None, Ok(Vec::new())));
203-
204-
let level = caps
205-
.name("level")
206-
.and_then(|l| l.as_str().parse().ok())
207-
// Setting the target without the level enables every level for that target
208-
.unwrap_or(LevelFilter::TRACE);
222+
Level { level_start } => {
223+
cur.level = match &from[level_start..] {
224+
"" => LevelFilter::TRACE,
225+
level => LevelFilter::from_str(level)?,
226+
};
227+
}
228+
Target | Complete => {}
229+
_ => return Err(ParseError::new()),
230+
};
209231

210-
Ok(Self {
211-
level,
212-
target,
213-
in_span,
214-
fields: fields?,
215-
})
232+
Ok(cur)
216233
}
217234
}
218235

0 commit comments

Comments
 (0)