001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018
019
020package org.apache.log4j;
021
022import java.io.IOException;
023import java.io.Writer;
024import java.io.File;
025import java.io.InterruptedIOException;
026
027import org.apache.log4j.helpers.OptionConverter;
028import org.apache.log4j.helpers.LogLog;
029import org.apache.log4j.helpers.CountingQuietWriter;
030import org.apache.log4j.spi.LoggingEvent;
031
032/**
033   RollingFileAppender extends FileAppender to backup the log files when
034   they reach a certain size.
035   
036   The log4j extras companion includes alternatives which should be considered
037   for new deployments and which are discussed in the documentation
038   for org.apache.log4j.rolling.RollingFileAppender.
039   
040
041   @author Heinz Richter
042   @author Ceki Gülcü
043
044*/
045public class RollingFileAppender extends FileAppender {
046
047  /**
048     The default maximum file size is 10MB.
049  */
050  protected long maxFileSize = 10*1024*1024;
051
052  /**
053     There is one backup file by default.
054   */
055  protected int  maxBackupIndex  = 1;
056
057  private long nextRollover = 0;
058
059  /**
060     The default constructor simply calls its {@link
061     FileAppender#FileAppender parents constructor}.  */
062  public
063  RollingFileAppender() {
064    super();
065  }
066
067  /**
068    Instantiate a RollingFileAppender and open the file designated by
069    <code>filename</code>. The opened filename will become the ouput
070    destination for this appender.
071
072    <p>If the <code>append</code> parameter is true, the file will be
073    appended to. Otherwise, the file desginated by
074    <code>filename</code> will be truncated before being opened.
075  */
076  public
077  RollingFileAppender(Layout layout, String filename, boolean append)
078                                      throws IOException {
079    super(layout, filename, append);
080  }
081
082  /**
083     Instantiate a FileAppender and open the file designated by
084    <code>filename</code>. The opened filename will become the output
085    destination for this appender.
086
087    <p>The file will be appended to.  */
088  public
089  RollingFileAppender(Layout layout, String filename) throws IOException {
090    super(layout, filename);
091  }
092
093  /**
094     Returns the value of the <b>MaxBackupIndex</b> option.
095   */
096  public
097  int getMaxBackupIndex() {
098    return maxBackupIndex;
099  }
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}