diff --git a/crates/core/src/parser/json.rs b/crates/core/src/parser/json.rs index 0e189a5..d70077c 100644 --- a/crates/core/src/parser/json.rs +++ b/crates/core/src/parser/json.rs @@ -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() { diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index 68c9c91..45112dd 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -71,13 +71,17 @@ impl FromStr for LogLevel { // 接收一个字符串切片 &str,返回 Result。 // 由于 Err 类型是 Infallible,实际上返回值总是 Ok(LogLevel)。 fn from_str(s: &str) -> Result { - // `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::(), Ok(LogLevel::Warn)); + assert_eq!("\tINFO".parse::(), Ok(LogLevel::Info)); + assert_eq!("ERROR\n".parse::(), Ok(LogLevel::Error)); + assert_eq!(" debug ".parse::(), Ok(LogLevel::Debug)); + assert_eq!("\tTRACE\t".parse::(), Ok(LogLevel::Trace)); + } + + #[test] + fn test_from_str_whitespace_unknown_trimmed() { + // Unknown stores the trimmed value, not the original. + assert_eq!( + " CUSTOM ".parse::(), + Ok(LogLevel::Unknown("CUSTOM".into())) + ); + // Pure whitespace becomes Unknown(""). + assert_eq!( + " ".parse::(), + Ok(LogLevel::Unknown("".into())) + ); + // Internal whitespace is NOT collapsed. + assert_eq!( + "W ARN".parse::(), + Ok(LogLevel::Unknown("W ARN".into())) + ); + } + #[test] // 测试:LogLevel 的 Display 输出格式是否正确。 fn test_display_output() {