Files
Unlucky-Launcher/crates/core/src/launch/log_level.rs
T
AskaEth bc41dfcf7d
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Unlucky Launcher v0.0.1-alpha
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
2026-10-31 21:54:10 +08:00

115 lines
3.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 日志级别猜测与“关键行”判定。
//!
//! 平移自 `analyse/02` §6.4(`Log4jLevel.guessLevel`):先认 log4j 注入的
//! `[HH:mm:ss] [thread/LEVEL]` 格式,再退到 JUL 的 `[INFO]` 形态;`stderr` 一律当
//! ERROR(`[authlib-injector]` 例外)。
//!
//! 目的有两个:给事件流打 `level`,以及让环形缓冲能“关键行永久保留”
//! (`analyse/02` §7.10-3:2000 行截断会把根因行挤掉)。
/// 日志级别(认知顺序:Error 最严重)。
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
/// 错误。
Error,
/// 警告。
Warn,
/// 信息。
Info,
/// 调试。
Debug,
/// 无法判断。
Unknown,
}
impl Level {
/// 事件流里用的字符串形式。
pub fn as_str(self) -> &'static str {
match self {
Self::Error => "error",
Self::Warn => "warn",
Self::Info => "info",
Self::Debug => "debug",
Self::Unknown => "unknown",
}
}
/// 是否错误级(退出分析筛行用)。
pub fn is_error(self) -> bool {
self == Self::Error
}
}
/// 猜测一行日志的级别。
pub fn guess(is_stderr: bool, line: &str) -> Level {
let trimmed = line.trim_start();
if is_stderr {
// authlib-injector 往 stderr 打的都是正常信息(`analyse/02` §6.4)。
if trimmed.starts_with("[authlib-injector]") {
return Level::Info;
}
return Level::Error;
}
if let Some(level) = log4j_level(trimmed) {
return level;
}
jul_level(trimmed).unwrap_or(Level::Unknown)
}
/// 是否“关键行”:命中这些特征的行不随环形淘汰(`Exception|Error|Caused by|OutOfMemory`)。
pub fn is_key_line(line: &str) -> bool {
let lower = line.to_ascii_lowercase();
lower.contains("exception")
|| lower.contains("error")
|| lower.contains("caused by")
|| lower.contains("outofmemory")
|| lower.contains("fatal")
}
/// 从 `[12:34:56] [Render thread/INFO]: ...` 里取级别。
///
/// 逐段扫描方括号内容,取第一段形如 `<线程名>/<LEVEL>` 的级别,最多看 3 段。
fn log4j_level(line: &str) -> Option<Level> {
let mut rest = line;
for _ in 0..3 {
let open = rest.find('[')?;
let tail = &rest[open + 1..];
let close = tail.find(']')?;
let segment = &tail[..close];
if let Some(slash) = segment.rfind('/') {
if let Some(level) = level_from_name(&segment[slash + 1..]) {
return Some(level);
}
}
rest = &tail[close + 1..];
}
None
}
fn level_from_name(name: &str) -> Option<Level> {
match name.trim() {
"ERROR" | "FATAL" => Some(Level::Error),
"WARN" | "WARNING" => Some(Level::Warn),
"INFO" | "NOTICE" => Some(Level::Info),
"DEBUG" | "TRACE" | "FINE" | "FINER" | "FINEST" => Some(Level::Debug),
_ => None,
}
}
/// JUL/裸标记形态:`[INFO]`、`[SEVERE]`、`[WARNING]`。
fn jul_level(line: &str) -> Option<Level> {
const MARKERS: &[(&str, Level)] = &[
("[SEVERE]", Level::Error),
("[ERROR]", Level::Error),
("[WARNING]", Level::Warn),
("[WARN]", Level::Warn),
("[INFO]", Level::Info),
("[DEBUG]", Level::Debug),
("[FINE]", Level::Debug),
];
MARKERS
.iter()
.find(|(marker, _)| line.starts_with(marker))
.map(|(_, level)| *level)
}