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