fix(parser): trim whitespace in LogLevel::from_str to prevent misclassification (closes #21)
Before this fix, level strings with surrounding whitespace (e.g. " WARN ") were incorrectly parsed as Unknown instead of the matching variant. This primarily affected the JSON log path where serde preserves the raw field value including whitespace. Changes: - Add s.trim() before case-insensitive matching in FromStr impl - Store trimmed value in Unknown variant to avoid whitespace noise - Add unit tests for whitespace-padded known levels - Add unit tests for Unknown trimming semantics (empty, internal ws) - Add JSON regression test for level field with surrounding whitespace
This commit is contained in:
@@ -319,6 +319,13 @@ mod tests {
|
||||
assert_eq!(parse_line(warn_line).unwrap().level, Some(LogLevel::Warn));
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Regression: level field with surrounding whitespace should still be recognized.
|
||||
fn test_level_whitespace_in_json() {
|
||||
let line = r#"{"level":" WARN ","message":"test"}"#;
|
||||
assert_eq!(parse_line(line).unwrap().level, Some(LogLevel::Warn));
|
||||
}
|
||||
|
||||
#[test]
|
||||
// 测试:所有候选时间戳键名(timestamp, time, ts, @timestamp)都能被识别。
|
||||
fn test_timestamp_key_names() {
|
||||
|
||||
@@ -71,13 +71,17 @@ impl FromStr for LogLevel {
|
||||
// 接收一个字符串切片 &str,返回 Result<LogLevel, Infallible>。
|
||||
// 由于 Err 类型是 Infallible,实际上返回值总是 Ok(LogLevel)。
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// `s.to_uppercase()` — 将字符串转换为大写,实现不区分大小写的匹配。
|
||||
// `s.trim()` — 去除字符串前后的 Unicode 空白字符。
|
||||
// 例如 " WARN " → "WARN","\tINFO\n" → "INFO"。
|
||||
let trimmed = s.trim();
|
||||
|
||||
// `trimmed.to_uppercase()` — 将 trimmed 后的字符串转换为大写,实现不区分大小写的匹配。
|
||||
// 例如 "info"、"Info"、"INFO" 都会被转换为 "INFO"。
|
||||
// 返回一个新的 String(堆分配)。
|
||||
//
|
||||
// `.as_str()` — 将 String 转换回 &str(字符串切片引用)。
|
||||
// 因为 match 需要匹配 &str 而不是 String。
|
||||
match s.to_uppercase().as_str() {
|
||||
match trimmed.to_uppercase().as_str() {
|
||||
// `|` 在 match 分支中表示"或"(multiple patterns)。
|
||||
// "ERROR" | "ERR" | "SEVERE" | "FATAL" 都匹配到 LogLevel::Error。
|
||||
"ERROR" | "ERR" | "SEVERE" | "FATAL" => Ok(LogLevel::Error),
|
||||
@@ -86,9 +90,9 @@ impl FromStr for LogLevel {
|
||||
"DEBUG" | "DBG" => Ok(LogLevel::Debug),
|
||||
"TRACE" | "TRC" => Ok(LogLevel::Trace),
|
||||
// `_` 是通配符,匹配所有未被上面分支捕获的值。
|
||||
// 对于未知级别,包装为 Unknown 并保存原始字符串。
|
||||
// s.to_string() 将 &str 转换为 String(注意这里用原始的 s,不是大写后的)。
|
||||
_ => Ok(LogLevel::Unknown(s.to_string())),
|
||||
// 对于未知级别,包装为 Unknown 并保存 trimmed 后的字符串。
|
||||
// s.to_string() 将 &str 转换为 String(注意这里用 trimmed,不是原始 s)。
|
||||
_ => Ok(LogLevel::Unknown(trimmed.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,6 +292,34 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str_whitespace_trimmed() {
|
||||
assert_eq!(" WARN ".parse::<LogLevel>(), Ok(LogLevel::Warn));
|
||||
assert_eq!("\tINFO".parse::<LogLevel>(), Ok(LogLevel::Info));
|
||||
assert_eq!("ERROR\n".parse::<LogLevel>(), Ok(LogLevel::Error));
|
||||
assert_eq!(" debug ".parse::<LogLevel>(), Ok(LogLevel::Debug));
|
||||
assert_eq!("\tTRACE\t".parse::<LogLevel>(), Ok(LogLevel::Trace));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str_whitespace_unknown_trimmed() {
|
||||
// Unknown stores the trimmed value, not the original.
|
||||
assert_eq!(
|
||||
" CUSTOM ".parse::<LogLevel>(),
|
||||
Ok(LogLevel::Unknown("CUSTOM".into()))
|
||||
);
|
||||
// Pure whitespace becomes Unknown("").
|
||||
assert_eq!(
|
||||
" ".parse::<LogLevel>(),
|
||||
Ok(LogLevel::Unknown("".into()))
|
||||
);
|
||||
// Internal whitespace is NOT collapsed.
|
||||
assert_eq!(
|
||||
"W ARN".parse::<LogLevel>(),
|
||||
Ok(LogLevel::Unknown("W ARN".into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// 测试:LogLevel 的 Display 输出格式是否正确。
|
||||
fn test_display_output() {
|
||||
|
||||
Reference in New Issue
Block a user