/** * * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * **/ package lucee.commons.io.log.log4j.appender; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import lucee.commons.io.res.Resource; import lucee.commons.io.retirement.RetireListener; import org.apache.log4j.Layout; import org.apache.log4j.helpers.CountingQuietWriter; import org.apache.log4j.helpers.LogLog; import org.apache.log4j.spi.LoggingEvent; public class RollingResourceAppender extends ResourceAppender { public static final long DEFAULT_MAX_FILE_SIZE = 10*1024*1024; public static final int DEFAULT_MAX_BACKUP_INDEX = 10; protected final long maxFileSize; protected int maxBackupIndex; private long nextRollover = 0; /** Instantiate a FileAppender and open the file designated by <code>filename</code>. The opened filename will become the output destination for this appender. <p>The file will be appended to. */ public RollingResourceAppender(Layout layout, Resource res,Charset charset,RetireListener listener) throws IOException { this(layout, res,charset, true,DEFAULT_MAX_FILE_SIZE,DEFAULT_MAX_BACKUP_INDEX,60,listener); } /** Instantiate a RollingFileAppender and open the file designated by <code>filename</code>. The opened filename will become the ouput destination for this appender. <p>If the <code>append</code> parameter is true, the file will be appended to. Otherwise, the file desginated by <code>filename</code> will be truncated before being opened. */ public RollingResourceAppender(Layout layout, Resource res,Charset charset, boolean append,RetireListener listener) throws IOException { this(layout, res,charset, append,DEFAULT_MAX_FILE_SIZE,DEFAULT_MAX_BACKUP_INDEX,60,listener); } /** Instantiate a RollingFileAppender and open the file designated by <code>filename</code>. The opened filename will become the ouput destination for this appender. <p>If the <code>append</code> parameter is true, the file will be appended to. Otherwise, the file desginated by <code>filename</code> will be truncated before being opened. */ public RollingResourceAppender(Layout layout, Resource res,Charset charset, boolean append, long maxFileSize,int maxBackupIndex, int timeout,RetireListener listener) throws IOException { super(layout, res,charset, append,timeout,listener); this.maxFileSize=maxFileSize; this.maxBackupIndex=maxBackupIndex; } /** Returns the value of the <b>MaxBackupIndex</b> option. */ public int getMaxBackupIndex() { return maxBackupIndex; } /** Get the maximum size that the output file is allowed to reach before being rolled over to backup files. @since 1.1 */ public long getMaximumFileSize() { return maxFileSize; } /** Implements the usual roll over behaviour. <p>If <code>MaxBackupIndex</code> is positive, then files {<code>File.1</code>, ..., <code>File.MaxBackupIndex -1</code>} are renamed to {<code>File.2</code>, ..., <code>File.MaxBackupIndex</code>}. Moreover, <code>File</code> is renamed <code>File.1</code> and closed. A new <code>File</code> is created to receive further log output. <p>If <code>MaxBackupIndex</code> is equal to zero, then the <code>File</code> is truncated with no backup files created. */ public void rollOver() { Resource target; Resource file; if (qw != null) { long size = ((CountingQuietWriter) qw).getCount(); LogLog.debug("rolling over count=" + size); // if operation fails, do not roll again until // maxFileSize more bytes are written nextRollover = size + maxFileSize; } LogLog.debug("maxBackupIndex="+maxBackupIndex); boolean renameSucceeded = true; Resource parent = res.getParentResource(); // If maxBackups <= 0, then there is no file renaming to be done. if(maxBackupIndex > 0) { // Delete the oldest file, to keep Windows happy. file = parent.getRealResource(res.getName()+"."+maxBackupIndex+".bak"); if (file.exists()) renameSucceeded = file.delete(); // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} for (int i = maxBackupIndex - 1; i >= 1 && renameSucceeded; i--) { file = parent.getRealResource(res.getName()+"."+i+".bak"); if (file.exists()) { target = parent.getRealResource(res.getName()+"."+(i + 1)+".bak"); LogLog.debug("Renaming file " + file + " to " + target); renameSucceeded = file.renameTo(target); } } if(renameSucceeded) { // Rename fileName to fileName.1 target = parent.getRealResource(res.getName()+".1.bak"); this.closeFile(); // keep windows happy. file = res; LogLog.debug("Renaming file " + file + " to " + target); renameSucceeded = file.renameTo(target); // if file rename failed, reopen file with append = true if (!renameSucceeded) { try { this.setFile(true); } catch(IOException e) { LogLog.error("setFile("+res+", true) call failed.", e); } } } } // if all renames were successful, then if (renameSucceeded) { try { // This will also close the file. This is OK since multiple // close operations are safe. this.setFile(false); nextRollover = 0; } catch(IOException e) { LogLog.error("setFile("+res+", false) call failed.", e); } } } @Override public synchronized void setFile(boolean append) throws IOException { long len = res.length();// this is done here, because in the location used the file is already locked super.setFile(append); if(append) { ((CountingQuietWriter) qw).setCount(len); } } /** Set the maximum number of backup files to keep around. <p>The <b>MaxBackupIndex</b> option determines how many backup files are kept before the oldest is erased. This option takes a positive integer value. If set to zero, then there will be no backup files and the log file will be truncated when it reaches <code>MaxFileSize</code>. */ public void setMaxBackupIndex(int maxBackups) { this.maxBackupIndex = maxBackups; } @Override protected void setQWForFiles(Writer writer) { this.qw = new CountingQuietWriter(writer, errorHandler); } /** This method differentiates RollingFileAppender from its super class. @since 0.9.0 */ @Override protected void subAppend(LoggingEvent event) { super.subAppend(event); if(res != null && qw != null) { long size = ((CountingQuietWriter) qw).getCount(); if (size >= maxFileSize && size >= nextRollover) { rollOver(); } } } }