diff --git a/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java b/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java index 0076a4d..27e360c 100644 --- a/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java +++ b/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java @@ -54,6 +54,7 @@ public class Bootstrap { logDir = baseDir.resolve("logs"); Files.createDirectories(logDir); javafxPath = baseDir.resolve("lib").resolve("javafx"); + rotateLogs(); log("=== ZernMC Launcher ==="); @@ -371,11 +372,50 @@ public class Bootstrap { return javaBin; } + private static void rotateLogs() { + // Minecraft-style: archive previous latest.log as .log.gz, keep unlimited. + try { + Path latest = logDir.resolve("latest.log"); + if (Files.exists(latest) && Files.size(latest) > 0) { + String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")); + int n = 1; + Path archive; + do { + archive = logDir.resolve(ts + (n == 1 ? "" : "-" + n) + ".log.gz"); + n++; + } while (Files.exists(archive)); + gzip(archive, latest); + Files.deleteIfExists(latest); + } + } catch (Exception e) { + System.out.println("[Bootstrap] rotate error: " + e.getMessage()); + } + // Migrate old unlimited log. + try { + Files.deleteIfExists(logDir.resolve("launcher.log")); + } catch (Exception ignored) { + } + } + + private static void gzip(Path archive, Path source) { + try (java.io.InputStream in = Files.newInputStream(source); + java.io.OutputStream out = Files.newOutputStream(archive); + java.util.zip.GZIPOutputStream gz = new java.util.zip.GZIPOutputStream(out)) { + byte[] buf = new byte[65536]; + int n; + while ((n = in.read(buf)) > 0) { + gz.write(buf, 0, n); + } + } catch (Exception e) { + System.out.println("[Bootstrap] gzip error: " + e.getMessage()); + } + } + private static void log(String msg) { String entry = "[" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "] " + msg; System.out.println(entry); try { - Files.writeString(logDir.resolve("launcher.log"), entry + "\n", + Files.writeString(logDir.resolve("latest.log"), entry + "\n", StandardOpenOption.CREATE, StandardOpenOption.APPEND); } catch (Exception ignored) {} if (ui != null) ui.setStatus(msg); @@ -881,7 +921,7 @@ public class Bootstrap { // Exit info.put("exit_code", exitCode); info.put("stderr", stderr != null ? stderr : ""); - info.put("launcher_log", readFileSafe(logDir.resolve("launcher.log"), 200)); + info.put("launcher_log", readFileSafe(logDir.resolve("latest.log"), 200)); // System Map system = new HashMap<>(); diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/Bootstrap.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/Bootstrap.java index 2ca0f04..96f1228 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/Bootstrap.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/Bootstrap.java @@ -211,7 +211,7 @@ public class Bootstrap { String entry = "[" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "] " + msg; System.out.println(entry); try { - Files.writeString(logDir.resolve("launcher.log"), entry + "\n", + Files.writeString(logDir.resolve("latest.log"), entry + "\n", StandardOpenOption.CREATE, StandardOpenOption.APPEND); } catch (Exception ignored) {} } diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/LauncherLogger.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/LauncherLogger.java index 582536d..a8e7eaa 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/LauncherLogger.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/LauncherLogger.java @@ -53,11 +53,10 @@ public class LauncherLogger { try { Path logsDir = Paths.get("").toAbsolutePath().resolve("logs"); Files.createDirectories(logsDir); - logFile = logsDir.resolve("launcher.log"); + rotateLogs(logsDir); + logFile = logsDir.resolve("latest.log"); - Files.writeString(logFile, - "=== Launcher Log " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " ===\n", - StandardOpenOption.CREATE, StandardOpenOption.APPEND); + Files.writeString(logFile, sessionHeader(), StandardOpenOption.CREATE); System.out.println("[LauncherLogger] initialized, log: " + logFile.toAbsolutePath()); } catch (Exception e) { @@ -66,6 +65,62 @@ public class LauncherLogger { } } + private static String sessionHeader() { + String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + String os = System.getProperty("os.name") + " " + System.getProperty("os.version") + + " " + System.getProperty("os.arch"); + String java = System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")"; + String mem = (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " MB"; + return "== ZernMC Launcher =======================================================\n" + + "== Started: " + ts + "\n" + + "== OS: " + os + "\n" + + "== Java: " + java + "\n" + + "== Max memory: " + mem + "\n" + + "====================================================================\n"; + } + + /** + * Minecraft-style rotation: compress the previous latest.log into a dated + * .log.gz archive (unlimited history), then remove the legacy launcher.log. + */ + private static void rotateLogs(Path logsDir) { + // Legacy one-instance log from older builds: delete it, it is replaced by latest.log. + try { + Files.deleteIfExists(logsDir.resolve("launcher.log")); + } catch (Exception ignored) { + } + + Path latest = logsDir.resolve("latest.log"); + try { + if (Files.exists(latest) && Files.size(latest) > 0) { + String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")); + int n = 1; + Path archive; + do { + archive = logsDir.resolve(ts + (n == 1 ? "" : "-" + n) + ".log.gz"); + n++; + } while (Files.exists(archive)); + gzip(logsDir, archive, latest); + Files.deleteIfExists(latest); + } + } catch (Exception e) { + System.err.println("[LauncherLogger] rotate error: " + e.getMessage()); + } + } + + private static void gzip(Path logsDir, Path archive, Path source) { + try (var in = Files.newInputStream(source); + var gz = new java.util.zip.GZIPOutputStream(Files.newOutputStream(archive))) { + byte[] buf = new byte[65536]; + int n; + while ((n = in.read(buf)) > 0) { + gz.write(buf, 0, n); + } + } catch (Exception e) { + System.err.println("[LauncherLogger] gzip error: " + e.getMessage()); + } + } + public static Path getLogFile() { return logFile; } diff --git a/launcher/pom.xml b/launcher/pom.xml index 6b552c5..5efa756 100644 --- a/launcher/pom.xml +++ b/launcher/pom.xml @@ -20,7 +20,7 @@ 1.0.16 - 13 + 14 21 21 UTF-8