From a8d8e1674b3f9dd13c13c32f58363efcbb43aa1c Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Sun, 11 Oct 2026 14:53:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=89=A9=E5=B1=95=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=EF=BC=8C=E6=94=AF=E6=8C=81=20JVM=20=E5=B4=A9?= =?UTF-8?q?=E6=BA=83=E4=B8=8E=E5=90=AF=E5=8A=A8=E5=99=A8=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LogType 新增 JvmCrash 与 LauncherLog,使崩溃弹窗能列出 hs_err_pid*.log、launcher_log.txt、latest_stdout.log 等日志。 - 新增 log_directories_for_type 与 is_log_file_for_type,统一 各日志类型的目录解析与文件名筛选 - 新增 isolated_minecraft_root,兼容 versions 版本隔离布局 - get_logs 追加 JvmCrash 与 LauncherLog 两类采集 - get_logs_by_filename / get_output_by_filename / delete_logs_by_filename 改用目录列表按存在性查找 - get_logs_from_type 遍历多目录并按文件名去重 --- packages/app-lib/src/api/logs.rs | 131 +++++++++++++++++++++++-------- 1 file changed, 100 insertions(+), 31 deletions(-) diff --git a/packages/app-lib/src/api/logs.rs b/packages/app-lib/src/api/logs.rs index 4ea104b..d55fc46 100644 --- a/packages/app-lib/src/api/logs.rs +++ b/packages/app-lib/src/api/logs.rs @@ -1,6 +1,6 @@ use std::fmt::Write as _; use std::io::{BufRead, Read, SeekFrom}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::SystemTime; use futures::TryFutureExt; @@ -39,6 +39,54 @@ pub struct Logs { pub enum LogType { InfoLog, CrashReport, + JvmCrash, + LauncherLog, +} + +fn isolated_minecraft_root(instance_root: &Path) -> Option { + let versions_dir = instance_root.parent()?; + if versions_dir + .file_name() + .is_some_and(|name| name.eq_ignore_ascii_case("versions")) + { + versions_dir.parent().map(Path::to_path_buf) + } else { + None + } +} + +fn log_directories_for_type( + state: &State, + game_dir: &Path, + log_type: LogType, +) -> Vec { + match log_type { + LogType::InfoLog => vec![state.directories.game_logs_dir(game_dir)], + LogType::CrashReport => { + vec![state.directories.game_crash_reports_dir(game_dir)] + } + LogType::JvmCrash | LogType::LauncherLog => { + let mut directories = vec![game_dir.to_path_buf()]; + if let Some(root) = isolated_minecraft_root(game_dir) { + directories.push(root); + } + directories + } + } +} + +fn is_log_file_for_type(log_type: LogType, file_name: &str) -> bool { + match log_type { + LogType::JvmCrash => { + let lower = file_name.to_ascii_lowercase(); + lower.starts_with("hs_err") && lower.ends_with(".log") + } + LogType::LauncherLog => matches!( + file_name.to_ascii_lowercase().as_str(), + "launcher_log.txt" | "latest_stdout.log" + ), + _ => true, + } } const LOG_COMPACTION_THRESHOLD: usize = 20; @@ -448,14 +496,11 @@ pub async fn get_logs_from_type( .directories .resolve_game_dir(&instance_path, game_dir_override.as_deref()); - let logs_folder = match log_type { - LogType::InfoLog => state.directories.game_logs_dir(&game_dir), - LogType::CrashReport => { - state.directories.game_crash_reports_dir(&game_dir) + let mut seen_files = std::collections::HashSet::new(); + for logs_folder in log_directories_for_type(&state, &game_dir, log_type) { + if !logs_folder.exists() { + continue; } - }; - - if logs_folder.exists() { for entry in std::fs::read_dir(&logs_folder) .map_err(|e| IOError::with_path(e, &logs_folder))? { @@ -471,6 +516,12 @@ pub async fn get_logs_from_type( } if let Some(file_name) = path.file_name() { let file_name = file_name.to_string_lossy().to_string(); + if !is_log_file_for_type(log_type, &file_name) { + continue; + } + if !seen_files.insert(file_name.clone()) { + continue; + } logs.push( Logs::build( log_type, @@ -507,6 +558,20 @@ pub async fn get_logs( &mut logs, ) .await?; + get_logs_from_type( + instance_id, + LogType::JvmCrash, + clear_contents, + &mut logs, + ) + .await?; + get_logs_from_type( + instance_id, + LogType::LauncherLog, + clear_contents, + &mut logs, + ) + .await?; let mut logs = logs.into_iter().collect::>>()?; logs.sort_by(|a, b| b.age.cmp(&a.age).then(b.filename.cmp(&a.filename))); @@ -526,13 +591,15 @@ pub async fn get_logs_by_filename( .directories .resolve_game_dir(&instance_path, game_dir_override.as_deref()); - let path = match log_type { - LogType::InfoLog => state.directories.game_logs_dir(&game_dir), - LogType::CrashReport => { - state.directories.game_crash_reports_dir(&game_dir) - } - } - .join(&filename); + let path = log_directories_for_type(&state, &game_dir, log_type) + .into_iter() + .map(|directory| directory.join(&filename)) + .find(|path| path.is_file()) + .ok_or_else(|| { + crate::ErrorKind::OtherError(format!( + "Log file not found: {filename}" + )) + })?; let metadata = std::fs::metadata(&path)?; let age = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH); @@ -546,14 +613,15 @@ async fn get_output_by_filename_from_path( log_type: LogType, file_name: &str, ) -> crate::Result { - let logs_folder = match log_type { - LogType::InfoLog => state.directories.game_logs_dir(game_dir), - LogType::CrashReport => { - state.directories.game_crash_reports_dir(game_dir) - } - }; - - let path = logs_folder.join(file_name); + let path = log_directories_for_type(state, game_dir, log_type) + .into_iter() + .map(|directory| directory.join(file_name)) + .find(|path| path.is_file()) + .ok_or_else(|| { + crate::ErrorKind::OtherError(format!( + "Log file not found: {file_name}" + )) + })?; let credentials = Credentials::get_all(&state.pool) .await? @@ -650,14 +718,15 @@ pub async fn delete_logs_by_filename( .directories .resolve_game_dir(&instance_path, game_dir_override.as_deref()); - let logs_folder = match log_type { - LogType::InfoLog => state.directories.game_logs_dir(&game_dir), - LogType::CrashReport => { - state.directories.game_crash_reports_dir(&game_dir) - } - }; - - let path = logs_folder.join(filename); + let path = log_directories_for_type(&state, &game_dir, log_type) + .into_iter() + .map(|directory| directory.join(filename)) + .find(|path| path.is_file()) + .ok_or_else(|| { + crate::ErrorKind::OtherError(format!( + "Log file not found: {filename}" + )) + })?; io::remove_file(&path).await?; Ok(()) }