导出日志时附带最近日志 (#4051)

Co-authored-by: Glavo <zjx001202@gmail.com>
This commit is contained in:
Zkitefly
2025-08-10 21:55:01 +08:00
committed by GitHub
parent 7ec880852f
commit 41fd38d8d1
3 changed files with 158 additions and 50 deletions

View File

@@ -1,6 +1,24 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.logging;
import org.jackhuang.hmcl.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.tukaani.xz.LZMA2Options;
import org.tukaani.xz.XZOutputStream;
@@ -127,47 +145,10 @@ public final class Logger {
String caller = CLASS_NAME + ".onExit";
if (logRetention > 0 && logFile != null) {
List<Pair<Path, int[]>> list = new ArrayList<>();
Pattern fileNamePattern = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})T(?<hour>\\d{2})-(?<minute>\\d{2})-(?<second>\\d{2})(\\.(?<n>\\d+))?\\.log(\\.(gz|xz))?");
Path dir = logFile.getParent();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path path : stream) {
Matcher matcher = fileNamePattern.matcher(path.getFileName().toString());
if (matcher.matches() && Files.isRegularFile(path)) {
int year = Integer.parseInt(matcher.group("year"));
int month = Integer.parseInt(matcher.group("month"));
int day = Integer.parseInt(matcher.group("day"));
int hour = Integer.parseInt(matcher.group("hour"));
int minute = Integer.parseInt(matcher.group("minute"));
int second = Integer.parseInt(matcher.group("second"));
int n = Optional.ofNullable(matcher.group("n")).map(Integer::parseInt).orElse(0);
list.add(Pair.pair(path, new int[]{year, month, day, hour, minute, second, n}));
}
}
} catch (IOException e) {
log(Level.WARNING, caller, "Failed to list log files in " + dir, e);
}
var list = findRecentLogFiles(Integer.MAX_VALUE);
if (list.size() > logRetention) {
list.sort((a, b) -> {
int[] v1 = a.getValue();
int[] v2 = b.getValue();
assert v1.length == v2.length;
for (int i = 0; i < v1.length; i++) {
int c = Integer.compare(v1[i], v2[i]);
if (c != 0)
return c;
}
return 0;
});
for (int i = 0, end = list.size() - logRetention; i < end; i++) {
Path file = list.get(i).getKey();
Path file = list.get(i);
try {
if (!Files.isSameFile(file, logFile)) {
log(Level.INFO, caller, "Delete old log file " + file, null);
@@ -276,6 +257,40 @@ public final class Logger {
return logFile;
}
public @NotNull List<Path> findRecentLogFiles(int n) {
if (n <= 0 || logFile == null)
return List.of();
var currentLogFile = LogFile.ofFile(logFile);
Path logDir = logFile.getParent();
if (logDir == null || !Files.isDirectory(logDir))
return List.of();
var logFiles = new ArrayList<LogFile>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(logDir)) {
for (Path path : stream) {
LogFile item = LogFile.ofFile(path);
if (item != null && (currentLogFile == null || item.compareTo(currentLogFile) < 0)) {
logFiles.add(item);
}
}
} catch (IOException e) {
log(Level.WARNING, CLASS_NAME + ".findRecentLogFiles", "Failed to list log files in " + logDir, e);
return List.of();
}
logFiles.sort(Comparator.naturalOrder());
final int resultLength = Math.min(n, logFiles.size());
final int offset = logFiles.size() - resultLength;
var result = new Path[resultLength];
for (int i = 0; i < resultLength; i++) {
result[i] = logFiles.get(i + offset).file;
}
return List.of(result);
}
public void exportLogs(OutputStream output) throws IOException {
Objects.requireNonNull(output);
LogEvent.ExportLog event = new LogEvent.ExportLog(output);
@@ -352,4 +367,69 @@ public final class Logger {
public void trace(String msg, Throwable exception) {
log(Level.TRACE, CallerFinder.getCaller(), msg, exception);
}
private static final class LogFile implements Comparable<LogFile> {
private static final Pattern FILE_NAME_PATTERN = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})T(?<hour>\\d{2})-(?<minute>\\d{2})-(?<second>\\d{2})(\\.(?<n>\\d+))?\\.log(\\.(gz|xz))?");
private static @Nullable LogFile ofFile(Path file) {
if (!Files.isRegularFile(file))
return null;
Matcher matcher = FILE_NAME_PATTERN.matcher(file.getFileName().toString());
if (!matcher.matches())
return null;
int year = Integer.parseInt(matcher.group("year"));
int month = Integer.parseInt(matcher.group("month"));
int day = Integer.parseInt(matcher.group("day"));
int hour = Integer.parseInt(matcher.group("hour"));
int minute = Integer.parseInt(matcher.group("minute"));
int second = Integer.parseInt(matcher.group("second"));
int n = Optional.ofNullable(matcher.group("n")).map(Integer::parseInt).orElse(0);
return new LogFile(file, year, month, day, hour, minute, second, n);
}
private final Path file;
private final int year;
private final int month;
private final int day;
private final int hour;
private final int minute;
private final int second;
private final int n;
private LogFile(Path file, int year, int month, int day, int hour, int minute, int second, int n) {
this.file = file;
this.year = year;
this.month = month;
this.day = day;
this.hour = hour;
this.minute = minute;
this.second = second;
this.n = n;
}
@Override
public int compareTo(@NotNull Logger.LogFile that) {
if (this.year != that.year) return Integer.compare(this.year, that.year);
if (this.month != that.month) return Integer.compare(this.month, that.month);
if (this.day != that.day) return Integer.compare(this.day, that.day);
if (this.hour != that.hour) return Integer.compare(this.hour, that.hour);
if (this.minute != that.minute) return Integer.compare(this.minute, that.minute);
if (this.second != that.second) return Integer.compare(this.second, that.second);
if (this.n != that.n) return Integer.compare(this.n, that.n);
return 0;
}
@Override
public int hashCode() {
return Objects.hash(year, month, day, hour, minute, second, n);
}
@Override
public boolean equals(Object obj) {
return obj instanceof LogFile && compareTo((LogFile) obj) == 0;
}
}
}