Fork of Poseidon providing Bukkit #1060 to older Beta versions (b1.0-b1.7.3)
at develop 150 lines 7.0 kB view raw
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