001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 018package org.apache.logging.log4j.core.appender.rolling.action; 019 020import java.io.IOException; 021import java.nio.file.FileVisitor; 022import java.nio.file.Files; 023import java.nio.file.Path; 024import java.util.List; 025import java.util.Objects; 026 027import org.apache.logging.log4j.core.config.Configuration; 028import org.apache.logging.log4j.core.config.plugins.Plugin; 029import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 030import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 031import org.apache.logging.log4j.core.config.plugins.PluginElement; 032import org.apache.logging.log4j.core.config.plugins.PluginFactory; 033import org.apache.logging.log4j.core.lookup.StrSubstitutor; 034 035/** 036 * Rollover or scheduled action for deleting old log files that are accepted by the specified PathFilters. 037 */ 038@Plugin(name = "Delete", category = "Core", printObject = true) 039public class DeleteAction extends AbstractPathAction { 040 041 private final PathSorter pathSorter; 042 private final boolean testMode; 043 private final ScriptCondition scriptCondition; 044 045 /** 046 * Creates a new DeleteAction that starts scanning for files to delete from the specified base path. 047 * 048 * @param basePath base path from where to start scanning for files to delete. 049 * @param followSymbolicLinks whether to follow symbolic links. Default is false. 050 * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0 051 * means that only the starting file is visited, unless denied by the security manager. A value of 052 * MAX_VALUE may be used to indicate that all levels should be visited. 053 * @param testMode if true, files are not deleted but instead a message is printed to the <a 054 * href="http://logging.apache.org/log4j/2.x/manual/configuration.html#StatusMessages">status logger</a> 055 * at INFO level. Users can use this to do a dry run to test if their configuration works as expected. 056 * @param sorter sorts 057 * @param pathConditions an array of path filters (if more than one, they all need to accept a path before it is 058 * deleted). 059 * @param scriptCondition 060 */ 061 DeleteAction(final String basePath, final boolean followSymbolicLinks, final int maxDepth, final boolean testMode, 062 final PathSorter sorter, final PathCondition[] pathConditions, final ScriptCondition scriptCondition, 063 final StrSubstitutor subst) { 064 super(basePath, followSymbolicLinks, maxDepth, pathConditions, subst); 065 this.testMode = testMode; 066 this.pathSorter = Objects.requireNonNull(sorter, "sorter"); 067 this.scriptCondition = scriptCondition; 068 if (scriptCondition == null && (pathConditions == null || pathConditions.length == 0)) { 069 LOGGER.error("Missing Delete conditions: unconditional Delete not supported"); 070 throw new IllegalArgumentException("Unconditional Delete not supported"); 071 } 072 } 073 074 /* 075 * (non-Javadoc) 076 * 077 * @see org.apache.logging.log4j.core.appender.rolling.action.AbstractPathAction#execute() 078 */ 079 @Override 080 public boolean execute() throws IOException { 081 return scriptCondition != null ? executeScript() : super.execute(); 082 } 083 084 private boolean executeScript() throws IOException { 085 final List<PathWithAttributes> selectedForDeletion = callScript(); 086 if (selectedForDeletion == null) { 087 LOGGER.trace("Script returned null list (no files to delete)"); 088 return true; 089 } 090 deleteSelectedFiles(selectedForDeletion); 091 return true; 092 } 093 094 private List<PathWithAttributes> callScript() throws IOException { 095 final List<PathWithAttributes> sortedPaths = getSortedPaths(); 096 trace("Sorted paths:", sortedPaths); 097 final List<PathWithAttributes> result = scriptCondition.selectFilesToDelete(getBasePath(), sortedPaths); 098 return result; 099 } 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}