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.log4j.rolling;
19  
20  import java.io.File;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.apache.log4j.pattern.PatternConverter;
25  import org.apache.log4j.rolling.helper.Action;
26  import org.apache.log4j.rolling.helper.FileRenameAction;
27  import org.apache.log4j.rolling.helper.GZCompressAction;
28  import org.apache.log4j.rolling.helper.ZipCompressAction;
29  import org.apache.log4j.helpers.LogLog;
30  
31  
32  /***
33   * When rolling over, <code>FixedWindowRollingPolicy</code> renames files
34   * according to a fixed window algorithm as described below.
35   *
36   * <p>The <b>ActiveFileName</b> property, which is required, represents the name
37   * of the file where current logging output will be written.
38   * The <b>FileNamePattern</b>  option represents the file name pattern for the
39   * archived (rolled over) log files. If present, the <b>FileNamePattern</b>
40   * option must include an integer token, that is the string "%i" somewhere
41   * within the pattern.
42   *
43   * <p>Let <em>max</em> and <em>min</em> represent the values of respectively
44   * the <b>MaxIndex</b> and <b>MinIndex</b> options. Let "foo.log" be the value
45   * of the <b>ActiveFile</b> option and "foo.%i.log" the value of
46   * <b>FileNamePattern</b>. Then, when rolling over, the file
47   * <code>foo.<em>max</em>.log</code> will be deleted, the file
48   * <code>foo.<em>max-1</em>.log</code> will be renamed as
49   * <code>foo.<em>max</em>.log</code>, the file <code>foo.<em>max-2</em>.log</code>
50   * renamed as <code>foo.<em>max-1</em>.log</code>, and so on,
51   * the file <code>foo.<em>min+1</em>.log</code> renamed as
52   * <code>foo.<em>min+2</em>.log</code>. Lastly, the active file <code>foo.log</code>
53   * will be renamed as <code>foo.<em>min</em>.log</code> and a new active file name
54   * <code>foo.log</code> will be created.
55   *
56   * <p>Given that this rollover algorithm requires as many file renaming
57   * operations as the window size, large window sizes are discouraged. The
58   * current implementation will automatically reduce the window size to 12 when
59   * larger values are specified by the user.
60   *
61   *
62   * @author Ceki G&uuml;lc&uuml;
63   * */
64  public final class FixedWindowRollingPolicy extends RollingPolicyBase {
65  
66    /***
67     * It's almost always a bad idea to have a large window size, say over 12.
68     */
69    private static final int MAX_WINDOW_SIZE = 12;
70  
71    /***
72     * Index for oldest retained log file.
73     */
74    private int maxIndex;
75  
76    /***
77     * Index for most recent log file.
78     */
79    private int minIndex;
80  
81    /***
82     *  if true, then an explicit name for the active file was
83     * specified using RollingFileAppender.file or the
84     * redundent RollingPolicyBase.setActiveFile
85     */
86    private boolean explicitActiveFile;
87  
88    /***
89     * Constructs a new instance.
90     */
91    public FixedWindowRollingPolicy() {
92      minIndex = 1;
93      maxIndex = 7;
94    }
95  
96    /***
97     * {@inheritDoc}
98     */
99    public void activateOptions() {
100     super.activateOptions();
101 
102     if (maxIndex < minIndex) {
103       LogLog.warn(
104         "MaxIndex (" + maxIndex + ") cannot be smaller than MinIndex ("
105         + minIndex + ").");
106       LogLog.warn("Setting maxIndex to equal minIndex.");
107       maxIndex = minIndex;
108     }
109 
110     if ((maxIndex - minIndex) > MAX_WINDOW_SIZE) {
111       LogLog.warn("Large window sizes are not allowed.");
112       maxIndex = minIndex + MAX_WINDOW_SIZE;
113       LogLog.warn("MaxIndex reduced to " + String.valueOf(maxIndex) + ".");
114     }
115 
116     PatternConverter itc = getIntegerPatternConverter();
117 
118     if (itc == null) {
119       throw new IllegalStateException(
120         "FileNamePattern [" + getFileNamePattern()
121         + "] does not contain a valid integer format specifier");
122     }
123   }
124 
125   /***
126    * {@inheritDoc}
127    */
128   public RolloverDescription initialize(
129     final String file, final boolean append) {
130     String newActiveFile = file;
131     explicitActiveFile = false;
132 
133     if (activeFileName != null) {
134       explicitActiveFile = true;
135       newActiveFile = activeFileName;
136     }
137 
138     if (file != null) {
139       explicitActiveFile = true;
140       newActiveFile = file;
141     }
142 
143     if (!explicitActiveFile) {
144       StringBuffer buf = new StringBuffer();
145       formatFileName(new Integer(minIndex), buf);
146       newActiveFile = buf.toString();
147     }
148 
149     return new RolloverDescriptionImpl(newActiveFile, append, null, null);
150   }
151 
152   /***
153    * {@inheritDoc}
154    */
155   public RolloverDescription rollover(final String currentFileName) {
156     if (maxIndex >= 0) {
157       int purgeStart = minIndex;
158 
159       if (!explicitActiveFile) {
160         purgeStart++;
161       }
162 
163       if (!purge(purgeStart, maxIndex)) {
164         return null;
165       }
166 
167       StringBuffer buf = new StringBuffer();
168       formatFileName(new Integer(purgeStart), buf);
169 
170       String renameTo = buf.toString();
171       String compressedName = renameTo;
172       Action compressAction = null;
173 
174       if (renameTo.endsWith(".gz")) {
175         renameTo = renameTo.substring(0, renameTo.length() - 3);
176         compressAction =
177           new GZCompressAction(
178             new File(renameTo), new File(compressedName), true);
179       } else if (renameTo.endsWith(".zip")) {
180         renameTo = renameTo.substring(0, renameTo.length() - 4);
181         compressAction =
182           new ZipCompressAction(
183             new File(renameTo), new File(compressedName), true);
184       }
185 
186       FileRenameAction renameAction =
187         new FileRenameAction(
188           new File(currentFileName), new File(renameTo), false);
189 
190       return new RolloverDescriptionImpl(
191         currentFileName, false, renameAction, compressAction);
192     }
193 
194     return null;
195   }
196 
197   /***
198    * Get index of oldest log file to be retained.
199    * @return index of oldest log file.
200    */
201   public int getMaxIndex() {
202     return maxIndex;
203   }
204 
205   /***
206    * Get index of most recent log file.
207    * @return index of oldest log file.
208    */
209   public int getMinIndex() {
210     return minIndex;
211   }
212 
213   /***
214    * Set index of oldest log file to be retained.
215    * @param maxIndex index of oldest log file to be retained.
216    */
217   public void setMaxIndex(int maxIndex) {
218     this.maxIndex = maxIndex;
219   }
220 
221   /***
222    * Set index of most recent log file.
223    * @param minIndex Index of most recent log file.
224    */
225   public void setMinIndex(int minIndex) {
226     this.minIndex = minIndex;
227   }
228 
229   /***
230    * Purge and rename old log files in preparation for rollover
231    * @param lowIndex low index
232    * @param highIndex high index.  Log file associated with high
233    * index will be deleted if needed.
234    * @return true if purge was successful and rollover should be attempted.
235    */
236   private boolean purge(final int lowIndex, final int highIndex) {
237     int suffixLength = 0;
238 
239     List renames = new ArrayList();
240     StringBuffer buf = new StringBuffer();
241     formatFileName(new Integer(lowIndex), buf);
242 
243     String lowFilename = buf.toString();
244 
245     if (lowFilename.endsWith(".gz")) {
246       suffixLength = 3;
247     } else if (lowFilename.endsWith(".zip")) {
248       suffixLength = 4;
249     }
250 
251     for (int i = lowIndex; i <= highIndex; i++) {
252       File toRename = new File(lowFilename);
253       boolean isBase = false;
254 
255       if (suffixLength > 0) {
256         File toRenameBase =
257           new File(
258             lowFilename.substring(0, lowFilename.length() - suffixLength));
259 
260         if (toRename.exists()) {
261           if (toRenameBase.exists()) {
262             toRenameBase.delete();
263           }
264         } else {
265           toRename = toRenameBase;
266           isBase = true;
267         }
268       }
269 
270       if (toRename.exists()) {
271         //
272         //    if at upper index then
273         //        attempt to delete last file
274         //        if that fails then abandon purge
275         if (i == highIndex) {
276           if (!toRename.delete()) {
277             return false;
278           }
279 
280           break;
281         }
282 
283         //
284         //   if intermediate index
285         //     add a rename action to the list
286         buf.setLength(0);
287         formatFileName(new Integer(i + 1), buf);
288 
289         String highFilename = buf.toString();
290         String renameTo = highFilename;
291 
292         if (isBase) {
293           renameTo =
294             highFilename.substring(0, highFilename.length() - suffixLength);
295         }
296 
297         renames.add(new FileRenameAction(toRename, new File(renameTo), true));
298         lowFilename = highFilename;
299       } else {
300         break;
301       }
302     }
303 
304     //
305     //   work renames backwards
306     //
307     for (int i = renames.size() - 1; i >= 0; i--) {
308       Action action = (Action) renames.get(i);
309 
310       try {
311         if (!action.execute()) {
312           return false;
313         }
314       } catch (Exception ex) {
315         LogLog.warn("Exception during purge in RollingFileAppender", ex);
316 
317         return false;
318       }
319     }
320 
321     return true;
322   }
323 }