Fork of Poseidon providing Bukkit #1060 to older Beta versions (b1.0-b1.7.3)
1package com.legacyminecraft.poseidon.util;
2
3import org.bukkit.Bukkit;
4import com.legacyminecraft.poseidon.PoseidonPlugin;
5
6import java.io.File;
7import java.io.FileWriter;
8import java.io.IOException;
9import java.io.PrintWriter;
10import java.nio.file.Files;
11import java.time.Duration;
12import java.time.LocalDateTime;
13import java.time.ZonedDateTime;
14import java.util.Arrays;
15import java.util.concurrent.TimeUnit;
16import java.util.logging.Logger;
17import java.util.logging.*;
18
19public class ServerLogRotator {
20 private final String latestLogFileName;
21 private final Logger logger;
22
23 public ServerLogRotator(String latestLogFileName) {
24 this.latestLogFileName = latestLogFileName;
25 this.logger = Logger.getLogger("Minecraft");
26 }
27
28 /**
29 * Checks if the date in the log line is today's date
30 *
31 * @param date The date in the log line. Format: "yyyy-MM-dd"
32 * @return True if the date in the log line is today's date, false otherwise
33 */
34 private boolean isToday(String date) {
35 String[] dateParts = date.split("-");
36 LocalDateTime logLineDateTime = LocalDateTime.of(Integer.parseInt(dateParts[0]), Integer.parseInt(dateParts[1]), Integer.parseInt(dateParts[2]), 0, 0, 0);
37 LocalDateTime now = LocalDateTime.now();
38 return logLineDateTime.getYear() == now.getYear() && logLineDateTime.getMonthValue() == now.getMonthValue() && logLineDateTime.getDayOfMonth() == now.getDayOfMonth();
39 }
40
41 /**
42 * Archives a log line to a log file with the same date as the date in the log line
43 *
44 * @param parts The log line to archive to a log file haven been split already e.g. ["2024-03-20", "13:02:27", "[INFO]", "This is a log message..."]
45 */
46 private void archiveLine(String[] parts) {
47
48 try {
49
50 String date = parts[0];
51 String time = parts[1];
52 String logLevel = parts[2];
53 String message = String.join(" ", Arrays.copyOfRange(parts, 3, parts.length));
54 // check if a log file with this information already exists
55 File logFile = new File("." + File.separator + "logs" + File.separator + date + ".log");
56 if (!logFile.exists()) {
57 logFile.createNewFile();
58 }
59 // append the log line to the log file with the same date as the date in the log line
60 FileWriter fileWriter = new FileWriter(logFile, true);
61 PrintWriter writer = new PrintWriter(fileWriter);
62 writer.println(date + " " + time + " " + logLevel + " " + message);
63 writer.close();
64
65 // catch any exceptions that occur during the process, and log them. IOExceptions are possible when calling createNewFile()
66 } catch (IOException e) {
67 logger.log(Level.SEVERE, "[Poseidon] Failed to create new log file!");
68 logger.log(Level.SEVERE, e.toString());
69 }
70 }
71
72 /**
73 * Builds historical logs from the latest.log file. Logs from today's date are kept in the latest.log file, while logs from previous dates are archived to log files with the same date as the date in the log line.
74 * Note that if latest.log contains logs from multiple days, the logs will be split by date and archived to the appropriate log files.
75 */
76 private void buildHistoricalLogsFromLatestLogFile() {
77
78 logger.log(Level.INFO, "[Poseidon] Building logs from latest.log...");
79
80 try {
81 // open latest log file
82 File latestLog = new File("." + File.separator + "logs" + File.separator + this.latestLogFileName + ".log");
83 if (!latestLog.exists()) {
84 logger.log(Level.INFO, "[Poseidon] No logs to build from latest.log!");
85 return;
86 }
87
88 // split the contents of the latest log file by line (and strip the newline character)
89 String content = new String(Files.readAllBytes(latestLog.toPath()));
90 String[] lines = content.split("\n");
91 // create a StringBuilder to store today's logs (to write back to latest.log after archiving the rest of the logs)
92 StringBuilder todayLogs = new StringBuilder();
93
94 for (String line : lines) {
95
96 String[] splitLine = line.split(" ");
97 if (splitLine.length < 3) { // all lines will start with a date, time, and log level e.g. "2024-03-20 13:02:27 [INFO]"
98 continue;
99 }
100
101 // make sure the first index is a date
102 if (!splitLine[0].matches("\\d{4}-\\d{2}-\\d{2}")) {
103 continue;
104 }
105
106 // if the log line is of today's date, do not archive it (ignore times)
107 if (isToday(splitLine[0])) {
108 todayLogs.append(line).append("\n");
109 continue;
110 }
111
112 // archive the log line to a log file with the same date as the date in the log line
113 archiveLine(splitLine);
114
115 }
116
117 // clear latest.log and write back today's logs from the StringBuilder
118 FileWriter fileWriter = new FileWriter(latestLog);
119 PrintWriter writer = new PrintWriter(fileWriter);
120 writer.print(todayLogs);
121 writer.close();
122
123 logger.log(Level.INFO, "[Poseidon] Logs built from latest.log!");
124
125 // catch any exceptions that occur during the process, and log them
126 } catch (Exception e) {
127 logger.log(Level.SEVERE, "[Poseidon] Failed to build logs from latest.log!");
128 logger.log(Level.SEVERE, e.toString());
129 }
130 }
131
132 public void start() {
133 // Calculate the initial delay and period for the log rotation task
134 ZonedDateTime now = ZonedDateTime.now();
135 ZonedDateTime nextRun = now.withHour(0).withMinute(0).withSecond(0).withNano(0);
136 if (now.compareTo(nextRun) > 0) nextRun = nextRun.plusDays(1);
137 Duration duration = Duration.between(now, nextRun);
138 long initialDelay = duration.getSeconds();
139 long period = TimeUnit.DAYS.toSeconds(1);
140
141 // do log rotation immediately upon startup to ensure that logs are archived correctly.
142 buildHistoricalLogsFromLatestLogFile();
143
144 // Schedule the log rotation task to run every day at midnight offset by one second to avoid missing logs
145 logger.log(Level.INFO, "[Poseidon] Log rotation task scheduled for run in " + initialDelay + " seconds, and then every " + period + " seconds.");
146 logger.log(Level.INFO, "[Poseidon] If latest.log contains logs from earlier, not previously archived dates, they will be archived to the appropriate log files " + "upon first run of the log rotation task. If log files already exist for these dates, the logs will be appended to the existing log files!");
147 Bukkit.getScheduler().scheduleAsyncRepeatingTask(new PoseidonPlugin(), this::buildHistoricalLogsFromLatestLogFile, (initialDelay + 1) * 20, period * 20);
148 }
149}
150