/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.log4j.rolling; import java.io.File; import java.util.ArrayList; import java.util.List; import org.apache.log4j.pattern.PatternConverter; import org.apache.log4j.rolling.helper.Action; import org.apache.log4j.rolling.helper.FileRenameAction; import org.apache.log4j.rolling.helper.GZCompressAction; import org.apache.log4j.rolling.helper.ZipCompressAction; import org.apache.log4j.helpers.LogLog; /** * When rolling over, <code>FixedWindowRollingPolicy</code> renames files * according to a fixed window algorithm as described below. * * <p>The <b>ActiveFileName</b> property, which is required, represents the name * of the file where current logging output will be written. * The <b>FileNamePattern</b> option represents the file name pattern for the * archived (rolled over) log files. If present, the <b>FileNamePattern</b> * option must include an integer token, that is the string "%i" somewhere * within the pattern. * * <p>Let <em>max</em> and <em>min</em> represent the values of respectively * the <b>MaxIndex</b> and <b>MinIndex</b> options. Let "foo.log" be the value * of the <b>ActiveFile</b> option and "foo.%i.log" the value of * <b>FileNamePattern</b>. Then, when rolling over, the file * <code>foo.<em>max</em>.log</code> will be deleted, the file * <code>foo.<em>max-1</em>.log</code> will be renamed as * <code>foo.<em>max</em>.log</code>, the file <code>foo.<em>max-2</em>.log</code> * renamed as <code>foo.<em>max-1</em>.log</code>, and so on, * the file <code>foo.<em>min+1</em>.log</code> renamed as * <code>foo.<em>min+2</em>.log</code>. Lastly, the active file <code>foo.log</code> * will be renamed as <code>foo.<em>min</em>.log</code> and a new active file name * <code>foo.log</code> will be created. * * <p>Given that this rollover algorithm requires as many file renaming * operations as the window size, large window sizes are discouraged. The * current implementation will automatically reduce the window size to 12 when * larger values are specified by the user. * * * @author Ceki Gülcü * */ public final class FixedWindowRollingPolicy extends RollingPolicyBase { /** * It's almost always a bad idea to have a large window size, say over 12. */ private static final int MAX_WINDOW_SIZE = 12; /** * Index for oldest retained log file. */ private int maxIndex; /** * Index for most recent log file. */ private int minIndex; /** * if true, then an explicit name for the active file was * specified using RollingFileAppender.file or the * redundent RollingPolicyBase.setActiveFile */ private boolean explicitActiveFile; /** * Constructs a new instance. */ public FixedWindowRollingPolicy() { minIndex = 1; maxIndex = 7; } /** * {@inheritDoc} */ public void activateOptions() { super.activateOptions(); if (maxIndex < minIndex) { LogLog.warn( "MaxIndex (" + maxIndex + ") cannot be smaller than MinIndex (" + minIndex + ")."); LogLog.warn("Setting maxIndex to equal minIndex."); maxIndex = minIndex; } if ((maxIndex - minIndex) > MAX_WINDOW_SIZE) { LogLog.warn("Large window sizes are not allowed."); maxIndex = minIndex + MAX_WINDOW_SIZE; LogLog.warn("MaxIndex reduced to " + String.valueOf(maxIndex) + "."); } PatternConverter itc = getIntegerPatternConverter(); if (itc == null) { throw new IllegalStateException( "FileNamePattern [" + getFileNamePattern() + "] does not contain a valid integer format specifier"); } } /** * {@inheritDoc} */ public RolloverDescription initialize( final String file, final boolean append) { String newActiveFile = file; explicitActiveFile = false; if (activeFileName != null) { explicitActiveFile = true; newActiveFile = activeFileName; } if (file != null) { explicitActiveFile = true; newActiveFile = file; } if (!explicitActiveFile) { StringBuffer buf = new StringBuffer(); formatFileName(new Integer(minIndex), buf); newActiveFile = buf.toString(); } return new RolloverDescriptionImpl(newActiveFile, append, null, null); } /** * {@inheritDoc} */ public RolloverDescription rollover(final String currentFileName) { if (maxIndex >= 0) { int purgeStart = minIndex; if (!explicitActiveFile) { purgeStart++; } if (!purge(purgeStart, maxIndex)) { return null; } StringBuffer buf = new StringBuffer(); formatFileName(new Integer(purgeStart), buf); String renameTo = buf.toString(); String compressedName = renameTo; Action compressAction = null; if (renameTo.endsWith(".gz")) { renameTo = renameTo.substring(0, renameTo.length() - 3); compressAction = new GZCompressAction( new File(renameTo), new File(compressedName), true); } else if (renameTo.endsWith(".zip")) { renameTo = renameTo.substring(0, renameTo.length() - 4); compressAction = new ZipCompressAction( new File(renameTo), new File(compressedName), true); } FileRenameAction renameAction = new FileRenameAction( new File(currentFileName), new File(renameTo), false); return new RolloverDescriptionImpl( currentFileName, false, renameAction, compressAction); } return null; } /** * Get index of oldest log file to be retained. * @return index of oldest log file. */ public int getMaxIndex() { return maxIndex; } /** * Get index of most recent log file. * @return index of oldest log file. */ public int getMinIndex() { return minIndex; } /** * Set index of oldest log file to be retained. * @param maxIndex index of oldest log file to be retained. */ public void setMaxIndex(int maxIndex) { this.maxIndex = maxIndex; } /** * Set index of most recent log file. * @param minIndex Index of most recent log file. */ public void setMinIndex(int minIndex) { this.minIndex = minIndex; } /** * Purge and rename old log files in preparation for rollover * @param lowIndex low index * @param highIndex high index. Log file associated with high * index will be deleted if needed. * @return true if purge was successful and rollover should be attempted. */ private boolean purge(final int lowIndex, final int highIndex) { int suffixLength = 0; List renames = new ArrayList(); StringBuffer buf = new StringBuffer(); formatFileName(new Integer(lowIndex), buf); String lowFilename = buf.toString(); if (lowFilename.endsWith(".gz")) { suffixLength = 3; } else if (lowFilename.endsWith(".zip")) { suffixLength = 4; } for (int i = lowIndex; i <= highIndex; i++) { File toRename = new File(lowFilename); boolean isBase = false; if (suffixLength > 0) { File toRenameBase = new File( lowFilename.substring(0, lowFilename.length() - suffixLength)); if (toRename.exists()) { if (toRenameBase.exists()) { toRenameBase.delete(); } } else { toRename = toRenameBase; isBase = true; } } if (toRename.exists()) { // // if at upper index then // attempt to delete last file // if that fails then abandon purge if (i == highIndex) { if (!toRename.delete()) { return false; } break; } // // if intermediate index // add a rename action to the list buf.setLength(0); formatFileName(new Integer(i + 1), buf); String highFilename = buf.toString(); String renameTo = highFilename; if (isBase) { renameTo = highFilename.substring(0, highFilename.length() - suffixLength); } renames.add(new FileRenameAction(toRename, new File(renameTo), true)); lowFilename = highFilename; } else { break; } } // // work renames backwards // for (int i = renames.size() - 1; i >= 0; i--) { Action action = (Action) renames.get(i); try { if (!action.execute()) { return false; } } catch (Exception ex) { LogLog.warn("Exception during purge in RollingFileAppender", ex); return false; } } return true; } }