1569572ba0
The game writes logs/latest.log in its home like vanilla's log4j config (client-1.21.2.xml): [HH:mm:ss] [thread/LEVEL]: message, rolled at start and at midnight to logs/<date>-<n>.log.gz, seven a day. Messages go through the log facade and still print their old text on stderr. A main-thread panic or a fatal error writes crash-reports/crash-<date>_<time>-client.txt (error, stack, Horizon and Minecraft versions, OS, CPU, memory, GPU adapter, render settings, resource packs, world, last 50 log lines). Development builds crash on purpose with HORIZON_DEBUG_CRASH. The launcher shows "Horizon crashed" when the game it started ends badly, with Open report, Copy report, Open logs folder and Report on Discord; Settings gets a Logs row. horizon-launcher logs and horizon-ctl logs (--lines N, --crash) print the log's end or the newest report, and both MCP servers have a logs tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
2.2 KiB
Rust
63 lines
2.2 KiB
Rust
//! `horizon-launcher logs`: the end of the game's latest.log, and its newest crash report.
|
|
use std::path::Path;
|
|
use std::process::Command;
|
|
|
|
fn logs(home: &Path, args: &[&str]) -> (bool, String, String) {
|
|
let out = Command::new(env!("CARGO_BIN_EXE_horizon-launcher"))
|
|
.arg("logs")
|
|
.args(args)
|
|
.env("HORIZON_HOME", home)
|
|
.output()
|
|
.unwrap();
|
|
(
|
|
out.status.success(),
|
|
String::from_utf8_lossy(&out.stdout).into_owned(),
|
|
String::from_utf8_lossy(&out.stderr).into_owned(),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn logs_prints_the_log_tail_and_the_newest_crash_report() {
|
|
let home = std::env::temp_dir().join(format!("horizon-launcher-logs-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&home);
|
|
|
|
let (ok, _, err) = logs(&home, &[]);
|
|
assert!(!ok);
|
|
assert!(err.contains("latest.log"), "{err}");
|
|
|
|
std::fs::create_dir_all(home.join("logs")).unwrap();
|
|
let text: String = (1..=150)
|
|
.map(|i| format!("[10:00:00] [Render thread/INFO]: line {i}\n"))
|
|
.collect();
|
|
std::fs::write(home.join("logs/latest.log"), text).unwrap();
|
|
let (ok, out, _) = logs(&home, &[]);
|
|
assert!(ok);
|
|
assert_eq!(out.lines().count(), 100);
|
|
assert!(out.starts_with("[10:00:00] [Render thread/INFO]: line 51\n"));
|
|
let (_, out, _) = logs(&home, &["--lines", "1"]);
|
|
assert_eq!(out, "[10:00:00] [Render thread/INFO]: line 150\n");
|
|
let (_, out, _) = logs(&home, &["--json", "--lines", "1"]);
|
|
let json: serde_json::Value = serde_json::from_str(&out).unwrap();
|
|
assert_eq!(json["text"], "[10:00:00] [Render thread/INFO]: line 150\n");
|
|
assert!(json["path"].as_str().unwrap().ends_with("latest.log"));
|
|
|
|
let (ok, _, err) = logs(&home, &["--crash"]);
|
|
assert!(!ok);
|
|
assert!(err.contains("No crash report"), "{err}");
|
|
std::fs::create_dir_all(home.join("crash-reports")).unwrap();
|
|
std::fs::write(
|
|
home.join("crash-reports/crash-2026-10-13_08.00.00-client.txt"),
|
|
"old\n",
|
|
)
|
|
.unwrap();
|
|
std::fs::write(
|
|
home.join("crash-reports/crash-2026-10-14_08.00.00-client.txt"),
|
|
"new\n",
|
|
)
|
|
.unwrap();
|
|
let (ok, out, _) = logs(&home, &["--crash"]);
|
|
assert!(ok);
|
|
assert_eq!(out, "new\n");
|
|
let _ = std::fs::remove_dir_all(&home);
|
|
}
|