LOG4J2-435 added support for a TestMode parameter to allow testing
without accidentally deleting the wrong files

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/11e9a27e
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/11e9a27e
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/11e9a27e

Branch: refs/heads/master
Commit: 11e9a27eb9ee13c6c9a7b0e3203c88e07a2881ab
Parents: e9875dc
Author: rpopma <[email protected]>
Authored: Sun Nov 29 00:13:29 2015 +0900
Committer: rpopma <[email protected]>
Committed: Sun Nov 29 00:13:29 2015 +0900

----------------------------------------------------------------------
 .../appender/rolling/action/DeleteAction.java   | 30 ++++++++++---
 .../rolling/action/DeletingVisitor.java         | 23 +++++++++-
 .../rolling/action/DeleteActionTest.java        | 46 ++++++++++++++------
 .../rolling/action/DeletingVisitorTest.java     | 27 +++++++++---
 src/site/xdoc/manual/appenders.xml              | 10 +++++
 5 files changed, 108 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/11e9a27e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java
index 4cb6b9f..7010766 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java
@@ -37,7 +37,8 @@ import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 @Plugin(name = "Delete", category = "Core", printObject = true)
 public class DeleteAction extends AbstractPathAction {
 
-    private PathSorter pathSorter;
+    private final PathSorter pathSorter;
+    private final boolean testMode;
 
     /**
      * Creates a new DeleteAction that starts scanning for files to delete 
from the specified base path.
@@ -47,13 +48,17 @@ public class DeleteAction extends AbstractPathAction {
      * @param maxDepth The maxDepth parameter is the maximum number of levels 
of directories to visit. A value of 0
      *            means that only the starting file is visited, unless denied 
by the security manager. A value of
      *            MAX_VALUE may be used to indicate that all levels should be 
visited.
+     * @param testMode if true, files are not deleted but instead a message is 
printed to the <a
+     *            
href="http://logging.apache.org/log4j/2.x/manual/configuration.html#StatusMessages";>status
 logger</a>
+     *            at INFO level. Users can use this to do a dry run to test if 
their configuration works as expected.
      * @param sorter sorts
      * @param pathConditions an array of path filters (if more than one, they 
all need to accept a path before it is
      *            deleted).
      */
-    DeleteAction(final String basePath, final boolean followSymbolicLinks, 
final int maxDepth, final PathSorter sorter,
-            final PathCondition[] pathConditions, final StrSubstitutor subst) {
+    DeleteAction(final String basePath, final boolean followSymbolicLinks, 
final int maxDepth, final boolean testMode,
+            final PathSorter sorter, final PathCondition[] pathConditions, 
final StrSubstitutor subst) {
         super(basePath, followSymbolicLinks, maxDepth, pathConditions, subst);
+        this.testMode = testMode;
         this.pathSorter = Objects.requireNonNull(sorter, "sorter");
         if (pathConditions == null || pathConditions.length == 0) {
             LOGGER.error("Missing Delete conditions: unconditional Delete not 
supported");
@@ -94,9 +99,18 @@ public class DeleteAction extends AbstractPathAction {
         return sortedPaths;
     }
 
+    /**
+     * Returns {@code true} if files are not deleted even when all conditions 
accept a path, {@code false} otherwise.
+     * 
+     * @return {@code true} if files are not deleted even when all conditions 
accept a path, {@code false} otherwise
+     */
+    public boolean isTestMode() {
+        return testMode;
+    }
+
     @Override
     protected FileVisitor<Path> createFileVisitor(final Path visitorBaseDir, 
final List<PathCondition> conditions) {
-        return new DeletingVisitor(visitorBaseDir, conditions);
+        return new DeletingVisitor(visitorBaseDir, conditions, testMode);
     }
 
     /**
@@ -107,6 +121,10 @@ public class DeleteAction extends AbstractPathAction {
      * @param maxDepth The maxDepth parameter is the maximum number of levels 
of directories to visit. A value of 0
      *            means that only the starting file is visited, unless denied 
by the security manager. A value of
      *            MAX_VALUE may be used to indicate that all levels should be 
visited.
+     * @param testMode if true, files are not deleted but instead a message is 
printed to the <a
+     *            
href="http://logging.apache.org/log4j/2.x/manual/configuration.html#StatusMessages";>status
 logger</a>
+     *            at INFO level. Users can use this to do a dry run to test if 
their configuration works as expected.
+     *            Default is false.
      * @param PathSorter a plugin implementing the {@link PathSorter} interface
      * @param PathConditions an array of path conditions (if more than one, 
they all need to accept a path before it is
      *            deleted).
@@ -119,11 +137,13 @@ public class DeleteAction extends AbstractPathAction {
             @PluginAttribute("basePath") final String basePath, //
             @PluginAttribute(value = "followLinks", defaultBoolean = false) 
final boolean followLinks,
             @PluginAttribute(value = "maxDepth", defaultInt = 1) final int 
maxDepth,
+            @PluginAttribute(value = "testMode", defaultBoolean = false) final 
boolean testMode,
             @PluginElement("PathSorter") final PathSorter sorterParameter,
             @PluginElement("PathConditions") final PathCondition[] 
pathConditions,
             @PluginConfiguration final Configuration config) {
             // @formatter:on
         final PathSorter sorter = sorterParameter == null ? new 
PathSortByModificationTime(true) : sorterParameter;
-        return new DeleteAction(basePath, followLinks, maxDepth, sorter, 
pathConditions, config.getStrSubstitutor());
+        return new DeleteAction(basePath, followLinks, maxDepth, testMode, 
sorter, pathConditions,
+                config.getStrSubstitutor());
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/11e9a27e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
index fc42c05..dd94589 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
@@ -36,6 +36,7 @@ public class DeletingVisitor extends SimpleFileVisitor<Path> {
     private static final Logger LOGGER = StatusLogger.getLogger();
 
     private final Path basePath;
+    private final boolean testMode;
     private final List<? extends PathCondition> pathConditions;
 
     /**
@@ -43,8 +44,13 @@ public class DeletingVisitor extends SimpleFileVisitor<Path> 
{
      * 
      * @param basePath used to relativize paths
      * @param pathConditions objects that need to confirm whether a file can 
be deleted
+     * @param testMode if true, files are not deleted but instead a message is 
printed to the <a
+     *            
href="http://logging.apache.org/log4j/2.x/manual/configuration.html#StatusMessages";>status
 logger</a>
+     *            at INFO level. Users can use this to do a dry run to test if 
their configuration works as expected.
      */
-    public DeletingVisitor(final Path basePath, final List<? extends 
PathCondition> pathConditions) {
+    public DeletingVisitor(final Path basePath, final List<? extends 
PathCondition> pathConditions,
+            final boolean testMode) {
+        this.testMode = testMode;
         this.basePath = Objects.requireNonNull(basePath, "basePath");
         this.pathConditions = Objects.requireNonNull(pathConditions, 
"pathConditions");
         for (final PathCondition condition : pathConditions) {
@@ -61,7 +67,11 @@ public class DeletingVisitor extends SimpleFileVisitor<Path> 
{
                 return FileVisitResult.CONTINUE;
             }
         }
-        delete(file);
+        if (isTestMode()) {
+            LOGGER.info("Deleting {} (TEST MODE: file not actually deleted)", 
file);
+        } else {
+            delete(file);
+        }
         return FileVisitResult.CONTINUE;
     }
 
@@ -75,4 +85,13 @@ public class DeletingVisitor extends SimpleFileVisitor<Path> 
{
         LOGGER.trace("Deleting {}", file);
         Files.delete(file);
     }
+
+    /**
+     * Returns {@code true} if files are not deleted even when all conditions 
accept a path, {@code false} otherwise.
+     * 
+     * @return {@code true} if files are not deleted even when all conditions 
accept a path, {@code false} otherwise
+     */
+    public boolean isTestMode() {
+        return testMode;
+    }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/11e9a27e/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java
index 8793732..4f0b36e 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java
@@ -39,22 +39,24 @@ import static org.junit.Assert.*;
  */
 public class DeleteActionTest {
 
-    private static DeleteAction createAnyFilter(String path, boolean 
followLinks, int maxDepth) {
+    private static DeleteAction createAnyFilter(String path, boolean 
followLinks, int maxDepth, boolean testMode) {
         PathCondition[] pathFilters = {new FixedCondition(true)};
-        return create(path, followLinks, maxDepth, pathFilters);
+        return create(path, followLinks, maxDepth, testMode, pathFilters);
     }
 
-    private static DeleteAction create(String path, boolean followLinks, int 
maxDepth, PathCondition[] filters) {
+    private static DeleteAction create(String path, boolean followLinks, int 
maxDepth, boolean testMode,
+            PathCondition[] filters) {
         Configuration config = new BasicConfigurationFactory().new 
BasicConfiguration();
-        DeleteAction delete = DeleteAction.createDeleteAction(path, 
followLinks, maxDepth, null, filters, config);
+        DeleteAction delete = DeleteAction.createDeleteAction(path, 
followLinks, maxDepth, testMode, null, filters,
+                config);
         return delete;
     }
 
     @Test
     public void testGetBasePathResolvesLookups() {
-        DeleteAction delete = createAnyFilter("${sys:user.home}/a/b/c", false, 
1);
-        
-        Path actual = delete.getBasePath();        
+        DeleteAction delete = createAnyFilter("${sys:user.home}/a/b/c", false, 
1, false);
+
+        Path actual = delete.getBasePath();
         String expected = System.getProperty("user.home") + "/a/b/c";
 
         assertEquals(FileSystems.getDefault().getPath(expected), actual);
@@ -62,40 +64,56 @@ public class DeleteActionTest {
 
     @Test
     public void testGetBasePathStringReturnsOriginalParam() {
-        DeleteAction delete = createAnyFilter("${sys:user.home}/a/b/c", false, 
1);
+        DeleteAction delete = createAnyFilter("${sys:user.home}/a/b/c", false, 
1, false);
         assertEquals("${sys:user.home}/a/b/c", delete.getBasePathString());
     }
 
     @Test
     public void testGetMaxDepthReturnsConstructorValue() {
-        DeleteAction delete = createAnyFilter("any", false, 23);
+        DeleteAction delete = createAnyFilter("any", false, 23, false);
         assertEquals(23, delete.getMaxDepth());
     }
 
     @Test
     public void testGetOptionsReturnsEmptySetIfNotFollowingLinks() {
-        DeleteAction delete = createAnyFilter("any", false, 0);
+        DeleteAction delete = createAnyFilter("any", false, 0, false);
         assertEquals(Collections.emptySet(), delete.getOptions());
     }
 
     @Test
     public void testGetOptionsReturnsSetWithFollowLinksIfFollowingLinks() {
-        DeleteAction delete = createAnyFilter("any", true, 0);
+        DeleteAction delete = createAnyFilter("any", true, 0, false);
         assertEquals(EnumSet.of(FileVisitOption.FOLLOW_LINKS), 
delete.getOptions());
     }
 
     @Test
     public void testGetFiltersReturnsConstructorValue() {
         PathCondition[] filters = {new FixedCondition(true), new 
FixedCondition(false)};
-        
-        DeleteAction delete = create("any", true, 0, filters);
+
+        DeleteAction delete = create("any", true, 0, false, filters);
         assertEquals(Arrays.asList(filters), delete.getPathConditions());
     }
 
     @Test
     public void testCreateFileVisitorReturnsDeletingVisitor() {
-        DeleteAction delete = createAnyFilter("any", true, 0);
+        DeleteAction delete = createAnyFilter("any", true, 0, false);
+        FileVisitor<Path> visitor = 
delete.createFileVisitor(delete.getBasePath(), delete.getPathConditions());
+        assertTrue(visitor instanceof DeletingVisitor);
+    }
+
+    @Test
+    public void testCreateFileVisitorTestModeIsActionTestMode() {
+        DeleteAction delete = createAnyFilter("any", true, 0, false);
+        assertFalse(delete.isTestMode());
         FileVisitor<Path> visitor = 
delete.createFileVisitor(delete.getBasePath(), delete.getPathConditions());
         assertTrue(visitor instanceof DeletingVisitor);
+        assertFalse(((DeletingVisitor) visitor).isTestMode());
+
+        DeleteAction deleteTestMode = createAnyFilter("any", true, 0, true);
+        assertTrue(deleteTestMode.isTestMode());
+        FileVisitor<Path> testVisitor = 
deleteTestMode.createFileVisitor(delete.getBasePath(),
+                delete.getPathConditions());
+        assertTrue(testVisitor instanceof DeletingVisitor);
+        assertTrue(((DeletingVisitor) testVisitor).isTestMode());
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/11e9a27e/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java
index d4053d1..2b3eb0c 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java
@@ -40,8 +40,9 @@ public class DeletingVisitorTest {
     static class DeletingVisitorHelper extends DeletingVisitor {
         List<Path> deleted = new ArrayList<Path>();
 
-        public DeletingVisitorHelper(final Path basePath, final List<? extends 
PathCondition> pathFilters) {
-            super(basePath, pathFilters);
+        public DeletingVisitorHelper(final Path basePath, final List<? extends 
PathCondition> pathFilters,
+                final boolean testMode) {
+            super(basePath, pathFilters, testMode);
         }
 
         @Override
@@ -54,7 +55,7 @@ public class DeletingVisitorTest {
     public void testAcceptedFilesAreDeleted() throws IOException {
         Path base = Paths.get("/a/b/c");
         final FixedCondition ACCEPT_ALL = new FixedCondition(true);
-        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
Arrays.asList(ACCEPT_ALL));
+        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
Arrays.asList(ACCEPT_ALL), false);
 
         Path any = Paths.get("/a/b/c/any");
         visitor.visitFile(any, null);
@@ -65,7 +66,7 @@ public class DeletingVisitorTest {
     public void testRejectedFilesAreNotDeleted() throws IOException {
         Path base = Paths.get("/a/b/c");
         final FixedCondition REJECT_ALL = new FixedCondition(false);
-        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
Arrays.asList(REJECT_ALL));
+        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
Arrays.asList(REJECT_ALL), false);
 
         Path any = Paths.get("/a/b/c/any");
         visitor.visitFile(any, null);
@@ -78,7 +79,7 @@ public class DeletingVisitorTest {
         final FixedCondition ACCEPT_ALL = new FixedCondition(true);
         final FixedCondition REJECT_ALL = new FixedCondition(false);
         List<? extends PathCondition> filters = Arrays.asList(ACCEPT_ALL, 
ACCEPT_ALL, REJECT_ALL);
-        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
filters);
+        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
filters, false);
 
         Path any = Paths.get("/a/b/c/any");
         visitor.visitFile(any, null);
@@ -90,7 +91,7 @@ public class DeletingVisitorTest {
         Path base = Paths.get("/a/b/c");
         final FixedCondition ACCEPT_ALL = new FixedCondition(true);
         List<? extends PathCondition> filters = Arrays.asList(ACCEPT_ALL, 
ACCEPT_ALL, ACCEPT_ALL);
-        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
filters);
+        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
filters, false);
 
         Path any = Paths.get("/a/b/c/any");
         visitor.visitFile(any, null);
@@ -98,6 +99,18 @@ public class DeletingVisitorTest {
     }
 
     @Test
+    public void testInTestModeFileIsNotDeletedEvenIfAllFiltersAccept() throws 
IOException {
+        Path base = Paths.get("/a/b/c");
+        final FixedCondition ACCEPT_ALL = new FixedCondition(true);
+        List<? extends PathCondition> filters = Arrays.asList(ACCEPT_ALL, 
ACCEPT_ALL, ACCEPT_ALL);
+        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
filters, true);
+
+        Path any = Paths.get("/a/b/c/any");
+        visitor.visitFile(any, null);
+        assertFalse(visitor.deleted.contains(any));
+    }
+
+    @Test
     public void testVisitFileRelativizesAgainstBase() throws IOException {
 
         PathCondition filter = new PathCondition() {
@@ -114,7 +127,7 @@ public class DeletingVisitorTest {
             }
         };
         Path base = Paths.get("/a/b/c");
-        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
Arrays.asList(filter));
+        DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, 
Arrays.asList(filter), false);
 
         Path child = Paths.get("/a/b/c/relative");
         visitor.visitFile(child, null);

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/11e9a27e/src/site/xdoc/manual/appenders.xml
----------------------------------------------------------------------
diff --git a/src/site/xdoc/manual/appenders.xml 
b/src/site/xdoc/manual/appenders.xml
index 97ef344..b376cbd 100644
--- a/src/site/xdoc/manual/appenders.xml
+++ b/src/site/xdoc/manual/appenders.xml
@@ -2478,7 +2478,10 @@ public class JpaLogEntity extends 
AbstractLogEventWrapperEntity {
             <tt>max</tt> attribute.
             The Delete action lets users configure one or more conditions that 
select the files to delete
             relative to a base directory.
+          </p>
+          <p>
             Note that it is possible to delete any file, not just rolled over 
log files, so use this action with care!
+            With the <tt>testMode</tt> parameter you can test your 
configuration without accidentally deleting the wrong files.
           </p>
           <table>
             <caption align="top">Delete Parameters</caption>
@@ -2507,6 +2510,13 @@ public class JpaLogEntity extends 
AbstractLogEventWrapperEntity {
               <td>Whether to follow symbolic links. Default is false.</td>
             </tr>
             <tr>
+              <td>testMode</td>
+              <td>boolean</td>
+              <td>If true, files are not deleted but instead a message is 
printed to the <a
+                 href="configuration.html#StatusMessages">status logger</a> at 
INFO level.
+                 Use this to do a dry run to test if the configuration works 
as expected. Default is false.</td>
+            </tr>
+            <tr>
               <td>pathSorter</td>
               <td>PathSorter</td>
               <td>A plugin implementing the 

Reply via email to