Skip to content

Commit 06e5695

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

File tree

2 files changed

+112
-92
lines changed

2 files changed

+112
-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: 109 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,120 @@ 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.trim().char_indices() {
143+
state = match (state, c) {
144+
(Start, '[') => Span { span_start: i + 1 },
145+
(Start, c) if !c.is_alphanumeric() => return Err(ParseError::new()),
146+
(Start, _) => LevelOrTarget { start: i },
147+
(LevelOrTarget { start }, '=') => {
148+
cur.target = Some(from[start..i].to_owned());
149+
Level { level_start: i + 1 }
150+
}
151+
(LevelOrTarget { start }, '[') => {
152+
cur.target = Some(from[start..i].to_owned());
153+
Span { span_start: i + 1 }
154+
}
155+
(LevelOrTarget { start }, ',') => {
156+
let (level, target) = match &from[start..] {
157+
"" => (LevelFilter::TRACE, None),
158+
level_or_target => match LevelFilter::from_str(level_or_target) {
159+
Ok(level) => (level, None),
160+
Err(_) => (LevelFilter::TRACE, Some(level_or_target.to_owned())),
161+
},
162+
};
163+
164+
cur.level = level;
165+
cur.target = target;
166+
Complete
167+
}
168+
(state @ LevelOrTarget { .. }, _) => state,
169+
(Target, '=') => Level { level_start: i + 1 },
170+
(Span { span_start }, ']') => {
171+
cur.in_span = Some(from[span_start..i].to_owned());
172+
Target
173+
}
174+
(Span { span_start }, '{') => {
175+
cur.in_span = match &from[span_start..i] {
176+
"" => None,
177+
_ => Some(from[span_start..i].to_owned()),
178+
};
179+
Field { field_start: i + 1 }
180+
}
181+
(state @ Span { .. }, _) => state,
182+
(Field { field_start }, '}') => {
183+
cur.fields.push(match &from[field_start..i] {
184+
"" => return Err(ParseError::new()),
185+
field => field::Match::parse(field, regex)?,
186+
});
187+
Fields
188+
}
189+
(Field { field_start }, ',') => {
190+
cur.fields.push(match &from[field_start..i] {
191+
"" => return Err(ParseError::new()),
192+
field => field::Match::parse(field, regex)?,
193+
});
194+
Field { field_start: i + 1 }
195+
}
196+
(state @ Field { .. }, _) => state,
197+
(Fields, ']') => Target,
198+
(Level { level_start }, ',') => {
199+
cur.level = match &from[level_start..i] {
200+
"" => LevelFilter::TRACE,
201+
level => LevelFilter::from_str(level)?,
202+
};
203+
Complete
204+
}
205+
(state @ Level { .. }, _) => state,
206+
_ => return Err(ParseError::new()),
207+
};
174208
}
175209

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())
210+
match state {
211+
LevelOrTarget { start } => {
212+
let (level, target) = match &from[start..] {
213+
"" => (LevelFilter::TRACE, None),
214+
level_or_target => match LevelFilter::from_str(level_or_target) {
215+
Ok(level) => (level, None),
216+
// Setting the target without the level enables every level for that target
217+
Err(_) => (LevelFilter::TRACE, Some(level_or_target.to_owned())),
218+
},
219+
};
220+
221+
cur.level = level;
222+
cur.target = target;
182223
}
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);
224+
Level { level_start } => {
225+
cur.level = match &from[level_start..] {
226+
"" => LevelFilter::TRACE,
227+
level => LevelFilter::from_str(level)?,
228+
};
229+
}
230+
Target | Complete => {}
231+
_ => return Err(ParseError::new()),
232+
};
209233

210-
Ok(Self {
211-
level,
212-
target,
213-
in_span,
214-
fields: fields?,
215-
})
234+
Ok(cur)
216235
}
217236
}
218237

0 commit comments

Comments
 (0)