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}