Files
horizon-airdef/launcher/tests/logs.rs
T
Ludovic Marie 1569572ba0 Game logs and crash reports; launcher crash notice; logs in the CLIs and MCP
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>
2026-10-14 16:58:35 +02:00

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);
}