001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.commons.io.log.log4j.appender; 020 021import java.io.IOException; 022import java.io.Writer; 023import java.nio.charset.Charset; 024 025import lucee.commons.io.res.Resource; 026import lucee.commons.io.retirement.RetireListener; 027 028import org.apache.log4j.Layout; 029import org.apache.log4j.helpers.CountingQuietWriter; 030import org.apache.log4j.helpers.LogLog; 031import org.apache.log4j.spi.LoggingEvent; 032 033public class RollingResourceAppender extends ResourceAppender { 034 035 036 public static final long DEFAULT_MAX_FILE_SIZE = 10*1024*1024; 037 public static final int DEFAULT_MAX_BACKUP_INDEX = 10; 038 039 protected final long maxFileSize; 040 protected int maxBackupIndex; 041 042 private long nextRollover = 0; 043 044 045 /** 046 Instantiate a FileAppender and open the file designated by 047 <code>filename</code>. The opened filename will become the output 048 destination for this appender. 049 050 <p>The file will be appended to. */ 051 public RollingResourceAppender(Layout layout, Resource res,Charset charset,RetireListener listener) throws IOException { 052 this(layout, res,charset, true,DEFAULT_MAX_FILE_SIZE,DEFAULT_MAX_BACKUP_INDEX,60,listener); 053 } 054 055 056 /** 057 Instantiate a RollingFileAppender and open the file designated by 058 <code>filename</code>. The opened filename will become the ouput 059 destination for this appender. 060 061 <p>If the <code>append</code> parameter is true, the file will be 062 appended to. Otherwise, the file desginated by 063 <code>filename</code> will be truncated before being opened. 064 */ 065 public RollingResourceAppender(Layout layout, Resource res,Charset charset, boolean append,RetireListener listener) throws IOException { 066 this(layout, res,charset, append,DEFAULT_MAX_FILE_SIZE,DEFAULT_MAX_BACKUP_INDEX,60,listener); 067 } 068 069 070 /** 071 Instantiate a RollingFileAppender and open the file designated by 072 <code>filename</code>. The opened filename will become the ouput 073 destination for this appender. 074 075 <p>If the <code>append</code> parameter is true, the file will be 076 appended to. Otherwise, the file desginated by 077 <code>filename</code> will be truncated before being opened. 078 */ 079 public RollingResourceAppender(Layout layout, Resource res,Charset charset, boolean append, long maxFileSize,int maxBackupIndex, int timeout,RetireListener listener) throws IOException { 080 super(layout, res,charset, append,timeout,listener); 081 this.maxFileSize=maxFileSize; 082 this.maxBackupIndex=maxBackupIndex; 083 } 084 085 086/** 087 Returns the value of the <b>MaxBackupIndex</b> option. 088 */ 089 public int getMaxBackupIndex() { 090 return maxBackupIndex; 091 } 092 093 /** 094 Get the maximum size that the output file is allowed to reach 095 before being rolled over to backup files. 096 097 @since 1.1 098 */ 099 public long getMaximumFileSize() { 100 return maxFileSize; 101 } 102 103 /** 104 Implements the usual roll over behaviour. 105 106 <p>If <code>MaxBackupIndex</code> is positive, then files 107 {<code>File.1</code>, ..., <code>File.MaxBackupIndex -1</code>} 108 are renamed to {<code>File.2</code>, ..., 109 <code>File.MaxBackupIndex</code>}. Moreover, <code>File</code> is 110 renamed <code>File.1</code> and closed. A new <code>File</code> is 111 created to receive further log output. 112 113 <p>If <code>MaxBackupIndex</code> is equal to zero, then the 114 <code>File</code> is truncated with no backup files created. 115 116 */ 117 public void rollOver() { 118 Resource target; 119 Resource file; 120 121 if (qw != null) { 122 long size = ((CountingQuietWriter) qw).getCount(); 123 LogLog.debug("rolling over count=" + size); 124 // if operation fails, do not roll again until 125 // maxFileSize more bytes are written 126 nextRollover = size + maxFileSize; 127 } 128 LogLog.debug("maxBackupIndex="+maxBackupIndex); 129 130 boolean renameSucceeded = true; 131 Resource parent = res.getParentResource(); 132 133 // If maxBackups <= 0, then there is no file renaming to be done. 134 if(maxBackupIndex > 0) { 135 // Delete the oldest file, to keep Windows happy. 136 file = parent.getRealResource(res.getName()+"."+maxBackupIndex+".bak"); 137 138 if (file.exists()) renameSucceeded = file.delete(); 139 140 // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} 141 for (int i = maxBackupIndex - 1; i >= 1 && renameSucceeded; i--) { 142 file = parent.getRealResource(res.getName()+"."+i+".bak"); 143 if (file.exists()) { 144 target = parent.getRealResource(res.getName()+"."+(i + 1)+".bak"); 145 LogLog.debug("Renaming file " + file + " to " + target); 146 renameSucceeded = file.renameTo(target); 147 } 148 } 149 150 if(renameSucceeded) { 151 // Rename fileName to fileName.1 152 target = parent.getRealResource(res.getName()+".1.bak"); 153 154 this.closeFile(); // keep windows happy. 155 156 file = res; 157 LogLog.debug("Renaming file " + file + " to " + target); 158 renameSucceeded = file.renameTo(target); 159 160 // if file rename failed, reopen file with append = true 161 162 if (!renameSucceeded) { 163 try { 164 this.setFile(true); 165 } 166 catch(IOException e) { 167 LogLog.error("setFile("+res+", true) call failed.", e); 168 } 169 } 170 } 171 } 172 173 // if all renames were successful, then 174 175 if (renameSucceeded) { 176 try { 177 // This will also close the file. This is OK since multiple 178 // close operations are safe. 179 this.setFile(false); 180 nextRollover = 0; 181 } 182 catch(IOException e) { 183 LogLog.error("setFile("+res+", false) call failed.", e); 184 } 185 } 186 } 187 188 public 189 synchronized 190 void setFile(boolean append) throws IOException { 191 long len = res.length();// this is done here, because in the location used the file is already locked 192 super.setFile(append); 193 if(append) { 194 ((CountingQuietWriter) qw).setCount(len); 195 } 196 } 197 198 199 /** 200 Set the maximum number of backup files to keep around. 201 202 <p>The <b>MaxBackupIndex</b> option determines how many backup 203 files are kept before the oldest is erased. This option takes 204 a positive integer value. If set to zero, then there will be no 205 backup files and the log file will be truncated when it reaches 206 <code>MaxFileSize</code>. 207 */ 208 public 209 void setMaxBackupIndex(int maxBackups) { 210 this.maxBackupIndex = maxBackups; 211 } 212 213 214 protected 215 void setQWForFiles(Writer writer) { 216 this.qw = new CountingQuietWriter(writer, errorHandler); 217 } 218 219 /** 220 This method differentiates RollingFileAppender from its super 221 class. 222 223 @since 0.9.0 224 */ 225 protected void subAppend(LoggingEvent event) { 226 super.subAppend(event); 227 if(res != null && qw != null) { 228 long size = ((CountingQuietWriter) qw).getCount(); 229 if (size >= maxFileSize && size >= nextRollover) { 230 rollOver(); 231 } 232 } 233 } 234}