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