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
