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
19
20 package org.apache.log4j;
21
22 import java.io.IOException;
23 import java.io.Writer;
24 import java.io.File;
25 import java.io.InterruptedIOException;
26
27 import org.apache.log4j.helpers.OptionConverter;
28 import org.apache.log4j.helpers.LogLog;
29 import org.apache.log4j.helpers.CountingQuietWriter;
30 import org.apache.log4j.spi.LoggingEvent;
31
32 /**
33 RollingFileAppender extends FileAppender to backup the log files when
34 they reach a certain size.
35
36 The log4j extras companion includes alternatives which should be considered
37 for new deployments and which are discussed in the documentation
38 for org.apache.log4j.rolling.RollingFileAppender.
39
40
41 @author Heinz Richter
42 @author Ceki Gülcü
43
44 */
45 public class RollingFileAppender extends FileAppender {
46
47 /**
48 The default maximum file size is 10MB.
49 */
50 protected long maxFileSize = 10*1024*1024;
51
52 /**
53 There is one backup file by default.
54 */
55 protected int maxBackupIndex = 1;
56
57 private long nextRollover = 0;
58
59 /**
60 The default constructor simply calls its {@link
61 FileAppender#FileAppender parents constructor}. */
62 public
63 RollingFileAppender() {
64 super();
65 }
66
67 /**
68 Instantiate a RollingFileAppender and open the file designated by
69 <code>filename</code>. The opened filename will become the ouput
70 destination for this appender.
71
72 <p>If the <code>append</code> parameter is true, the file will be
73 appended to. Otherwise, the file desginated by
74 <code>filename</code> will be truncated before being opened.
75 */
76 public
77 RollingFileAppender(Layout layout, String filename, boolean append)
78 throws IOException {
79 super(layout, filename, append);
80 }
81
82 /**
83 Instantiate a FileAppender and open the file designated by
84 <code>filename</code>. The opened filename will become the output
85 destination for this appender.
86
87 <p>The file will be appended to. */
88 public
89 RollingFileAppender(Layout layout, String filename) throws IOException {
90 super(layout, filename);
91 }
92
93 /**
94 Returns the value of the <b>MaxBackupIndex</b> option.
95 */
96 public
97 int getMaxBackupIndex() {
98 return maxBackupIndex;
99 }
100
101 /**
102 Get the maximum size that the output file is allowed to reach
103 before being rolled over to backup files.
104
105 @since 1.1
106 */
107 public
108 long getMaximumFileSize() {
109 return maxFileSize;
110 }
111
112 /**
113 Implements the usual roll over behaviour.
114
115 <p>If <code>MaxBackupIndex</code> is positive, then files
116 {<code>File.1</code>, ..., <code>File.MaxBackupIndex -1</code>}
117 are renamed to {<code>File.2</code>, ...,
118 <code>File.MaxBackupIndex</code>}. Moreover, <code>File</code> is
119 renamed <code>File.1</code> and closed. A new <code>File</code> is
120 created to receive further log output.
121
122 <p>If <code>MaxBackupIndex</code> is equal to zero, then the
123 <code>File</code> is truncated with no backup files created.
124
125 */
126 public // synchronization not necessary since doAppend is alreasy synched
127 void rollOver() {
128 File target;
129 File file;
130
131 if (qw != null) {
132 long size = ((CountingQuietWriter) qw).getCount();
133 LogLog.debug("rolling over count=" + size);
134 // if operation fails, do not roll again until
135 // maxFileSize more bytes are written
136 nextRollover = size + maxFileSize;
137 }
138 LogLog.debug("maxBackupIndex="+maxBackupIndex);
139
140 boolean renameSucceeded = true;
141 // If maxBackups <= 0, then there is no file renaming to be done.
142 if(maxBackupIndex > 0) {
143 // Delete the oldest file, to keep Windows happy.
144 file = new File(fileName + '.' + maxBackupIndex);
145 if (file.exists())
146 renameSucceeded = file.delete();
147
148 // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2}
149 for (int i = maxBackupIndex - 1; i >= 1 && renameSucceeded; i--) {
150 file = new File(fileName + "." + i);
151 if (file.exists()) {
152 target = new File(fileName + '.' + (i + 1));
153 LogLog.debug("Renaming file " + file + " to " + target);
154 renameSucceeded = file.renameTo(target);
155 }
156 }
157
158 if(renameSucceeded) {
159 // Rename fileName to fileName.1
160 target = new File(fileName + "." + 1);
161
162 this.closeFile(); // keep windows happy.
163
164 file = new File(fileName);
165 LogLog.debug("Renaming file " + file + " to " + target);
166 renameSucceeded = file.renameTo(target);
167 //
168 // if file rename failed, reopen file with append = true
169 //
170 if (!renameSucceeded) {
171 try {
172 this.setFile(fileName, true, bufferedIO, bufferSize);
173 }
174 catch(IOException e) {
175 if (e instanceof InterruptedIOException) {
176 Thread.currentThread().interrupt();
177 }
178 LogLog.error("setFile("+fileName+", true) call failed.", e);
179 }
180 }
181 }
182 }
183
184 //
185 // if all renames were successful, then
186 //
187 if (renameSucceeded) {
188 try {
189 // This will also close the file. This is OK since multiple
190 // close operations are safe.
191 this.setFile(fileName, false, bufferedIO, bufferSize);
192 nextRollover = 0;
193 }
194 catch(IOException e) {
195 if (e instanceof InterruptedIOException) {
196 Thread.currentThread().interrupt();
197 }
198 LogLog.error("setFile("+fileName+", false) call failed.", e);
199 }
200 }
201 }
202
203 public
204 synchronized
205 void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)
206 throws IOException {
207 super.setFile(fileName, append, this.bufferedIO, this.bufferSize);
208 if(append) {
209 File f = new File(fileName);
210 ((CountingQuietWriter) qw).setCount(f.length());
211 }
212 }
213
214
215 /**
216 Set the maximum number of backup files to keep around.
217
218 <p>The <b>MaxBackupIndex</b> option determines how many backup
219 files are kept before the oldest is erased. This option takes
220 a positive integer value. If set to zero, then there will be no
221 backup files and the log file will be truncated when it reaches
222 <code>MaxFileSize</code>.
223 */
224 public
225 void setMaxBackupIndex(int maxBackups) {
226 this.maxBackupIndex = maxBackups;
227 }
228
229 /**
230 Set the maximum size that the output file is allowed to reach
231 before being rolled over to backup files.
232
233 <p>This method is equivalent to {@link #setMaxFileSize} except
234 that it is required for differentiating the setter taking a
235 <code>long</code> argument from the setter taking a
236 <code>String</code> argument by the JavaBeans {@link
237 java.beans.Introspector Introspector}.
238
239 @see #setMaxFileSize(String)
240 */
241 public
242 void setMaximumFileSize(long maxFileSize) {
243 this.maxFileSize = maxFileSize;
244 }
245
246
247 /**
248 Set the maximum size that the output file is allowed to reach
249 before being rolled over to backup files.
250
251 <p>In configuration files, the <b>MaxFileSize</b> option takes an
252 long integer in the range 0 - 2^63. You can specify the value
253 with the suffixes "KB", "MB" or "GB" so that the integer is
254 interpreted being expressed respectively in kilobytes, megabytes
255 or gigabytes. For example, the value "10KB" will be interpreted
256 as 10240.
257 */
258 public
259 void setMaxFileSize(String value) {
260 maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1);
261 }
262
263 protected
264 void setQWForFiles(Writer writer) {
265 this.qw = new CountingQuietWriter(writer, errorHandler);
266 }
267
268 /**
269 This method differentiates RollingFileAppender from its super
270 class.
271
272 @since 0.9.0
273 */
274 protected
275 void subAppend(LoggingEvent event) {
276 super.subAppend(event);
277 if(fileName != null && qw != null) {
278 long size = ((CountingQuietWriter) qw).getCount();
279 if (size >= maxFileSize && size >= nextRollover) {
280 rollOver();
281 }
282 }
283 }
284 }