diff options
Diffstat (limited to 'trunk/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java')
-rw-r--r-- | trunk/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java | 1094 |
1 files changed, 1094 insertions, 0 deletions
diff --git a/trunk/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java b/trunk/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java new file mode 100644 index 0000000000..be52b82789 --- /dev/null +++ b/trunk/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java @@ -0,0 +1,1094 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.log4j; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.GZIPOutputStream; + +import org.apache.log4j.helpers.CountingQuietWriter; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * <p>CompositeRollingAppender combines RollingFileAppender and DailyRollingFileAppender<br> It can function as either + * or do both at the same time (making size based rolling files like RollingFileAppender until a data/time boundary is + * crossed at which time it rolls all of those files as per the DailyRollingFileAppender) based on the setting for + * <code>rollingStyle</code>.<br> <br> To use CompositeRollingAppender to roll log files as they reach a certain size + * (like RollingFileAppender), set rollingStyle=1 (@see config.size)<br> To use CompositeRollingAppender to roll log + * files at certain time intervals (daily for example), set rollingStyle=2 and a datePattern (@see config.time)<br> To + * have CompositeRollingAppender roll log files at a certain size AND rename those according to time intervals, set + * rollingStyle=3 (@see config.composite)<br> + * + * <p>A of few additional optional features have been added:<br> -- Attach date pattern for current log file (@see + * staticLogFileName)<br> -- Backup number increments for newer files (@see countDirection)<br> -- Infinite number of + * backups by file size (@see maxSizeRollBackups)<br> <br> <p>A few notes and warnings: For large or infinite number of + * backups countDirection > 0 is highly recommended, with staticLogFileName = false if time based rolling is also used + * -- this will reduce the number of file renamings to few or none. Changing staticLogFileName or countDirection + * without clearing the directory could have nasty side effects. If Date/Time based rolling is enabled, + * CompositeRollingAppender will attempt to roll existing files in the directory without a date/time tag based on the + * last modified date of the base log files last modification.<br> <br> <p>A maximum number of backups based on + * date/time boundries would be nice but is not yet implemented.<br> + * + * @author Kevin Steppe + * @author Heinz Richter + * @author Eirik Lygre + * @author Ceki Gülcü + * @author Martin Ritchie + */ +public class QpidCompositeRollingAppender extends FileAppender +{ + // The code assumes that the following 'time' constants are in a increasing + // sequence. + static final int TOP_OF_TROUBLE = -1; + static final int TOP_OF_MINUTE = 0; + static final int TOP_OF_HOUR = 1; + static final int HALF_DAY = 2; + static final int TOP_OF_DAY = 3; + static final int TOP_OF_WEEK = 4; + static final int TOP_OF_MONTH = 5; + + /** Style of rolling to use */ + static final int BY_SIZE = 1; + static final int BY_DATE = 2; + static final int BY_COMPOSITE = 3; + + // Not currently used + static final String S_BY_SIZE = "Size"; + static final String S_BY_DATE = "Date"; + static final String S_BY_COMPOSITE = "Composite"; + + /** The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" meaning daily rollover. */ + private String datePattern = "'.'yyyy-MM-dd"; + + /** + * The actual formatted filename that is currently being written to or will be the file transferred to on roll over + * (based on staticLogFileName). + */ + private String scheduledFilename = null; + + /** The timestamp when we shall next recompute the filename. */ + private long nextCheck = System.currentTimeMillis() - 1; + + /** Holds date of last roll over */ + Date now = new Date(); + + SimpleDateFormat sdf; + + /** Helper class to determine next rollover time */ + RollingCalendar rc = new RollingCalendar(); + + /** Current period for roll overs */ + int checkPeriod = TOP_OF_TROUBLE; + + /** The default maximum file size is 10MB. */ + protected long maxFileSize = 10 * 1024 * 1024; + + /** There is zero backup files by default. */ + protected int maxSizeRollBackups = 0; + /** How many sized based backups have been made so far */ + protected int curSizeRollBackups = 0; + + /** not yet implemented */ + protected int maxTimeRollBackups = -1; + protected int curTimeRollBackups = 0; + + /** + * By default newer files have lower numbers. (countDirection < 0) ie. log.1 is most recent, log.5 is the 5th + * backup, etc... countDirection > 0 does the opposite ie. log.1 is the first backup made, log.5 is the 5th backup + * made, etc. For infinite backups use countDirection > 0 to reduce rollOver costs. + */ + protected int countDirection = -1; + + /** Style of rolling to Use. BY_SIZE (1), BY_DATE(2), BY COMPOSITE(3) */ + protected int rollingStyle = BY_COMPOSITE; + protected boolean rollDate = true; + protected boolean rollSize = true; + + /** + * By default file.log is always the current file. Optionally file.log.yyyy-mm-dd for current formated datePattern + * can by the currently logging file (or file.log.curSizeRollBackup or even file.log.yyyy-mm-dd.curSizeRollBackup) + * This will make time based roll overs with a large number of backups much faster -- it won't have to rename all + * the backups! + */ + protected boolean staticLogFileName = true; + + /** FileName provided in configuration. Used for rolling properly */ + protected String baseFileName; + + /** Do we want to .gz our backup files. */ + protected boolean compress = false; + + /** Do we want to use a second thread when compressing our backup files. */ + protected boolean compressAsync = false; + + /** Do we want to start numbering files at zero. */ + protected boolean zeroBased = false; + + /** Path provided in configuration. Used for moving backup files to */ + protected String backupFilesToPath = null; + private final ConcurrentLinkedQueue<CompressJob> _compress = new ConcurrentLinkedQueue<CompressJob>(); + private AtomicBoolean _compressing = new AtomicBoolean(false); + private static final String COMPRESS_EXTENSION = ".gz"; + + /** The default constructor does nothing. */ + public QpidCompositeRollingAppender() + { } + + /** + * Instantiate a <code>CompositeRollingAppender</code> and open the file designated by <code>filename</code>. The + * opened filename will become the ouput destination for this appender. + */ + public QpidCompositeRollingAppender(Layout layout, String filename, String datePattern) throws IOException + { + this(layout, filename, datePattern, true); + } + + /** + * Instantiate a CompositeRollingAppender 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 QpidCompositeRollingAppender(Layout layout, String filename, boolean append) throws IOException + { + super(layout, filename, append); + } + + /** + * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename + * will become the ouput destination for this appender. + */ + public QpidCompositeRollingAppender(Layout layout, String filename, String datePattern, boolean append) + throws IOException + { + super(layout, filename, append); + this.datePattern = datePattern; + activateOptions(); + } + + /** + * Instantiate a CompositeRollingAppender 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. DatePattern is default. + */ + public QpidCompositeRollingAppender(Layout layout, String filename) throws IOException + { + super(layout, filename); + } + + /** + * The <b>DatePattern</b> takes a string in the same format as expected by {@link java.text.SimpleDateFormat}. This + * options determines the rollover schedule. + */ + public void setDatePattern(String pattern) + { + datePattern = pattern; + } + + /** Returns the value of the <b>DatePattern</b> option. */ + public String getDatePattern() + { + return datePattern; + } + + /** Returns the value of the <b>maxSizeRollBackups</b> option. */ + public int getMaxSizeRollBackups() + { + return maxSizeRollBackups; + } + + /** + * 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; + } + + /** + * <p>Set the maximum number of backup files to keep around based on file size. + * + * <p>The <b>MaxSizeRollBackups</b> option determines how many backup files are kept before the oldest is erased. + * This option takes an 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>. If a negative number is supplied then no deletions will be + * made. Note that this could result in very slow performance as a large number of files are rolled over unless + * {@link #setCountDirection} up is used. + * + * <p>The maximum applys to -each- time based group of files and -not- the total. Using a daily roll the maximum + * total files would be (#days run) * (maxSizeRollBackups) + */ + public void setMaxSizeRollBackups(int maxBackups) + { + maxSizeRollBackups = maxBackups; + } + + /** + * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * <p>This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter + * taking a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans {@link + * java.beans.Introspector Introspector}. + * + * @see #setMaxFileSize(String) + */ + public void setMaxFileSize(long maxFileSize) + { + this.maxFileSize = maxFileSize; + } + + /** + * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * <p>This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter + * taking a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans {@link + * java.beans.Introspector Introspector}. + * + * @see #setMaxFileSize(String) + */ + public void setMaximumFileSize(long maxFileSize) + { + this.maxFileSize = maxFileSize; + } + + /** + * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * <p>In configuration files, the <b>MaxFileSize</b> option takes an long integer in the range 0 - 2^63. You can + * specify the value with the suffixes "KB", "MB" or "GB" so that the integer is interpreted being expressed + * respectively in kilobytes, megabytes or gigabytes. For example, the value "10KB" will be interpreted as 10240. + */ + public void setMaxFileSize(String value) + { + maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1); + } + + protected void setQWForFiles(Writer writer) + { + qw = new CountingQuietWriter(writer, errorHandler); + } + + // Taken verbatum from DailyRollingFileAppender + int computeCheckPeriod() + { + RollingCalendar c = new RollingCalendar(); + // set sate to 1970-01-01 00:00:00 GMT + Date epoch = new Date(0); + if (datePattern != null) + { + for (int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) + { + String r0 = sdf.format(epoch); + c.setType(i); + Date next = new Date(c.getNextCheckMillis(epoch)); + String r1 = sdf.format(next); + // LogLog.debug("Type = "+i+", r0 = "+r0+", r1 = "+r1); + if ((r0 != null) && (r1 != null) && !r0.equals(r1)) + { + return i; + } + } + } + + return TOP_OF_TROUBLE; // Deliberately head for trouble... + } + + // Now for the new stuff + /** + * Handles append time behavior for CompositeRollingAppender. This checks if a roll over either by date (checked + * first) or time (checked second) is need and then appends to the file last. + */ + protected void subAppend(LoggingEvent event) + { + + if (rollDate) + { + long n = System.currentTimeMillis(); + if (n >= nextCheck) + { + now.setTime(n); + nextCheck = rc.getNextCheckMillis(now); + + rollOverTime(); + } + } + + if (rollSize) + { + if ((fileName != null) && (((CountingQuietWriter) qw).getCount() >= maxFileSize)) + { + rollOverSize(); + } + } + + super.subAppend(event); + } + + public void setFile(String file) + { + baseFileName = file.trim(); + fileName = file.trim(); + } + + /** + * Creates and opens the file for logging. If <code>staticLogFileName</code> is false then the fully qualified name + * is determined and used. + */ + public synchronized void setFile(String fileName, boolean append) throws IOException + { + if (!staticLogFileName) + { + scheduledFilename = fileName = fileName.trim() + sdf.format(now); + } + + super.setFile(fileName, append, bufferedIO, bufferSize); + + if (append) + { + File f = new File(fileName); + ((CountingQuietWriter) qw).setCount(f.length()); + } + } + + public int getCountDirection() + { + return countDirection; + } + + public void setCountDirection(int direction) + { + countDirection = direction; + } + + public int getRollingStyle() + { + return rollingStyle; + } + + public void setRollingStyle(int style) + { + rollingStyle = style; + switch (rollingStyle) + { + + case BY_SIZE: + rollDate = false; + rollSize = true; + break; + + case BY_DATE: + rollDate = true; + rollSize = false; + break; + + case BY_COMPOSITE: + rollDate = true; + rollSize = true; + break; + + default: + errorHandler.error("Invalid rolling Style, use 1 (by size only), 2 (by date only) or 3 (both)"); + } + } + + /* + public void setRollingStyle(String style) { + if (style == S_BY_SIZE) { + rollingStyle = BY_SIZE; + } + else if (style == S_BY_DATE) { + rollingStyle = BY_DATE; + } + else if (style == S_BY_COMPOSITE) { + rollingStyle = BY_COMPOSITE; + } + } + */ + public boolean getStaticLogFileName() + { + return staticLogFileName; + } + + public void setStaticLogFileName(boolean s) + { + staticLogFileName = s; + } + + public void setStaticLogFileName(String value) + { + setStaticLogFileName(OptionConverter.toBoolean(value, true)); + } + + public boolean getCompressBackupFiles() + { + return compress; + } + + public void setCompressBackupFiles(boolean c) + { + compress = c; + } + + public boolean getCompressAsync() + { + return compressAsync; + } + + public void setCompressAsync(boolean c) + { + compressAsync = c; + if (compressAsync) + { + executor = Executors.newFixedThreadPool(1); + + compressor = new Compressor(); + } + } + + public boolean getZeroBased() + { + return zeroBased; + } + + public void setZeroBased(boolean z) + { + zeroBased = z; + } + + public String getBackupFilesToPath() + { + return backupFilesToPath; + } + + public void setbackupFilesToPath(String path) + { + File td = new File(path); + if (!td.exists()) + { + td.mkdirs(); + } + + backupFilesToPath = path; + } + + /** + * Initializes based on exisiting conditions at time of <code> activateOptions</code>. The following is done:<br> + * <br> A) determine curSizeRollBackups<br> B) determine curTimeRollBackups (not implemented)<br> C) initiates a + * roll over if needed for crossing a date boundary since the last run. + */ + protected void existingInit() + { + curTimeRollBackups = 0; + + // part A starts here + // This is now down at first log when curSizeRollBackup==0 see rollFile + // part A ends here + + // part B not yet implemented + + // part C + if (staticLogFileName && rollDate) + { + File old = new File(baseFileName); + if (old.exists()) + { + Date last = new Date(old.lastModified()); + if (!(sdf.format(last).equals(sdf.format(now)))) + { + scheduledFilename = baseFileName + sdf.format(last); + LogLog.debug("Initial roll over to: " + scheduledFilename); + rollOverTime(); + } + } + } + + LogLog.debug("curSizeRollBackups after rollOver at: " + curSizeRollBackups); + // part C ends here + + } + + /** + * Sets initial conditions including date/time roll over information, first check, scheduledFilename, and calls + * <code>existingInit</code> to initialize the current # of backups. + */ + public void activateOptions() + { + + // REMOVE removed rollDate from boolean to enable Alex's change + if (datePattern != null) + { + now.setTime(System.currentTimeMillis()); + sdf = new SimpleDateFormat(datePattern); + int type = computeCheckPeriod(); + // printPeriodicity(type); + rc.setType(type); + // next line added as this removes the name check in rollOver + nextCheck = rc.getNextCheckMillis(now); + } + else + { + if (rollDate) + { + LogLog.error("Either DatePattern or rollingStyle options are not set for [" + name + "]."); + } + } + + existingInit(); + + if (rollDate && (fileName != null) && (scheduledFilename == null)) + { + scheduledFilename = fileName + sdf.format(now); + } + + try + { + this.setFile(fileName, true); + } + catch (IOException e) + { + errorHandler.error("Cannot set file name:" + fileName); + } + + super.activateOptions(); + } + + /** + * Rollover the file(s) to date/time tagged file(s). Opens the new file (through setFile) and resets + * curSizeRollBackups. + */ + protected void rollOverTime() + { + + curTimeRollBackups++; + + this.closeFile(); // keep windows happy. + + + rollFile(); + + try + { + curSizeRollBackups = 0; // We're cleared out the old date and are ready for the new + + // new scheduled name + scheduledFilename = fileName + sdf.format(now); + this.setFile(baseFileName, false); + } + catch (IOException e) + { + errorHandler.error("setFile(" + fileName + ", false) call failed."); + } + + } + + /** + * Renames file <code>from</code> to file <code>to</code>. It also checks for existence of target file and deletes + * if it does. + */ + protected void rollFile(String from, String to, boolean compress) + { + if (from.equals(to)) + { + if (compress) + { + LogLog.error("Attempting to compress file with same output name."); + } + + return; + } + + File target = new File(to); + + File file = new File(from); + // Perform Roll by renaming + if (!file.getPath().equals(target.getPath())) + { + file.renameTo(target); + } + + // Compress file after it has been moved out the way... this is safe + // as it will gain a .gz ending and we can then safely delete this file + // as it will not be the statically named value. + if (compress) + { + compress(target); + } + + LogLog.debug(from + " -> " + to); + } + + private void compress(File target) + { + if (compressAsync) + { + synchronized (_compress) + { + _compress.offer(new CompressJob(target, target)); + } + + startCompression(); + } + else + { + doCompress(target, target); + } + } + + private void startCompression() + { + if (_compressing.compareAndSet(false, true)) + { + executor.execute(compressor); + } + } + + /** Delete's the specified file if it exists */ + protected void deleteFile(String fileName) + { + File file = compress ? new File(fileName + COMPRESS_EXTENSION) : new File(fileName); + if (file.exists()) + { + file.delete(); + } + } + + /** + * Implements roll overs base on file size. + * + * <p>If the maximum number of size based backups is reached (<code>curSizeRollBackups == maxSizeRollBackups</code) + * then the oldest file is deleted -- it's index determined by the sign of countDirection.<br> If + * <code>countDirection</code> < 0, then files {<code>File.1</code>, ..., <code>File.curSizeRollBackups -1</code>} + * are renamed to {<code>File.2</code>, ..., <code>File.curSizeRollBackups</code>}. Moreover, <code>File</code> is + * renamed <code>File.1</code> and closed.<br> + * + * A new file is created to receive further log output. + * + * <p>If <code>maxSizeRollBackups</code> is equal to zero, then the <code>File</code> is truncated with no backup + * files created. + * + * <p>If <code>maxSizeRollBackups</code> < 0, then <code>File</code> is renamed if needed and no files are deleted. + */ + + // synchronization not necessary since doAppend is alreasy synched + protected void rollOverSize() + { + File file; + + this.closeFile(); // keep windows happy. + + LogLog.debug("rolling over count=" + ((CountingQuietWriter) qw).getCount()); + LogLog.debug("maxSizeRollBackups = " + maxSizeRollBackups); + LogLog.debug("curSizeRollBackups = " + curSizeRollBackups); + LogLog.debug("countDirection = " + countDirection); + + // If maxBackups <= 0, then there is no file renaming to be done. + if (maxSizeRollBackups != 0) + { + rollFile(); + } + + try + { + // This will also close the file. This is OK since multiple + // close operations are safe. + this.setFile(baseFileName, false); + } + catch (IOException e) + { + LogLog.error("setFile(" + fileName + ", false) call failed.", e); + } + } + + /** + * Perform file Rollover ensuring the countDirection is applied along with + * the other options + */ + private void rollFile() + { + LogLog.debug("CD="+countDirection+",start"); + if (countDirection < 0) + { + // If we haven't rolled yet then validate we have the right value + // for curSizeRollBackups + if (curSizeRollBackups == 0) + { + //Validate curSizeRollBackups + curSizeRollBackups = countFileIndex(fileName); + // decrement to offset the later increment + curSizeRollBackups--; + } + + // If we are not keeping an infinite set of backups the delete oldest + if (maxSizeRollBackups > 0) + { + LogLog.debug("CD=-1,curSizeRollBackups:"+curSizeRollBackups); + LogLog.debug("CD=-1,maxSizeRollBackups:"+maxSizeRollBackups); + + // Delete the oldest file. + // curSizeRollBackups is never -1 so infinite backups are ok here + if ((curSizeRollBackups - maxSizeRollBackups) >= 0) + { + //The oldest file is the one with the largest number + // as the 0 is always fileName + // which moves to fileName.1 etc. + LogLog.debug("CD=-1,deleteFile:"+curSizeRollBackups); + deleteFile(fileName + '.' + curSizeRollBackups); + // decrement to offset the later increment + curSizeRollBackups--; + } + } + // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} + for (int i = curSizeRollBackups; i >= 1; i--) + { + String oldName = (fileName + "." + i); + String newName = (fileName + '.' + (i + 1)); + + // Ensure that when compressing we rename the compressed archives + if (compress) + { + rollFile(oldName + COMPRESS_EXTENSION, newName + COMPRESS_EXTENSION, false); + } + else + { + rollFile(oldName, newName, false); + } + } + + curSizeRollBackups++; + // Rename fileName to fileName.1 + rollFile(fileName, fileName + ".1", compress); + + } // REMOVE This code branching for Alexander Cerna's request + else if (countDirection == 0) + { + // rollFile based on date pattern + now.setTime(System.currentTimeMillis()); + String newFile = fileName + sdf.format(now); + + // If we haven't rolled yet then validate we have the right value + // for curSizeRollBackups + if (curSizeRollBackups == 0) + { + //Validate curSizeRollBackups + curSizeRollBackups = countFileIndex(newFile); + // to balance the increment just coming up. as the count returns + // the next free number not the last used. + curSizeRollBackups--; + } + + // If we are not keeping an infinite set of backups the delete oldest + if (maxSizeRollBackups > 0) + { + // Don't prune older files if they exist just go for the last + // one based on our maxSizeRollBackups. This means we may have + // more files left on disk that maxSizeRollBackups if this value + // is adjusted between runs but that is an acceptable state. + // Otherwise we would have to check on startup that we didn't + // have more than maxSizeRollBackups and prune then. + + if (((curSizeRollBackups - maxSizeRollBackups) >= 0)) + { + LogLog.debug("CD=0,curSizeRollBackups:"+curSizeRollBackups); + LogLog.debug("CD=0,maxSizeRollBackups:"+maxSizeRollBackups); + + // delete the first and keep counting up. + int oldestFileIndex = curSizeRollBackups - maxSizeRollBackups + 1; + LogLog.debug("CD=0,deleteFile:"+oldestFileIndex); + deleteFile(newFile + '.' + oldestFileIndex); + } + } + + + String finalName = newFile; + + curSizeRollBackups++; + + // Add rollSize if it is > 0 + if (curSizeRollBackups > 0 ) + { + finalName = newFile + '.' + curSizeRollBackups; + + } + + rollFile(fileName, finalName, compress); + } + else + { // countDirection > 0 + // If we haven't rolled yet then validate we have the right value + // for curSizeRollBackups + if (curSizeRollBackups == 0) + { + //Validate curSizeRollBackups + curSizeRollBackups = countFileIndex(fileName); + // to balance the increment just coming up. as the count returns + // the next free number not the last used. + curSizeRollBackups--; + } + + // If we are not keeping an infinite set of backups the delete oldest + if (maxSizeRollBackups > 0) + { + LogLog.debug("CD=1,curSizeRollBackups:"+curSizeRollBackups); + LogLog.debug("CD=1,maxSizeRollBackups:"+maxSizeRollBackups); + + // Don't prune older files if they exist just go for the last + // one based on our maxSizeRollBackups. This means we may have + // more files left on disk that maxSizeRollBackups if this value + // is adjusted between runs but that is an acceptable state. + // Otherwise we would have to check on startup that we didn't + // have more than maxSizeRollBackups and prune then. + + if (((curSizeRollBackups - maxSizeRollBackups) >= 0)) + { + // delete the first and keep counting up. + int oldestFileIndex = curSizeRollBackups - maxSizeRollBackups + 1; + LogLog.debug("CD=1,deleteFile:"+oldestFileIndex); + deleteFile(fileName + '.' + oldestFileIndex); + } + } + + + curSizeRollBackups++; + + rollFile(fileName, fileName + '.' + curSizeRollBackups, compress); + + } + LogLog.debug("CD="+countDirection+",done"); + } + + + private int countFileIndex(String fileName) + { + return countFileIndex(fileName, true); + } + /** + * Use filename as a base name and find what count number we are up to by + * looking at the files in this format: + * + * <filename>.<count>[COMPRESS_EXTENSION] + * + * If a count value of 1 cannot be found then a directory listing is + * performed to try and identify if there is a valid value for <count>. + * + * + * @param fileName the basefilename to use + * @param checkBackupLocation should backupFilesToPath location be checked for existing backups + * @return int the next free index + */ + private int countFileIndex(String fileName, boolean checkBackupLocation) + { + String testFileName; + + // It is possible for index 1..n to be missing leaving n+1..n+1+m logs + // in this scenario we should still return n+1+m+1 + int index=1; + + testFileName = fileName + "." + index; + + // Bail out early if there is a problem with the file + if (new File(testFileName) == null + || new File(testFileName + COMPRESS_EXTENSION) == null) + + { + return index; + } + + // Check that we do not have the 1..n missing scenario + if (!(new File(testFileName).exists() + || new File(testFileName + COMPRESS_EXTENSION).exists())) + + { + int max=0; + String prunedFileName = new File(fileName).getName(); + + // Look through all files to find next index + if (new File(fileName).getParentFile() != null) + { + for (File file : new File(fileName).getParentFile().listFiles()) + { + String name = file.getName(); + + if (name.startsWith(prunedFileName) && !name.equals(prunedFileName)) + { + String parsedCount = name.substring(prunedFileName.length() + 1); + + if (parsedCount.endsWith(COMPRESS_EXTENSION)) + { + parsedCount = parsedCount.substring(0, parsedCount.indexOf(COMPRESS_EXTENSION)); + } + + try + { + max = Integer.parseInt(parsedCount); + + // if we got a good value then update our index value. + if (max > index) + { + // +1 as we want to return the next free value. + index = max + 1; + } + } + catch (NumberFormatException nfe) + { + //ignore it assume file doesn't exist. + } + } + } + } + + // Update testFileName + testFileName = fileName + "." + index; + } + + + while (new File(testFileName).exists() + || new File(testFileName + COMPRESS_EXTENSION).exists()) + { + index++; + testFileName = fileName + "." + index; + } + + if (checkBackupLocation && index == 1 && backupFilesToPath != null) + { + LogLog.debug("Trying backup location:"+backupFilesToPath + System.getProperty("file.separator") + fileName); + return countFileIndex(backupFilesToPath + System.getProperty("file.separator") + new File(fileName).getName(), false); + } + + return index; + } + + protected synchronized void doCompress(File from, File to) + { + String toFile; + if (backupFilesToPath == null) + { + toFile = to.getPath() + COMPRESS_EXTENSION; + } + else + { + toFile = backupFilesToPath + System.getProperty("file.separator") + to.getName() + COMPRESS_EXTENSION; + } + + File target = new File(toFile); + if (target.exists()) + { + LogLog.debug("deleting existing target file: " + target); + target.delete(); + } + + try + { + // Create the GZIP output stream + GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(target)); + + // Open the input file + FileInputStream in = new FileInputStream(from); + + // Transfer bytes from the input file to the GZIP output stream + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) + { + out.write(buf, 0, len); + } + + in.close(); + + // Complete the GZIP file + out.finish(); + out.close(); + // Remove old file. + from.delete(); + } + catch (IOException e) + { + if (target.exists()) + { + target.delete(); + } + + rollFile(from.getPath(), to.getPath(), false); + } + } + + private class CompressJob + { + File _from, _to; + + CompressJob(File from, File to) + { + _from = from; + _to = to; + } + + File getFrom() + { + return _from; + } + + File getTo() + { + return _to; + } + } + + Compressor compressor = null; + + Executor executor; + + private class Compressor implements Runnable + { + public void run() + { + boolean running = true; + while (running) + { + CompressJob job = _compress.poll(); + + doCompress(job.getFrom(), job.getTo()); + + synchronized (_compress) + { + if (_compress.isEmpty()) + { + running = false; + _compressing.set(false); + } + } + } + + } + } +} |