1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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ülcü
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
273
274
275 if (i == highIndex) {
276 if (!toRename.delete()) {
277 return false;
278 }
279
280 break;
281 }
282
283
284
285
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
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 }