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 package org.apache.logging.log4j.core.appender.rolling; 018 019 import org.apache.logging.log4j.Logger; 020 import org.apache.logging.log4j.core.appender.rolling.helper.Action; 021 import org.apache.logging.log4j.core.appender.rolling.helper.FileRenameAction; 022 import org.apache.logging.log4j.core.appender.rolling.helper.GZCompressAction; 023 import org.apache.logging.log4j.core.appender.rolling.helper.ZipCompressAction; 024 import org.apache.logging.log4j.core.config.Configuration; 025 import org.apache.logging.log4j.core.config.plugins.Plugin; 026 import org.apache.logging.log4j.core.config.plugins.PluginAttr; 027 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 028 import org.apache.logging.log4j.core.config.plugins.PluginFactory; 029 import org.apache.logging.log4j.core.lookup.StrSubstitutor; 030 import org.apache.logging.log4j.status.StatusLogger; 031 032 import java.io.File; 033 import java.util.ArrayList; 034 import java.util.List; 035 036 /** 037 * When rolling over, <code>FixedWindowRollingPolicy</code> renames files 038 * according to a fixed window algorithm as described below. 039 * <p/> 040 * <p>The <b>ActiveFileName</b> property, which is required, represents the name 041 * of the file where current logging output will be written. 042 * The <b>FileNamePattern</b> option represents the file name pattern for the 043 * archived (rolled over) log files. If present, the <b>FileNamePattern</b> 044 * option must include an integer token, that is the string "%i" somewhere 045 * within the pattern. 046 * <p/> 047 * <p>Let <em>max</em> and <em>min</em> represent the values of respectively 048 * the <b>MaxIndex</b> and <b>MinIndex</b> options. Let "foo.log" be the value 049 * of the <b>ActiveFile</b> option and "foo.%i.log" the value of 050 * <b>FileNamePattern</b>. Then, when rolling over, the file 051 * <code>foo.<em>max</em>.log</code> will be deleted, the file 052 * <code>foo.<em>max-1</em>.log</code> will be renamed as 053 * <code>foo.<em>max</em>.log</code>, the file <code>foo.<em>max-2</em>.log</code> 054 * renamed as <code>foo.<em>max-1</em>.log</code>, and so on, 055 * the file <code>foo.<em>min+1</em>.log</code> renamed as 056 * <code>foo.<em>min+2</em>.log</code>. Lastly, the active file <code>foo.log</code> 057 * will be renamed as <code>foo.<em>min</em>.log</code> and a new active file name 058 * <code>foo.log</code> will be created. 059 * <p/> 060 * <p>Given that this rollover algorithm requires as many file renaming 061 * operations as the window size, large window sizes are discouraged. The 062 * current implementation will automatically reduce the window size to 12 when 063 * larger values are specified by the user. 064 */ 065 @Plugin(name = "DefaultRolloverStrategy", type = "Core", printObject = true) 066 public class DefaultRolloverStrategy implements RolloverStrategy { 067 /** 068 * Allow subclasses access to the status logger without creating another instance. 069 */ 070 protected static final Logger LOGGER = StatusLogger.getLogger(); 071 072 private static final int MIN_WINDOW_SIZE = 1; 073 private static final int DEFAULT_WINDOW_SIZE = 7; 074 075 /** 076 * Index for oldest retained log file. 077 */ 078 private final int maxIndex; 079 080 /** 081 * Index for most recent log file. 082 */ 083 private final int minIndex; 084 085 private final StrSubstitutor subst; 086 087 /** 088 * Constructs a new instance. 089 * @param min The minimum index. 090 * @param max The maximum index. 091 */ 092 protected DefaultRolloverStrategy(int min, int max, StrSubstitutor subst) { 093 minIndex = min; 094 maxIndex = max; 095 this.subst = subst; 096 } 097 098 /** 099 * Perform the rollover. 100 * @param manager The RollingFileManager name for current active log file. 101 * @return A RolloverDescription. 102 * @throws SecurityException if an error occurs. 103 */ 104 public RolloverDescription rollover(RollingFileManager manager) throws SecurityException { 105 if (maxIndex >= 0) { 106 int purgeStart = minIndex; 107 108 if (!purge(purgeStart, maxIndex, manager)) { 109 return null; 110 } 111 112 StringBuilder buf = new StringBuilder(); 113 manager.getProcessor().formatFileName(buf, purgeStart); 114 String currentFileName = manager.getFileName(); 115 116 String renameTo = subst.replace(buf); 117 String compressedName = renameTo; 118 Action compressAction = null; 119 120 if (renameTo.endsWith(".gz")) { 121 renameTo = renameTo.substring(0, renameTo.length() - 3); 122 compressAction = new GZCompressAction(new File(renameTo), new File(compressedName), true); 123 } else if (renameTo.endsWith(".zip")) { 124 renameTo = renameTo.substring(0, renameTo.length() - 4); 125 compressAction = new ZipCompressAction(new File(renameTo), new File(compressedName), true); 126 } 127 128 FileRenameAction renameAction = 129 new FileRenameAction(new File(currentFileName), new File(renameTo), false); 130 131 return new RolloverDescriptionImpl(currentFileName, false, renameAction, compressAction); 132 } 133 134 return null; 135 } 136 137 /** 138 * Purge and rename old log files in preparation for rollover 139 * 140 * @param lowIndex low index 141 * @param highIndex high index. Log file associated with high index will be deleted if needed. 142 * @param manager The RollingFileManager 143 * @return true if purge was successful and rollover should be attempted. 144 */ 145 private boolean purge(final int lowIndex, final int highIndex, RollingFileManager manager) { 146 int suffixLength = 0; 147 148 List<FileRenameAction> renames = new ArrayList<FileRenameAction>(); 149 StringBuilder buf = new StringBuilder(); 150 manager.getProcessor().formatFileName(buf, lowIndex); 151 152 String lowFilename = subst.replace(buf); 153 154 if (lowFilename.endsWith(".gz")) { 155 suffixLength = 3; 156 } else if (lowFilename.endsWith(".zip")) { 157 suffixLength = 4; 158 } 159 160 for (int i = lowIndex; i <= highIndex; i++) { 161 File toRename = new File(lowFilename); 162 boolean isBase = false; 163 164 if (suffixLength > 0) { 165 File toRenameBase = 166 new File(lowFilename.substring(0, lowFilename.length() - suffixLength)); 167 168 if (toRename.exists()) { 169 if (toRenameBase.exists()) { 170 toRenameBase.delete(); 171 } 172 } else { 173 toRename = toRenameBase; 174 isBase = true; 175 } 176 } 177 178 if (toRename.exists()) { 179 // 180 // if at upper index then 181 // attempt to delete last file 182 // if that fails then abandon purge 183 if (i == highIndex) { 184 if (!toRename.delete()) { 185 return false; 186 } 187 188 break; 189 } 190 191 // 192 // if intermediate index 193 // add a rename action to the list 194 buf.setLength(0); 195 manager.getProcessor().formatFileName(buf, i + 1); 196 197 String highFilename = subst.replace(buf); 198 String renameTo = highFilename; 199 200 if (isBase) { 201 renameTo = highFilename.substring(0, highFilename.length() - suffixLength); 202 } 203 204 renames.add(new FileRenameAction(toRename, new File(renameTo), true)); 205 lowFilename = highFilename; 206 } else { 207 break; 208 } 209 } 210 211 // 212 // work renames backwards 213 // 214 for (int i = renames.size() - 1; i >= 0; i--) { 215 Action action = renames.get(i); 216 217 try { 218 if (!action.execute()) { 219 return false; 220 } 221 } catch (Exception ex) { 222 LOGGER.warn("Exception during purge in RollingFileAppender", ex); 223 return false; 224 } 225 } 226 227 return true; 228 } 229 230 @Override 231 public String toString() { 232 return "DefaultRolloverStrategy(min=" + minIndex + ", max=" + maxIndex + ")"; 233 } 234 235 /** 236 * Create the DefaultRolloverStrategy. 237 * @param max The maximum number of files to keep. 238 * @param min The minimum number of files to keep. 239 * @param config The Configuration. 240 * @return A DefaultRolloverStrategy. 241 */ 242 @PluginFactory 243 public static DefaultRolloverStrategy createStrategy(@PluginAttr("max") String max, 244 @PluginAttr("min") String min, 245 @PluginConfiguration Configuration config) { 246 247 int minIndex; 248 if (min != null) { 249 minIndex = Integer.parseInt(min); 250 if (minIndex < 1) { 251 LOGGER.error("Minimum window size too small. Limited to " + MIN_WINDOW_SIZE); 252 minIndex = MIN_WINDOW_SIZE; 253 } 254 } else { 255 minIndex = MIN_WINDOW_SIZE; 256 } 257 int maxIndex; 258 if (max != null) { 259 maxIndex = Integer.parseInt(max); 260 if (maxIndex < minIndex) { 261 maxIndex = minIndex < DEFAULT_WINDOW_SIZE ? DEFAULT_WINDOW_SIZE : minIndex; 262 LOGGER.error("Maximum window size must be greater than the minimum windows size. Set to " + maxIndex); 263 } 264 } else { 265 maxIndex = DEFAULT_WINDOW_SIZE; 266 } 267 return new DefaultRolloverStrategy(minIndex, maxIndex, config.getSubst()); 268 } 269 270 }