Merge pull request #18 from ApTx2012/feat/backend-logtype
Sync source to CNB / Sync Git ref (push) Skipped
Repository checks / typos (push) Canceled after 0s
Repository checks / tombi (push) Canceled after 0s
Axolotl desktop CI / guardrails (push) Canceled after 0s
Axolotl desktop CI / desktop (macos-latest) (push) Canceled after 0s
Axolotl desktop CI / desktop (ubuntu-latest) (push) Canceled after 0s
Axolotl desktop CI / desktop (windows-latest) (push) Canceled after 0s
Axolotl desktop CI / website (push) Canceled after 0s
Rust checks / shear (push) Canceled after 0s

feat: 扩展日志类型,支持 JVM 崩溃与启动器日志
This commit is contained in:
AxTps authored and GitHub committed 2026-10-11 15:44:25 +08:00
commit 2dcf26c9d9
1 file changed
+100 -31
+100 -31
View File
@@ -1,6 +1,6 @@
use std::fmt::Write as _; use std::fmt::Write as _;
use std::io::{BufRead, Read, SeekFrom}; use std::io::{BufRead, Read, SeekFrom};
use std::path::Path; use std::path::{Path, PathBuf};
use std::time::SystemTime; use std::time::SystemTime;
use futures::TryFutureExt; use futures::TryFutureExt;
@@ -48,6 +48,54 @@ pub struct Logs {
pub enum LogType { pub enum LogType {
InfoLog, InfoLog,
CrashReport, CrashReport,
JvmCrash,
LauncherLog,
}
fn isolated_minecraft_root(instance_root: &Path) -> Option<PathBuf> {
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<PathBuf> {
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; const LOG_COMPACTION_THRESHOLD: usize = 20;
@@ -457,14 +505,11 @@ pub async fn get_logs_from_type(
.directories .directories
.resolve_game_dir(&instance_path, game_dir_override.as_deref()); .resolve_game_dir(&instance_path, game_dir_override.as_deref());
let logs_folder = match log_type { let mut seen_files = std::collections::HashSet::new();
LogType::InfoLog => state.directories.game_logs_dir(&game_dir), for logs_folder in log_directories_for_type(&state, &game_dir, log_type) {
LogType::CrashReport => { if !logs_folder.exists() {
state.directories.game_crash_reports_dir(&game_dir) continue;
} }
};
if logs_folder.exists() {
for entry in std::fs::read_dir(&logs_folder) for entry in std::fs::read_dir(&logs_folder)
.map_err(|e| IOError::with_path(e, &logs_folder))? .map_err(|e| IOError::with_path(e, &logs_folder))?
{ {
@@ -480,6 +525,12 @@ pub async fn get_logs_from_type(
} }
if let Some(file_name) = path.file_name() { if let Some(file_name) = path.file_name() {
let file_name = file_name.to_string_lossy().to_string(); 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.push(
Logs::build( Logs::build(
log_type, log_type,
@@ -516,6 +567,20 @@ pub async fn get_logs(
&mut logs, &mut logs,
) )
.await?; .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::<crate::Result<Vec<Logs>>>()?; let mut logs = logs.into_iter().collect::<crate::Result<Vec<Logs>>>()?;
logs.sort_by(|a, b| b.age.cmp(&a.age).then(b.filename.cmp(&a.filename))); logs.sort_by(|a, b| b.age.cmp(&a.age).then(b.filename.cmp(&a.filename)));
@@ -535,13 +600,15 @@ pub async fn get_logs_by_filename(
.directories .directories
.resolve_game_dir(&instance_path, game_dir_override.as_deref()); .resolve_game_dir(&instance_path, game_dir_override.as_deref());
let path = match log_type { let path = log_directories_for_type(&state, &game_dir, log_type)
LogType::InfoLog => state.directories.game_logs_dir(&game_dir), .into_iter()
LogType::CrashReport => { .map(|directory| directory.join(&filename))
state.directories.game_crash_reports_dir(&game_dir) .find(|path| path.is_file())
} .ok_or_else(|| {
} crate::ErrorKind::OtherError(format!(
.join(&filename); "Log file not found: {filename}"
))
})?;
let metadata = std::fs::metadata(&path)?; let metadata = std::fs::metadata(&path)?;
let age = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH); let age = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
@@ -555,14 +622,15 @@ async fn get_output_by_filename_from_path(
log_type: LogType, log_type: LogType,
file_name: &str, file_name: &str,
) -> crate::Result<CensoredString> { ) -> crate::Result<CensoredString> {
let logs_folder = match log_type { let path = log_directories_for_type(state, game_dir, log_type)
LogType::InfoLog => state.directories.game_logs_dir(game_dir), .into_iter()
LogType::CrashReport => { .map(|directory| directory.join(file_name))
state.directories.game_crash_reports_dir(game_dir) .find(|path| path.is_file())
} .ok_or_else(|| {
}; crate::ErrorKind::OtherError(format!(
"Log file not found: {file_name}"
let path = logs_folder.join(file_name); ))
})?;
let credentials = Credentials::get_all(&state.pool) let credentials = Credentials::get_all(&state.pool)
.await? .await?
@@ -659,14 +727,15 @@ pub async fn delete_logs_by_filename(
.directories .directories
.resolve_game_dir(&instance_path, game_dir_override.as_deref()); .resolve_game_dir(&instance_path, game_dir_override.as_deref());
let logs_folder = match log_type { let path = log_directories_for_type(&state, &game_dir, log_type)
LogType::InfoLog => state.directories.game_logs_dir(&game_dir), .into_iter()
LogType::CrashReport => { .map(|directory| directory.join(filename))
state.directories.game_crash_reports_dir(&game_dir) .find(|path| path.is_file())
} .ok_or_else(|| {
}; crate::ErrorKind::OtherError(format!(
"Log file not found: {filename}"
let path = logs_folder.join(filename); ))
})?;
io::remove_file(&path).await?; io::remove_file(&path).await?;
Ok(()) Ok(())
} }