Index: src/main/java/org/apache/log4j/DailyRollingFileAppender.java
===================================================================
--- src/main/java/org/apache/log4j/DailyRollingFileAppender.java (revision 544346)
+++ src/main/java/org/apache/log4j/DailyRollingFileAppender.java (working copy)
@@ -28,6 +28,8 @@
import java.util.TimeZone;
import java.util.Locale;
+import org.apache.log4j.helpers.BackupDirectory;
+import org.apache.log4j.helpers.FileMove;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;
@@ -179,6 +181,7 @@
// The gmtTimeZone is used only in computeCheckPeriod() method.
static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
+ protected String backupDirectory = null;
/**
The default constructor does nothing. */
@@ -212,6 +215,19 @@
return datePattern;
}
+ public void setBackupDirectory(String directory) {
+ String val = directory.trim();
+ backupDirectory = val;
+ }
+
+ public String getBackupDirectory() {
+ return backupDirectory;
+ }
+
+ protected String getScheduledFilename() {
+ return scheduledFilename;
+ }
+
public void activateOptions() {
super.activateOptions();
if(datePattern != null && fileName != null) {
@@ -312,17 +328,20 @@
// close current file, and rename it to datedFilename
this.closeFile();
- File target = new File(scheduledFilename);
+ String backupFileName = BackupDirectory.createBackupFileName(backupDirectory, scheduledFilename);
+ LogLog.debug("backupFileName=" + backupFileName);
+
+ File target = new File(backupFileName);
if (target.exists()) {
target.delete();
}
File file = new File(fileName);
- boolean result = file.renameTo(target);
+ boolean result = FileMove.move(file, target);
if(result) {
- LogLog.debug(fileName +" -> "+ scheduledFilename);
+ LogLog.debug(fileName +" -> "+ target);
} else {
- LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");
+ LogLog.error("Failed to rename ["+fileName+"] to ["+target+"].");
}
try {
Index: src/main/java/org/apache/log4j/helpers/BackupDirectory.java
===================================================================
--- src/main/java/org/apache/log4j/helpers/BackupDirectory.java (revision 0)
+++ src/main/java/org/apache/log4j/helpers/BackupDirectory.java (revision 0)
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.io.File;
+
+/**
+ Utility class for generating a backup directory name
+ */
+public class BackupDirectory {
+
+ /** Create a backup file name
+ * Uses the log file name and the backup directory to generate the path for the backup file
+ * If the backup directory does not exist it will attempt to create the directory
+ *
+ * @param backupDirectory The backup directory path
+ * @param fileName The log file directory path
+ * @return The backup log file path
+ * */
+ public final static String createBackupFileName(String backupDirectory, String fileName) {
+ String fileNameOnly = fileName;
+ File backupDir = null;
+
+ if(backupDirectory != null) {
+ fileNameOnly = new File(fileName).getName();
+ backupDir = new File(backupDirectory);
+ if(!backupDir.exists()) {
+ LogLog.debug("Backup directory does not exist attempting to create backupDirectory=" + backupDir);
+ if(!backupDir.mkdirs()) {
+ LogLog.warn("Could not create backupDirectory=" + backupDir + " using default directory instead");
+ fileNameOnly = fileName;
+ backupDir = null;
+ }
+ } else if(!backupDir.isDirectory()) {
+ LogLog.warn("Backup directory is not a directory. backupDirectory=" + backupDir + " using default directory instead");
+ fileNameOnly = fileName;
+ backupDir = null;
+ }
+ }
+
+ return new File(backupDir, fileNameOnly).getPath();
+ }
+}
Index: src/main/java/org/apache/log4j/helpers/FileMove.java
===================================================================
--- src/main/java/org/apache/log4j/helpers/FileMove.java (revision 0)
+++ src/main/java/org/apache/log4j/helpers/FileMove.java (revision 0)
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ Utility class for moving Files
+ */
+public class FileMove {
+
+ /** Copy a File
+ * The renameTo method does not allow action across NFS mounted filesystems
+ * this method is the workaround
+ * see sun bug id:4073756 for more info
+ *
+ * @param fromFile The existing File
+ * @param toFile The new File
+ * @return true
if and only if the renaming succeeded;
+ * false
otherwise
+ * */
+ public final static boolean copy(File fromFile, File toFile) {
+
+ try {
+ FileInputStream in = new FileInputStream(fromFile);
+ FileOutputStream out = new FileOutputStream(toFile);
+ BufferedInputStream inBuffer = new BufferedInputStream(in);
+ BufferedOutputStream outBuffer = new
+ BufferedOutputStream(out);
+
+ int theByte = 0;
+
+ while ((theByte = inBuffer.read()) > -1) {
+ outBuffer.write(theByte);
+ }
+
+ outBuffer.close();
+ inBuffer.close();
+ out.close();
+ in.close();
+
+ // cleanup if files are not the same length
+ if (fromFile.length() != toFile.length()) {
+ toFile.delete();
+
+ return false;
+ }
+
+ return true;
+
+ } catch (IOException e) {
+
+ return false;
+ }
+ }
+
+ /** Move a File
+ * The renameTo method does not allow action across NFS mounted
+ * filesystems. this method is the workaround
+ * see sun bug id:4073756 for more info
+ *
+ * @param fromFile The existing File
+ * @param toFile The new File
+ * @return true
if and only if the renaming succeeded;
+ * false
otherwise
+ * */
+ public final static boolean move(File fromFile, File toFile) {
+
+ if(fromFile.renameTo(toFile)) {
+ return true;
+ }
+
+ // delete if copy was successful, otherwise move will fail
+ if (copy(fromFile, toFile)) {
+ return fromFile.delete();
+ }
+
+ return false;
+ }
+}
Index: src/main/java/org/apache/log4j/RollingFileAppender.java
===================================================================
--- src/main/java/org/apache/log4j/RollingFileAppender.java (revision 544346)
+++ src/main/java/org/apache/log4j/RollingFileAppender.java (working copy)
@@ -22,6 +22,8 @@
import java.io.IOException;
import java.io.Writer;
import java.io.File;
+import org.apache.log4j.helpers.BackupDirectory;
+import org.apache.log4j.helpers.FileMove;
import org.apache.log4j.helpers.OptionConverter;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.helpers.CountingQuietWriter;
@@ -48,6 +50,8 @@
protected int maxBackupIndex = 1;
private long nextRollover = 0;
+
+ protected String backupDirectory = null;
/**
The default constructor simply calls its {@link
@@ -102,6 +106,18 @@
return maxFileSize;
}
+ public
+ void setBackupDirectory(String directory) {
+ String val = directory.trim();
+ backupDirectory = val;
+ }
+
+ public
+ String getBackupDirectory() {
+ return backupDirectory;
+ }
+
+
/**
Implements the usual roll over behaviour.
@@ -130,33 +146,38 @@
}
LogLog.debug("maxBackupIndex="+maxBackupIndex);
+ String backupFileName = BackupDirectory.createBackupFileName(backupDirectory, fileName);
+ LogLog.debug("backupFileName=" + backupFileName);
+
boolean renameSucceeded = true;
// If maxBackups <= 0, then there is no file renaming to be done.
if(maxBackupIndex > 0) {
// Delete the oldest file, to keep Windows happy.
- file = new File(fileName + '.' + maxBackupIndex);
+ file = new File(backupFileName + '.' + maxBackupIndex);
if (file.exists())
renameSucceeded = file.delete();
// Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2}
for (int i = maxBackupIndex - 1; i >= 1 && renameSucceeded; i--) {
- file = new File(fileName + "." + i);
- if (file.exists()) {
- target = new File(fileName + '.' + (i + 1));
- LogLog.debug("Renaming file " + file + " to " + target);
- renameSucceeded = file.renameTo(target);
- }
+ file = new File(backupFileName + "." + i);
+ if (file.exists()) {
+ target = new File(backupFileName + '.' + (i + 1));
+
+ LogLog.debug("Renaming file " + file + " to " + target);
+ renameSucceeded = FileMove.move(file, target);
+ }
}
if(renameSucceeded) {
// Rename fileName to fileName.1
- target = new File(fileName + "." + 1);
+ target = new File(backupFileName + "." + 1);
this.closeFile(); // keep windows happy.
file = new File(fileName);
LogLog.debug("Renaming file " + file + " to " + target);
- renameSucceeded = file.renameTo(target);
+ renameSucceeded = FileMove.move(file, target);
+
//
// if file rename failed, reopen file with append = true
//
@@ -186,7 +207,7 @@
}
}
}
-
+
public
synchronized
void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)
Index: tests/build.xml
===================================================================
--- tests/build.xml (revision 544346)
+++ tests/build.xml (working copy)
@@ -161,7 +161,7 @@
HierarchyThreshold, DefaultInit, SocketServer,
XMLLayout, AsyncAppender,
OptionConverter, BoundedFIFO,
- CyclicBuffer, OR,
+ CyclicBuffer, BackupDirectory, OR,
LevelMatchFilter, PatternParser,
PatternLayout, RFA, ERFA, DRFA,
NTEventLogAppender, Syslog, Socket"/>
@@ -387,6 +387,15 @@
+
+
+
+
+
+
+
+
Index: tests/input/RFA2.properties
===================================================================
--- tests/input/RFA2.properties (revision 0)
+++ tests/input/RFA2.properties (revision 0)
@@ -0,0 +1,13 @@
+log4j.rootLogger=DEBUG, testAppender
+log4j.appender.testAppender=org.apache.log4j.RollingFileAppender
+log4j.appender.testAppender.file=output/RFA-testBackup.log
+log4j.appender.testAppender.BackupDirectory=output/archive
+log4j.appender.testAppender.Append=false
+log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout
+log4j.appender.testAppender.layout.ConversionPattern=%m\n
+log4j.appender.testAppender.maxFileSize=100
+
+# Prevent internal log4j DEBUG messages from polluting the output.
+log4j.logger.org.apache.log4j.PropertyConfigurator=INFO
+log4j.logger.org.apache.log4j.config.PropertySetter=INFO
+log4j.logger.org.apache.log4j.FileAppender=INFO
Index: tests/run-tests.bat
===================================================================
--- tests/run-tests.bat (revision 544346)
+++ tests/run-tests.bat (working copy)
@@ -75,6 +75,7 @@
java junit.textui.TestRunner org.apache.log4j.helpers.OptionConverterTestCase
java junit.textui.TestRunner org.apache.log4j.helpers.BoundedFIFOTestCase
java junit.textui.TestRunner org.apache.log4j.helpers.CyclicBufferTestCase
+java junit.textui.TestRunner org.apache.log4j.helpers.BackupDirectoryTestCase
java junit.textui.TestRunner org.apache.log4j.or.ORTestCase
java junit.textui.TestRunner org.apache.log4j.varia.LevelMatchFilterTestCase
java junit.textui.TestRunner org.apache.log4j.helpers.PatternParserTestCase
Index: tests/src/java/org/apache/log4j/DRFATestCase.java
===================================================================
--- tests/src/java/org/apache/log4j/DRFATestCase.java (revision 544346)
+++ tests/src/java/org/apache/log4j/DRFATestCase.java (working copy)
@@ -421,5 +421,48 @@
assertTrue(firstFile.length() > 0);
}
+
+ /**
+ * Tests rollOver with a minute periodicity.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public void testMinuteRolloverWithBackupDirectory() throws IOException, InterruptedException {
+ Layout layout = new SimpleLayout();
+ String filename = "output/drfa_minuteRolloverBackup.log";
+ String pattern = "'.'yyyy-MM-dd-HH-mm";
+ DailyRollingFileAppender appender =
+ new DailyRollingFileAppender(layout,
+ filename,
+ pattern);
+ appender.setBackupDirectory("output/drfa_archive");
+ Logger root = Logger.getRootLogger();
+ root.addAppender(appender);
+
+ root.info("Hello, World");
+ String expectedFilename = appender.getScheduledFilename();
+ String fileNameOnly = new File(expectedFilename).getName();
+ File backupFile = new File("output/drfa_archive/" + fileNameOnly);
+
+ // In theory this assertion could fail if this test is run twice in the same minute but it seems
+ // unlikely since we wait until the end of the current minute in the same test
+ assertFalse(backupFile.exists());
+
+ Calendar cal = Calendar.getInstance();
+ long now = cal.getTime().getTime();
+ cal.set(Calendar.SECOND, 3);
+ cal.set(Calendar.MILLISECOND, 0);
+ cal.add(Calendar.MINUTE, 1);
+
+ long until = cal.getTime().getTime();
+ Thread.sleep(until - now);
+
+ root.info("Hello, World");
+ assertTrue("backup file " + backupFile + " does not exist", backupFile.exists());
+ assertTrue(backupFile.length() > 0);
+
+ }
+
}
Index: tests/src/java/org/apache/log4j/helpers/BackupDirectoryTestCase.java
===================================================================
--- tests/src/java/org/apache/log4j/helpers/BackupDirectoryTestCase.java (revision 0)
+++ tests/src/java/org/apache/log4j/helpers/BackupDirectoryTestCase.java (revision 0)
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+
+/**
+ * Tests for BackupDirectory.
+ *
+ **/
+public class BackupDirectoryTestCase extends TestCase {
+ /**
+ * Create new instance of BackupDirectoryTest.
+ * @param testName test name
+ */
+ public BackupDirectoryTestCase(final String testName) {
+ super(testName);
+ }
+
+ public void testBackupDirectoryIsSubDirectoryOfFileDirectory() throws Exception {
+ String backupDirectory = "output" + File.separator + "archive";
+ String fileName = "output" + File.separator + "testBackupDirectory.log";
+ String backupFileName = BackupDirectory.createBackupFileName(backupDirectory, fileName);
+ assertTrue(new File(backupDirectory).exists());
+ assertFalse(fileName.equals(backupFileName));
+ assertFalse(new File(backupFileName).exists());
+ assertFalse(new File(fileName).exists());
+ }
+
+ public void testBackupDirectoryIsSameAsFileDirectory() throws Exception {
+ String backupDirectory = "output";
+ String fileName = backupDirectory + File.separator + "testBackupDirectory.log";
+ String backupFileName = BackupDirectory.createBackupFileName(backupDirectory, fileName);
+ assertTrue(new File(backupDirectory).exists());
+ assertEquals(fileName, backupFileName);
+ assertFalse(new File(backupFileName).exists());
+ assertFalse(new File(fileName).exists());
+ }
+
+ public void testInvalidBackupDirectory() throws Exception {
+ String invalidDirectory = "+<=>*";
+ String fileName = "output" + File.separator + "testBackupDirectory.log";
+ String backupFileName = BackupDirectory.createBackupFileName(invalidDirectory, fileName);
+ assertFalse(new File(invalidDirectory).exists());
+ assertEquals(fileName, backupFileName);
+
+ assertFalse(new File(backupFileName).exists());
+ assertFalse(new File(fileName).exists());
+ }
+
+ public void testBackupDirectoryIsAFileThatExists() throws Exception {
+ String backupDirectory = "output" + File.separator + "testBackupDirectory.log";
+ String fileName = backupDirectory;
+ assertTrue(new File(fileName).createNewFile());
+
+ String backupFileName = BackupDirectory.createBackupFileName(backupDirectory, fileName);
+ assertTrue(new File(backupDirectory).exists());
+ assertEquals(fileName, backupFileName);
+
+ assertTrue(new File(backupFileName).exists());
+ }
+}
Index: tests/src/java/org/apache/log4j/RFATestCase.java
===================================================================
--- tests/src/java/org/apache/log4j/RFATestCase.java (revision 544346)
+++ tests/src/java/org/apache/log4j/RFATestCase.java (working copy)
@@ -233,5 +233,63 @@
}
}
+ /**
+ * Test basic rolling functionality with backup directory using property file configuration.
+ */
+ public void testBackupDirectoryFromConfigFile() throws Exception {
+ Logger logger = Logger.getLogger(RFATestCase.class);
+ PropertyConfigurator.configure("input/RFA2.properties");
+ // Write exactly 10 bytes with each log
+ for (int i = 0; i < 25; i++) {
+ if (i < 10) {
+ logger.debug("Hello---" + i);
+ } else if (i < 100) {
+ logger.debug("Hello--" + i);
+ }
+ }
+
+ assertTrue(new File("output/RFA-testBackup.log").exists());
+ assertFalse(new File("output/RFA-testBackup.log.1").exists());
+ assertTrue(new File("output/archive/RFA-testBackup.log.1").exists());
+ }
+
+ /**
+ * Test basic rolling functionality with a backup directory using API configuration.
+ */
+ public void testBackupDirectoryThroughAPI() throws Exception {
+ Logger logger = Logger.getLogger(RFATestCase.class);
+ Logger root = Logger.getRootLogger();
+ PatternLayout layout = new PatternLayout("%m\n");
+ org.apache.log4j.RollingFileAppender rfa =
+ new org.apache.log4j.RollingFileAppender();
+ rfa.setName("ROLLING");
+ rfa.setLayout(layout);
+ rfa.setAppend(false);
+ rfa.setMaxBackupIndex(3);
+ rfa.setMaximumFileSize(100);
+ rfa.setFile("output/RFA-testBackup2.log");
+ rfa.setBackupDirectory("output/archive2");
+ rfa.activateOptions();
+ root.addAppender(rfa);
+
+ // Write exactly 10 bytes with each log
+ for (int i = 0; i < 55; i++) {
+ if (i < 10) {
+ logger.debug("Hello---" + i);
+ } else if (i < 100) {
+ logger.debug("Hello--" + i);
+ }
+ }
+
+ assertTrue(new File("output/RFA-testBackup2.log").exists());
+ assertFalse(new File("output/RFA-testBackup2.log.1").exists());
+ assertTrue(new File("output/archive2/RFA-testBackup2.log.1").exists());
+ assertFalse(new File("output/RFA-testBackup2.log.2").exists());
+ assertTrue(new File("output/archive2/RFA-testBackup2.log.2").exists());
+ assertFalse(new File("output/RFA-testBackup2.log.3").exists());
+ assertTrue(new File("output/archive2/RFA-testBackup2.log.3").exists());
+ assertFalse(new File("output/RFA-testBackup2.log.4").exists());
+ assertFalse(new File("output/archive2/RFA-testBackup2.log.4").exists());
+ }
}