View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  
18  package org.apache.logging.log4j.core.appender.rolling.action;
19  
20  import java.io.IOException;
21  import java.nio.file.FileVisitor;
22  import java.nio.file.Files;
23  import java.nio.file.Path;
24  import java.util.List;
25  import java.util.Objects;
26  
27  import org.apache.logging.log4j.core.Core;
28  import org.apache.logging.log4j.core.config.Configuration;
29  import org.apache.logging.log4j.core.config.plugins.Plugin;
30  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
31  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
32  import org.apache.logging.log4j.core.config.plugins.PluginElement;
33  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
34  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
35  
36  /**
37   * Rollover or scheduled action for deleting old log files that are accepted by the specified PathFilters.
38   */
39  @Plugin(name = "Delete", category = Core.CATEGORY_NAME, printObject = true)
40  public class DeleteAction extends AbstractPathAction {
41  
42      private final PathSorter pathSorter;
43      private final boolean testMode;
44      private final ScriptCondition scriptCondition;
45  
46      /**
47       * Creates a new DeleteAction that starts scanning for files to delete from the specified base path.
48       *
49       * @param basePath base path from where to start scanning for files to delete.
50       * @param followSymbolicLinks whether to follow symbolic links. Default is false.
51       * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0
52       *            means that only the starting file is visited, unless denied by the security manager. A value of
53       *            MAX_VALUE may be used to indicate that all levels should be visited.
54       * @param testMode if true, files are not deleted but instead a message is printed to the <a
55       *            href="http://logging.apache.org/log4j/2.x/manual/configuration.html#StatusMessages">status logger</a>
56       *            at INFO level. Users can use this to do a dry run to test if their configuration works as expected.
57       * @param sorter sorts
58       * @param pathConditions an array of path filters (if more than one, they all need to accept a path before it is
59       *            deleted).
60       * @param scriptCondition
61       */
62      DeleteAction(final String basePath, final boolean followSymbolicLinks, final int maxDepth, final boolean testMode,
63              final PathSorter sorter, final PathCondition[] pathConditions, final ScriptCondition scriptCondition,
64              final StrSubstitutor subst) {
65          super(basePath, followSymbolicLinks, maxDepth, pathConditions, subst);
66          this.testMode = testMode;
67          this.pathSorter = Objects.requireNonNull(sorter, "sorter");
68          this.scriptCondition = scriptCondition;
69          if (scriptCondition == null && (pathConditions == null || pathConditions.length == 0)) {
70              LOGGER.error("Missing Delete conditions: unconditional Delete not supported");
71              throw new IllegalArgumentException("Unconditional Delete not supported");
72          }
73      }
74  
75      /*
76       * (non-Javadoc)
77       *
78       * @see org.apache.logging.log4j.core.appender.rolling.action.AbstractPathAction#execute()
79       */
80      @Override
81      public boolean execute() throws IOException {
82          return scriptCondition != null ? executeScript() : super.execute();
83      }
84  
85      private boolean executeScript() throws IOException {
86          final List<PathWithAttributes> selectedForDeletion = callScript();
87          if (selectedForDeletion == null) {
88              LOGGER.trace("Script returned null list (no files to delete)");
89              return true;
90          }
91          deleteSelectedFiles(selectedForDeletion);
92          return true;
93      }
94  
95      private List<PathWithAttributes> callScript() throws IOException {
96          final List<PathWithAttributes> sortedPaths = getSortedPaths();
97          trace("Sorted paths:", sortedPaths);
98          final List<PathWithAttributes> result = scriptCondition.selectFilesToDelete(getBasePath(), sortedPaths);
99          return result;
100     }
101 
102     private void deleteSelectedFiles(final List<PathWithAttributes> selectedForDeletion) throws IOException {
103         trace("Paths the script selected for deletion:", selectedForDeletion);
104         for (final PathWithAttributes pathWithAttributes : selectedForDeletion) {
105             final Path path = pathWithAttributes == null ? null : pathWithAttributes.getPath();
106             if (isTestMode()) {
107                 LOGGER.info("Deleting {} (TEST MODE: file not actually deleted)", path);
108             } else {
109                 delete(path);
110             }
111         }
112     }
113 
114     /**
115      * Deletes the specified file.
116      *
117      * @param path the file to delete
118      * @throws IOException if a problem occurred deleting the file
119      */
120     protected void delete(final Path path) throws IOException {
121         LOGGER.trace("Deleting {}", path);
122         Files.deleteIfExists(path);
123     }
124 
125     /*
126      * (non-Javadoc)
127      *
128      * @see org.apache.logging.log4j.core.appender.rolling.action.AbstractPathAction#execute(FileVisitor)
129      */
130     @Override
131     public boolean execute(final FileVisitor<Path> visitor) throws IOException {
132         final List<PathWithAttributes> sortedPaths = getSortedPaths();
133         trace("Sorted paths:", sortedPaths);
134 
135         for (final PathWithAttributes element : sortedPaths) {
136             try {
137                 visitor.visitFile(element.getPath(), element.getAttributes());
138             } catch (final IOException ioex) {
139                 LOGGER.error("Error in post-rollover Delete when visiting {}", element.getPath(), ioex);
140                 visitor.visitFileFailed(element.getPath(), ioex);
141             }
142         }
143         // TODO return (visitor.success || ignoreProcessingFailure)
144         return true; // do not abort rollover even if processing failed
145     }
146 
147     private void trace(final String label, final List<PathWithAttributes> sortedPaths) {
148         LOGGER.trace(label);
149         for (final PathWithAttributes pathWithAttributes : sortedPaths) {
150             LOGGER.trace(pathWithAttributes);
151         }
152     }
153 
154     /**
155      * Returns a sorted list of all files up to maxDepth under the basePath.
156      *
157      * @return a sorted list of files
158      * @throws IOException
159      */
160     List<PathWithAttributes> getSortedPaths() throws IOException {
161         final SortingVisitor sort = new SortingVisitor(pathSorter);
162         super.execute(sort);
163         final List<PathWithAttributes> sortedPaths = sort.getSortedPaths();
164         return sortedPaths;
165     }
166 
167     /**
168      * Returns {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise.
169      *
170      * @return {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise
171      */
172     public boolean isTestMode() {
173         return testMode;
174     }
175 
176     @Override
177     protected FileVisitor<Path> createFileVisitor(final Path visitorBaseDir, final List<PathCondition> conditions) {
178         return new DeletingVisitor(visitorBaseDir, conditions, testMode);
179     }
180 
181     /**
182      * Create a DeleteAction.
183      *
184      * @param basePath base path from where to start scanning for files to delete.
185      * @param followLinks whether to follow symbolic links. Default is false.
186      * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0
187      *            means that only the starting file is visited, unless denied by the security manager. A value of
188      *            MAX_VALUE may be used to indicate that all levels should be visited.
189      * @param testMode if true, files are not deleted but instead a message is printed to the <a
190      *            href="http://logging.apache.org/log4j/2.x/manual/configuration.html#StatusMessages">status logger</a>
191      *            at INFO level. Users can use this to do a dry run to test if their configuration works as expected.
192      *            Default is false.
193      * @param PathSorter a plugin implementing the {@link PathSorter} interface
194      * @param PathConditions an array of path conditions (if more than one, they all need to accept a path before it is
195      *            deleted).
196      * @param config The Configuration.
197      * @return A DeleteAction.
198      */
199     @PluginFactory
200     public static DeleteAction createDeleteAction(
201             // @formatter:off
202             @PluginAttribute("basePath") final String basePath,
203             @PluginAttribute(value = "followLinks") final boolean followLinks,
204             @PluginAttribute(value = "maxDepth", defaultInt = 1) final int maxDepth,
205             @PluginAttribute(value = "testMode") final boolean testMode,
206             @PluginElement("PathSorter") final PathSorter sorterParameter,
207             @PluginElement("PathConditions") final PathCondition[] pathConditions,
208             @PluginElement("ScriptCondition") final ScriptCondition scriptCondition,
209             @PluginConfiguration final Configuration config) {
210             // @formatter:on
211         final PathSorter sorter = sorterParameter == null ? new PathSortByModificationTime(true) : sorterParameter;
212         return new DeleteAction(basePath, followLinks, maxDepth, testMode, sorter, pathConditions, scriptCondition,
213                 config.getStrSubstitutor());
214     }
215 }