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