From ccb96a8cf1362dfb401e461bad1af7f7e533a2e1 Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Tue, 13 Oct 2026 22:01:02 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20LogShare=20=E9=87=87=E9=9B=86?= =?UTF-8?q?=E6=94=B9=E7=94=A8=E8=BD=BB=E9=87=8F=E6=97=A5=E5=BF=97=E6=94=B6?= =?UTF-8?q?=E9=9B=86=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=B4=A9=E6=BA=83=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E4=B8=8A=E4=BC=A0=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 崩溃弹窗的 LogShare 摘要与 LogAgent 深度分析此前都会失败。原因是 logshare 的 prepare_upload 直接调用完整崩溃分析 analyze_crash,会 额外扫描 mod jar、比对 mod 快照并查询 Windows 事件,在这种大型实例 上容易出错或超时,错误被前端 uploadTicketForInstance 捕获成 null, 最终表现为无法上传/无法准备诊断。 对齐 Axolotl 的做法,新增轻量的 collect_crash_context / collect_crash_logs,只做路径解析、候选收集、读取与脱敏,不触发 mod 扫描与快照副作用。SourceCandidate / SourceText 补充 upload_name 字段(采集时按目录加 crash-reports/ 前缀),logshare 的 prepare_upload 改调该轻量函数。 --- .../app-lib/src/api/logs/crash_analysis.rs | 81 ++++++++++++++++++- packages/app-lib/src/api/logs/logshare.rs | 29 +------ 2 files changed, 78 insertions(+), 32 deletions(-) diff --git a/packages/app-lib/src/api/logs/crash_analysis.rs b/packages/app-lib/src/api/logs/crash_analysis.rs index 251b90e..7d20b9d 100644 --- a/packages/app-lib/src/api/logs/crash_analysis.rs +++ b/packages/app-lib/src/api/logs/crash_analysis.rs @@ -109,6 +109,7 @@ pub struct CrashAnalysisMod { struct SourceCandidate { path: PathBuf, filename: String, + upload_name: String, source_type: &'static str, modified: SystemTime, } @@ -116,10 +117,25 @@ struct SourceCandidate { #[derive(Debug)] struct SourceText { filename: String, + upload_name: String, source_type: &'static str, modified: SystemTime, content: String, } +pub(super) struct CollectedCrashLog { + pub name: String, + pub content: CensoredString, +} + +pub(super) struct CollectedCrashLogs { + pub combined_log: CensoredString, + pub sources: Vec, +} + +struct CrashContext { + sources: Vec, + credentials: Vec, +} struct Rule { id: &'static str, @@ -1185,20 +1201,25 @@ async fn collect_candidates( ) -> crate::Result> { let mut candidates = Vec::new(); let mut locations = vec![ - (directories.game_logs_dir(instance_root), "minecraft_log"), + ( + directories.game_logs_dir(instance_root), + "minecraft_log", + None, + ), ( directories.game_crash_reports_dir(instance_root), "crash_report", + Some("crash-reports"), ), - (instance_root.to_path_buf(), "instance_log"), + (instance_root.to_path_buf(), "instance_log", None), ]; if let Some(dot_minecraft) = isolated_minecraft_root(instance_root) { // PCL-CE also searches the shared `.minecraft` root for JVM crash // logs when a version-isolated instance is active. - locations.push((dot_minecraft, "minecraft_root_log")); + locations.push((dot_minecraft, "minecraft_root_log", None)); } - for (directory, default_type) in locations { + for (directory, default_type, upload_prefix) in locations { if !tokio::fs::try_exists(&directory).await? { continue; } @@ -1248,6 +1269,9 @@ async fn collect_candidates( }; candidates.push(SourceCandidate { path: entry.path(), + upload_name: upload_prefix + .map(|prefix| format!("{prefix}/{filename}")) + .unwrap_or_else(|| filename.clone()), filename, source_type, modified: metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH), @@ -1257,6 +1281,52 @@ async fn collect_candidates( Ok(candidates) } +async fn collect_crash_context( + instance_id: &str, +) -> crate::Result { + let state = State::get().await?; + let (instance_path, game_dir_override) = + resolve_instance_path(instance_id, &state).await?; + let instance_root = state + .directories + .resolve_game_dir(&instance_path, game_dir_override.as_deref()); + let candidates = + collect_candidates(&instance_root, &state.directories).await?; + let selected = select_run_candidates(candidates); + let sources = read_sources(selected).await; + let credentials = Credentials::get_all(&state.pool) + .await? + .into_iter() + .collect::>(); + Ok(CrashContext { + sources, + credentials, + }) +} + +pub(super) async fn collect_crash_logs( + instance_id: &str, +) -> crate::Result { + let CrashContext { + sources, + credentials, + .. + } = collect_crash_context(instance_id).await?; + let combined_log = + CensoredString::censor(build_combined_log(&sources), &credentials); + let sources = sources + .into_iter() + .map(|source| CollectedCrashLog { + name: source.upload_name, + content: CensoredString::censor(source.content, &credentials), + }) + .collect(); + Ok(CollectedCrashLogs { + combined_log, + sources, + }) +} + fn isolated_minecraft_root(instance_root: &Path) -> Option { let versions_dir = instance_root.parent()?; if versions_dir @@ -1320,6 +1390,7 @@ async fn read_sources(candidates: Vec) -> Vec { }; sources.push(SourceText { filename: candidate.filename, + upload_name: candidate.upload_name, source_type: candidate.source_type, modified: candidate.modified, content: String::from_utf8_lossy(bytes).into_owned(), @@ -1779,6 +1850,7 @@ mod tests { fn source(filename: &str, content: &str) -> SourceText { SourceText { filename: filename.to_string(), + upload_name: filename.to_string(), source_type: "minecraft_log", modified: SystemTime::now(), content: content.to_string(), @@ -1802,6 +1874,7 @@ mod tests { let candidates = vec![SourceCandidate { path: PathBuf::from("launcher_log.txt"), filename: "launcher_log.txt".to_string(), + upload_name: "launcher_log.txt".to_string(), source_type: "minecraft_log", modified: SystemTime::now() - Duration::from_secs(181), }]; diff --git a/packages/app-lib/src/api/logs/logshare.rs b/packages/app-lib/src/api/logs/logshare.rs index 19b43d4..eba4654 100644 --- a/packages/app-lib/src/api/logs/logshare.rs +++ b/packages/app-lib/src/api/logs/logshare.rs @@ -4,8 +4,7 @@ use futures::StreamExt; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; -use super::CensoredString; -use super::crash_analysis::analyze_crash; +use super::crash_analysis::collect_crash_logs; use crate::State; use crate::emit_logshare_ai_event; @@ -200,32 +199,6 @@ fn source_identifier() -> String { format!("{LOGSHARE_SOURCE_PREFIX}/{}", env!("CARGO_PKG_VERSION")) } -struct CollectedCrashLog { - name: String, - content: CensoredString, -} - -struct CollectedCrashLogs { - combined_log: CensoredString, - sources: Vec, -} - -async fn collect_crash_logs( - instance_id: &str, -) -> crate::Result { - let analysis = analyze_crash(instance_id).await?; - Ok(CollectedCrashLogs { - combined_log: analysis.combined_log, - sources: analysis - .sources - .into_iter() - .map(|source| CollectedCrashLog { - name: source.filename, - content: source.content, - }) - .collect(), - }) -} async fn prepare_upload(instance_id: &str) -> crate::Result { let settings = get_log_share_settings().await?; let logs = collect_crash_logs(instance_id).await?; From 0195cbf5ac38f484ae2f59ad7397412a23302b87 Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Sat, 17 Oct 2026 18:16:36 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20collect=5Fcrash?= =?UTF-8?q?=5Fcontext=20=E4=B8=AD=E5=87=AD=E6=8D=AE=E6=94=B6=E9=9B=86?= =?UTF-8?q?=E7=9A=84=E7=B1=BB=E5=9E=8B=E4=B8=8D=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Credentials::get_all 返回 DashMap,直接 collect 会得到 Vec<(Uuid, Credentials)>,与 CrashContext.credentials 的 Vec 不符,导致编译失败。补上与 analyze_crash 一致的 .map(|credential| credential.1)。 --- packages/app-lib/src/api/logs/crash_analysis.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app-lib/src/api/logs/crash_analysis.rs b/packages/app-lib/src/api/logs/crash_analysis.rs index 7d20b9d..ad72a90 100644 --- a/packages/app-lib/src/api/logs/crash_analysis.rs +++ b/packages/app-lib/src/api/logs/crash_analysis.rs @@ -1297,6 +1297,7 @@ async fn collect_crash_context( let credentials = Credentials::get_all(&state.pool) .await? .into_iter() + .map(|credential| credential.1) .collect::>(); Ok(CrashContext { sources,