diff --git a/tinylog-impl/pom.xml b/tinylog-impl/pom.xml
index 8e6308f0b..066b44ac3 100644
--- a/tinylog-impl/pom.xml
+++ b/tinylog-impl/pom.xml
@@ -75,7 +75,12 @@
org.tinylog
tinylog-api
-
+
+ org.codehaus.mojo
+ animal-sniffer-annotations
+ compile
+
+
diff --git a/tinylog-impl/src/main/java/org/tinylog/writers/RollingFileWriter.java b/tinylog-impl/src/main/java/org/tinylog/writers/RollingFileWriter.java
index 9a9713dfe..1d1b0ced4 100644
--- a/tinylog-impl/src/main/java/org/tinylog/writers/RollingFileWriter.java
+++ b/tinylog-impl/src/main/java/org/tinylog/writers/RollingFileWriter.java
@@ -17,10 +17,14 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
import org.tinylog.Level;
import org.tinylog.configuration.ServiceLoader;
import org.tinylog.core.LogEntry;
@@ -43,6 +47,7 @@ public final class RollingFileWriter extends AbstractFormatPatternWriter {
private final int backups;
private final boolean buffered;
private final boolean writingThread;
+ private final DynamicPath linkToLatest;
private final Charset charset;
private ByteArrayWriter writer;
@@ -72,8 +77,9 @@ public RollingFileWriter(final Map properties) throws FileNotFou
path = new DynamicPath(getFileName(properties));
policies = createPolicies(properties.get("policies"));
backups = properties.containsKey("backups") ? Integer.parseInt(properties.get("backups")) : -1;
+ linkToLatest = properties.containsKey("latest") ? new DynamicPath(properties.get("latest")) : null;
- List files = path.getAllFiles();
+ List files = filterOutSymlinks(path.getAllFiles());
String fileName;
boolean append;
@@ -96,7 +102,43 @@ public RollingFileWriter(final Map properties) throws FileNotFou
charset = getCharset(properties);
buffered = Boolean.parseBoolean(properties.get("buffered"));
writingThread = Boolean.parseBoolean(properties.get("writingthread"));
- writer = createByteArrayWriter(fileName, append, buffered, false, false);
+ writer = createByteArrayWriterAndLinkLatest(fileName, append, buffered, false, false);
+ }
+
+ @IgnoreJRERequirement
+ private static List filterOutSymlinks(final List files) {
+ if (!RuntimeProvider.isAndroid()) {
+ List symlinks = new ArrayList();
+ for (File file : files) {
+ if (Files.isSymbolicLink(file.toPath())) {
+ symlinks.add(file);
+ }
+ }
+ files.removeAll(symlinks);
+ }
+ return files;
+ }
+
+ @IgnoreJRERequirement
+ private ByteArrayWriter createByteArrayWriterAndLinkLatest(final String fileName, final boolean append, final boolean buffered,
+ final boolean threadSafe, final boolean shared) throws FileNotFoundException {
+ ByteArrayWriter writer = createByteArrayWriter(fileName, append, buffered, threadSafe, shared);
+ if (linkToLatest != null) {
+ File logFile = new File(fileName);
+ File linkFile = new File(linkToLatest.resolve());
+ if (!RuntimeProvider.isAndroid()) {
+ try {
+ Path linkPath = linkFile.toPath();
+ Files.delete(linkPath);
+ Files.createSymbolicLink(linkPath, logFile.toPath());
+ } catch (IOException exception) {
+ InternalLogger.log(Level.ERROR, exception, "Failed to create symlink '" + linkFile + "'");
+ }
+ } else {
+ InternalLogger.log(Level.WARN, "Cannot create symlink to latest log segment on Android");
+ }
+ }
+ return writer;
}
@Override
@@ -145,9 +187,11 @@ private void internalWrite(final byte[] data) throws IOException {
if (!canBeContinued(data, policies)) {
writer.close();
+ List existingFiles = filterOutSymlinks(path.getAllFiles());
+ deleteBackups(existingFiles, backups);
+
String fileName = path.resolve();
- deleteBackups(path.getAllFiles(), backups);
- writer = createByteArrayWriter(fileName, false, buffered, false, false);
+ writer = createByteArrayWriterAndLinkLatest(fileName, false, buffered, false, false);
for (Policy policy : policies) {
policy.reset();
diff --git a/tinylog-impl/src/test/java/org/tinylog/writers/RollingFileWriterTest.java b/tinylog-impl/src/test/java/org/tinylog/writers/RollingFileWriterTest.java
index 1319a16f3..ebddf47bd 100644
--- a/tinylog-impl/src/test/java/org/tinylog/writers/RollingFileWriterTest.java
+++ b/tinylog-impl/src/test/java/org/tinylog/writers/RollingFileWriterTest.java
@@ -481,4 +481,53 @@ public void isRegistered() throws IOException {
assertThat(writer).isInstanceOf(RollingFileWriter.class);
}
+ /**
+ * Verifies that obsolete backup files will be deleted when rolling log file.
+ *
+ * @throws IOException
+ * Failed access to temporary folder or files
+ */
+ @Test
+ public void updateSymlinkToLatestAtRollOver() throws IOException {
+ File file1 = folder.newFile("0");
+ File file2 = folder.newFile("1");
+ File file3 = folder.newFile("2");
+ File file4 = folder.newFile("3");
+ File file5 = folder.newFile("4");
+ File linkToLatest = folder.newFile("latest");
+
+ file1.setLastModified(0);
+ file2.setLastModified(1000);
+ file3.setLastModified(2000);
+ file4.setLastModified(3000);
+ file5.delete();
+
+ Map properties = new HashMap<>();
+ properties.put("file", new File(folder.getRoot(), "{count}").getAbsolutePath());
+ properties.put("latest", linkToLatest.getAbsolutePath());
+ properties.put("format", "{message}");
+ properties.put("policies", "size: 10");
+ properties.put("backups", "3");
+
+ RollingFileWriter writer = new RollingFileWriter(properties);
+
+ assertThat(file1).exists();
+ assertThat(file2).exists();
+ assertThat(file3).exists();
+ assertThat(file4).exists();
+ assertThat(file5).doesNotExist();
+
+ writer.write(LogEntryBuilder.empty().message("First").create());
+ writer.write(LogEntryBuilder.empty().message("Second").create());
+
+ assertThat(file1).doesNotExist();
+ assertThat(file2).exists();
+ assertThat(file3).exists();
+ assertThat(file4).hasContent("First" + NEW_LINE);
+ assertThat(file5).hasContent("Second" + NEW_LINE);
+
+ assertThat(linkToLatest.getCanonicalPath()).isEqualTo(file5.getCanonicalPath());
+
+ writer.close();
+ }
}