Skip to content

Commit b4df1d1

Browse files
Improve log file behaviour (#1262)
1 parent 181ba08 commit b4df1d1

File tree

2 files changed

+275
-6
lines changed

2 files changed

+275
-6
lines changed
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package io.eiren.util.logging;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
import java.io.BufferedOutputStream;
6+
import java.io.DataOutputStream;
7+
import java.io.File;
8+
import java.io.FileNotFoundException;
9+
import java.io.FileOutputStream;
10+
import java.nio.file.Path;
11+
import java.time.LocalDateTime;
12+
import java.time.format.DateTimeFormatter;
13+
import java.util.ArrayList;
14+
import java.util.logging.ErrorManager;
15+
import java.util.logging.LogRecord;
16+
import java.util.logging.StreamHandler;
17+
18+
19+
public class FileLogHandler extends StreamHandler {
20+
21+
protected class DatedLogFile implements Comparable<DatedLogFile> {
22+
public final File file;
23+
public final LocalDateTime dateTime;
24+
public final int count;
25+
26+
protected DatedLogFile(File file, LocalDateTime dateTime, int count) {
27+
this.file = file;
28+
this.dateTime = dateTime;
29+
this.count = count;
30+
}
31+
32+
@Override
33+
public int compareTo(@NotNull DatedLogFile o) {
34+
int dtCompare = dateTime.compareTo(o.dateTime);
35+
return dtCompare != 0 ? dtCompare : Integer.compare(count, o.count);
36+
}
37+
}
38+
39+
private final char sectionSeparator = '_';
40+
private final String logSuffix = ".log";
41+
42+
private final ArrayList<DatedLogFile> logFiles;
43+
44+
private final Path path;
45+
private final String logTag;
46+
private final DateTimeFormatter dateFormat;
47+
private final LocalDateTime dateTime;
48+
private final String date;
49+
private final int limit;
50+
private final int maxCount;
51+
private final long collectiveLimit;
52+
53+
private DataOutputStream curStream;
54+
private int fileCount = 0;
55+
private long collectiveSize = 0;
56+
57+
public FileLogHandler(
58+
@NotNull Path path,
59+
@NotNull String logTag,
60+
@NotNull DateTimeFormatter dateFormat,
61+
int limit,
62+
int count
63+
) {
64+
this(path, logTag, dateFormat, limit, count, -1);
65+
}
66+
67+
public FileLogHandler(
68+
@NotNull Path path,
69+
@NotNull String logTag,
70+
@NotNull DateTimeFormatter dateFormat,
71+
int limit,
72+
int count,
73+
long collectiveLimit
74+
) {
75+
this.path = path;
76+
this.logTag = logTag;
77+
78+
this.dateFormat = dateFormat;
79+
this.dateTime = LocalDateTime.now();
80+
this.date = dateTime.format(dateFormat);
81+
82+
this.limit = limit;
83+
this.maxCount = count;
84+
this.collectiveLimit = collectiveLimit;
85+
86+
// Find old logs to manage
87+
logFiles = findLogs(path);
88+
if (collectiveLimit > 0) {
89+
collectiveSize = sumFileSizes(logFiles);
90+
}
91+
92+
// Create new log and delete over the count
93+
newFile();
94+
}
95+
96+
private DatedLogFile parseFileName(File file) {
97+
String name = file.getName();
98+
99+
// Log name should have at least two separators, one integer, and at
100+
// least one char for the datetime (4 chars)
101+
if (
102+
!name.startsWith(logTag)
103+
|| !name.endsWith(logSuffix)
104+
|| name.length() < (logTag.length() + logSuffix.length() + 4)
105+
) {
106+
// Ignore non-matching files
107+
return null;
108+
}
109+
110+
int dateEnd = name.lastIndexOf(sectionSeparator);
111+
if (dateEnd < 0) {
112+
// Ignore non-matching files
113+
return null;
114+
}
115+
116+
try {
117+
// Move past the tag, then between the two separators
118+
String dateTimeStr = name.substring(logTag.length() + 1, dateEnd);
119+
LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, dateFormat);
120+
121+
// Move past the date separator and behind the suffix
122+
int logNum = Integer
123+
.parseInt(name, dateEnd + 1, name.length() - logSuffix.length(), 10);
124+
125+
return new DatedLogFile(file, dateTime, logNum);
126+
} catch (Exception e) {
127+
// Unable to parse log file, probably not valid
128+
return null;
129+
}
130+
}
131+
132+
private ArrayList<DatedLogFile> findLogs(Path path) {
133+
ArrayList<DatedLogFile> logFiles = new ArrayList<>();
134+
135+
File[] files = path.toFile().listFiles();
136+
if (files == null)
137+
return logFiles;
138+
139+
// Find all parseable log files
140+
for (File log : files) {
141+
DatedLogFile parsedFile = parseFileName(log);
142+
if (parsedFile != null) {
143+
logFiles.add(parsedFile);
144+
}
145+
}
146+
147+
return logFiles;
148+
}
149+
150+
private long sumFileSizes(ArrayList<DatedLogFile> logFiles) {
151+
long size = 0;
152+
for (DatedLogFile log : logFiles) {
153+
size += log.file.length();
154+
}
155+
return size;
156+
}
157+
158+
private void deleteFile(File file) {
159+
if (!file.delete()) {
160+
file.deleteOnExit();
161+
reportError(
162+
"Failed to delete file, deleting on exit.",
163+
null,
164+
ErrorManager.GENERIC_FAILURE
165+
);
166+
}
167+
}
168+
169+
private DatedLogFile getEarliestFile(ArrayList<DatedLogFile> logFiles) {
170+
DatedLogFile earliest = null;
171+
172+
for (DatedLogFile log : logFiles) {
173+
if (earliest == null || log.compareTo(earliest) < 0) {
174+
earliest = log;
175+
}
176+
}
177+
178+
return earliest;
179+
}
180+
181+
private synchronized void deleteEarliestFile() {
182+
DatedLogFile earliest = getEarliestFile(logFiles);
183+
if (earliest != null) {
184+
// If we have a collective limit, update the current size and clamp
185+
if (collectiveLimit > 0) {
186+
collectiveSize -= earliest.file.length();
187+
if (collectiveSize < 0)
188+
collectiveSize = 0;
189+
}
190+
191+
logFiles.remove(earliest);
192+
deleteFile(earliest.file);
193+
}
194+
}
195+
196+
private synchronized void newFile() {
197+
// Clear the last log file
198+
if (curStream != null) {
199+
collectiveSize += curStream.size();
200+
close();
201+
}
202+
203+
if (maxCount > 0) {
204+
// Delete files over the count
205+
while (logFiles.size() >= maxCount) {
206+
deleteEarliestFile();
207+
}
208+
}
209+
210+
if (collectiveLimit > 0) {
211+
// Delete files over the collective size limit
212+
while (!logFiles.isEmpty() && collectiveSize >= collectiveLimit) {
213+
deleteEarliestFile();
214+
}
215+
}
216+
217+
try {
218+
Path logPath = path
219+
.resolve(
220+
logTag
221+
+ sectionSeparator
222+
+ date
223+
+ sectionSeparator
224+
+ fileCount
225+
+ logSuffix
226+
);
227+
File newFile = logPath.toFile();
228+
229+
// Use DataOutputStream to count bytes written
230+
curStream = new DataOutputStream(
231+
new BufferedOutputStream(new FileOutputStream(newFile))
232+
);
233+
// Closes the last stream automatically if not already done
234+
setOutputStream(curStream);
235+
236+
// Add log to the tracking list to be deleted if needed
237+
logFiles.add(new DatedLogFile(newFile, dateTime, fileCount));
238+
fileCount += 1;
239+
} catch (FileNotFoundException e) {
240+
reportError(null, e, ErrorManager.OPEN_FAILURE);
241+
}
242+
}
243+
244+
@Override
245+
public synchronized void publish(LogRecord record) {
246+
if (!isLoggable(record)) {
247+
return;
248+
}
249+
250+
super.publish(record);
251+
flush();
252+
253+
if (collectiveLimit > 0) {
254+
// Delete files over the collective size limit
255+
while (!logFiles.isEmpty() && collectiveSize + curStream.size() >= collectiveLimit) {
256+
deleteEarliestFile();
257+
}
258+
}
259+
260+
// If written above the log limit, make a new file
261+
if (limit > 0 && curStream.size() >= limit) {
262+
newFile();
263+
}
264+
}
265+
}

server/core/src/main/java/io/eiren/util/logging/LogManager.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
import java.io.File;
44
import java.io.IOException;
55
import java.io.PrintStream;
6-
import java.nio.file.Paths;
6+
import java.time.format.DateTimeFormatter;
77
import java.util.concurrent.atomic.AtomicBoolean;
88
import java.util.logging.ConsoleHandler;
9-
import java.util.logging.FileHandler;
109
import java.util.logging.Handler;
1110
import java.util.logging.Level;
1211
import java.util.logging.Logger;
@@ -30,10 +29,15 @@ public static void initialize(File mainLogDir)
3029
if (!mainLogDir.exists())
3130
mainLogDir.mkdirs();
3231

33-
String lastLogPattern = Paths.get(mainLogDir.getPath(), "log_last_%g.log").toString();
34-
FileHandler filehandler = new FileHandler(lastLogPattern, 25 * 1000000, 2);
35-
filehandler.setFormatter(loc);
36-
global.addHandler(filehandler);
32+
FileLogHandler fileHandler = new FileLogHandler(
33+
mainLogDir.toPath(),
34+
"slimevr-server",
35+
DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"),
36+
25 * 1000000,
37+
2
38+
);
39+
fileHandler.setFormatter(loc);
40+
global.addHandler(fileHandler);
3741
}
3842
}
3943

0 commit comments

Comments
 (0)