feat(m188): launcher.log 早落盘 + 会话日志搬到 logs/game(旧家只读兼容)
用户投诉原话:「我们的启动日志放在哪?…他找半天没找到崩溃日志」「只有一个安装日志」。 启动器运行日志(.ULL/logs/launcher.log) - presenter 构造 HostEnv 时就登记日志目录:启动早期缓冲的 200 行([probe]/[font]/[theme]) 当场刷盘,不再是「只有开过一局游戏才有日志」。 - app/** 里所有 eprintln! 诊断改走 app::diag::diag!(stderr + 文件双写,行首 [tag] 即 scope), 含 boot/boot::fallback/state/update 各线。 - boot 那一拍补一行后端事实(request/effective/fallback)。 - 滚动上限(1 MiB → launcher.1.log,只留一份)写进 README 与 docs/logs-location.md。 会话日志搬家:data/agent/sessions → logs - LauncherLayout: sessions_root()/game_logs() 新增,agent_sessions() 降级为只读兼容。 - host/logs 与 cli/commands/sessions 两个根合并(同 id 新根优先),event_path 回落旧家。 - 不搬不删;日志目录写一份 session-log-location.json 记录(moved=0, policy=read-only-compat)。 入口与文案 - 设置页「通用」新增「日志文件夹」行:路径 + 打开目录 + 复制路径(i18n 中英)。 - 日志页页头的「打开目录」不再是占位:走 M187 的 open_dir,失败文本带路径并送进剪贴板。 - README.md / README.zh-CN.md 新增「日志在哪」章节(含 crash-reports 只在游戏走到那一步才有), docs/logs-location.md 给出上限、双写判据、迁移策略与没验的部分。 守门(每条都有变异证明,见报告) - 旧家会话仍列得出/打不开 ⇒ 只读新根变红;新会话写 logs/game ⇒ 只读旧根、写回旧家都变红。 - launcher.log 滚动 ⇒ 干掉 rename 变红。 - 打开日志文件夹指向真实目录 ⇒ 换成 cache/ 或去掉 create_dir_all 都变红。 - 日志根随三模式变化 ⇒ 把 HostEnv::logs_dir 写死成便携落点变红。
This commit is contained in:
1 parent
efc663a3cb
commit
5fd5cd47dc
47 files changed
+1371
-119
No files matched your search
@@ -27,7 +27,7 @@ pub struct CoreDiagnose {
|
||||
}
|
||||
|
||||
impl CoreDiagnose {
|
||||
/// `root` = `<数据根>/.ULL/data/agent/sessions`。
|
||||
/// `root` = `<数据根>/.ULL/logs`(M188 起;旧名是 `data/agent/sessions`)。
|
||||
#[must_use]
|
||||
pub fn new(root: impl Into<std::path::PathBuf>) -> Self {
|
||||
Self {
|
||||
@@ -35,6 +35,13 @@ impl CoreDiagnose {
|
||||
tail: DEFAULT_TAIL,
|
||||
}
|
||||
}
|
||||
|
||||
/// 再挂一个只读兼容的旧根(`data/agent/sessions`):旧会话也能诊断。
|
||||
#[must_use]
|
||||
pub fn with_legacy(mut self, legacy: impl Into<std::path::PathBuf>) -> Self {
|
||||
self.source = self.source.with_legacy(legacy);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn map_kind(kind: CoreFixKind) -> FixActionKind {
|
||||
|
||||
@@ -13,35 +13,69 @@ use unlucky_core::launch::session_index::{self, SessionIndexEntry};
|
||||
|
||||
use unlucky_agent::port::PortResult;
|
||||
|
||||
/// 一个数据根下的会话索引 + 事件流。
|
||||
/// 一个数据根下的会话索引 + 事件流(可以再挂一个**只读兼容**的旧根,M188)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionSource {
|
||||
root: PathBuf,
|
||||
legacy: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl SessionSource {
|
||||
/// `root` = `<数据根>/.ULL/data/agent/sessions`。
|
||||
/// `root` = `<数据根>/.ULL/logs`(M188 起;旧名是 `data/agent/sessions`)。
|
||||
#[must_use]
|
||||
pub fn new(root: impl Into<PathBuf>) -> Self {
|
||||
Self { root: root.into() }
|
||||
Self {
|
||||
root: root.into(),
|
||||
legacy: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 索引文件路径。
|
||||
/// 再挂一个只读兼容的旧根(`data/agent/sessions`):旧的会话仍然列得出来、打得开。
|
||||
#[must_use]
|
||||
pub fn with_legacy(mut self, legacy: impl Into<PathBuf>) -> Self {
|
||||
self.legacy = Some(legacy.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// 读会话时要看的根,**新根在前**。
|
||||
fn roots(&self) -> Vec<&Path> {
|
||||
let mut roots = vec![self.root.as_path()];
|
||||
if let Some(legacy) = &self.legacy {
|
||||
if legacy.is_dir() {
|
||||
roots.push(legacy.as_path());
|
||||
}
|
||||
}
|
||||
roots
|
||||
}
|
||||
|
||||
/// 索引文件路径(新根)。
|
||||
#[must_use]
|
||||
pub fn index(&self) -> PathBuf {
|
||||
session_index::index_path(&self.root)
|
||||
}
|
||||
|
||||
/// 全部会话(按 `started_at` 倒序,同 id 取最后一行)。
|
||||
/// 全部会话(按 `started_at` 倒序,同 id 取最后一行;两个根合并,新根优先)。
|
||||
///
|
||||
/// # Errors
|
||||
/// 索引读不了(权限/IO)时返回用户可读错误;不存在视为空列表。
|
||||
pub fn entries(&self) -> PortResult<Vec<SessionIndexEntry>> {
|
||||
let index = self.index();
|
||||
let mut entries = session_index::read_all(&index)
|
||||
.map_err(|error| format!("读会话索引 {} 失败:{error}", index.display()))?;
|
||||
entries.sort_by(|a, b| b.started_at.cmp(&a.started_at));
|
||||
Ok(entries)
|
||||
let mut merged: Vec<SessionIndexEntry> = Vec::new();
|
||||
for root in self.roots() {
|
||||
let index = session_index::index_path(root);
|
||||
let entries = session_index::read_all(&index)
|
||||
.map_err(|error| format!("读会话索引 {} 失败:{error}", index.display()))?;
|
||||
for entry in entries {
|
||||
if merged
|
||||
.iter()
|
||||
.any(|item| item.session_id == entry.session_id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
merged.push(entry);
|
||||
}
|
||||
}
|
||||
merged.sort_by(|a, b| b.started_at.cmp(&a.started_at));
|
||||
Ok(merged)
|
||||
}
|
||||
|
||||
/// 按完整 id 或唯一前缀找会话。
|
||||
@@ -85,9 +119,15 @@ impl SessionSource {
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// 事件流文件路径。
|
||||
/// 事件流文件路径(新根没有时回落到旧根)。
|
||||
#[must_use]
|
||||
pub fn event_path(&self, entry: &SessionIndexEntry) -> PathBuf {
|
||||
for root in self.roots() {
|
||||
let path = root.join("game").join(&entry.log_path);
|
||||
if path.is_file() {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
self.root.join("game").join(&entry.log_path)
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,12 @@ pub fn build(ctx: &Ctx) -> Ports {
|
||||
ctx.launcher_config(),
|
||||
ctx.portable_launcher_config(),
|
||||
)),
|
||||
sessions: Arc::new(CoreSessions::new(ctx.sessions_root())),
|
||||
diagnose: Arc::new(CoreDiagnose::new(ctx.sessions_root())),
|
||||
sessions: Arc::new(
|
||||
CoreSessions::new(ctx.sessions_root()).with_legacy(ctx.legacy_sessions_root()),
|
||||
),
|
||||
diagnose: Arc::new(
|
||||
CoreDiagnose::new(ctx.sessions_root()).with_legacy(ctx.legacy_sessions_root()),
|
||||
),
|
||||
game: Arc::new(CoreGame::new(
|
||||
ctx.layout(),
|
||||
ctx.game(),
|
||||
|
||||
@@ -15,13 +15,20 @@ pub struct CoreSessions {
|
||||
}
|
||||
|
||||
impl CoreSessions {
|
||||
/// `root` = `<数据根>/.ULL/data/agent/sessions`。
|
||||
/// `root` = `<数据根>/.ULL/logs`(M188 起;`LauncherConfig` 在其下建 `game/`)。
|
||||
#[must_use]
|
||||
pub fn new(root: impl Into<std::path::PathBuf>) -> Self {
|
||||
Self {
|
||||
source: SessionSource::new(root),
|
||||
}
|
||||
}
|
||||
|
||||
/// 再挂一个只读兼容的旧根(`<数据根>/.ULL/data/agent/sessions`,M188)。
|
||||
#[must_use]
|
||||
pub fn with_legacy(mut self, legacy: impl Into<std::path::PathBuf>) -> Self {
|
||||
self.source = self.source.with_legacy(legacy);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn summary_of(
|
||||
|
||||
Reference in new issue
Block a user