View Javadoc

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