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 遍历多目录并按文件名去重
This commit is contained in:
1 parent
60f3fd8816
commit
a8d8e1674b
1 file changed
+100
-31
@@ -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;
|
||||||
@@ -39,6 +39,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;
|
||||||
@@ -448,14 +496,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))?
|
||||||
{
|
{
|
||||||
@@ -471,6 +516,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,
|
||||||
@@ -507,6 +558,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)));
|
||||||
@@ -526,13 +591,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);
|
||||||
@@ -546,14 +613,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?
|
||||||
@@ -650,14 +718,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(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user