/* * * 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 org.apache.log4j.helpers.CountingQuietWriter; import org.apache.log4j.helpers.LogLog; import org.apache.log4j.helpers.OptionConverter; import org.apache.log4j.spi.LoggingEvent; 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; /** *

CompositeRollingAppender combines RollingFileAppender and DailyRollingFileAppender
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 * rollingStyle.

To use CompositeRollingAppender to roll log files as they reach a certain size * (like RollingFileAppender), set rollingStyle=1 (@see config.size)
To use CompositeRollingAppender to roll log * files at certain time intervals (daily for example), set rollingStyle=2 and a datePattern (@see config.time)
To * have CompositeRollingAppender roll log files at a certain size AND rename those according to time intervals, set * rollingStyle=3 (@see config.composite)
* *

A of few additional optional features have been added:
-- Attach date pattern for current log file (@see * staticLogFileName)
-- Backup number increments for newer files (@see countDirection)
-- Infinite number of * backups by file size (@see maxSizeRollBackups)

A few notes and warnings: For large or infinite number of * backups countDirection {@literal >} 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.

A maximum number of backups based on * date/time boundaries would be nice but is not yet implemented.
* * @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 */ private Date now = new Date(); private SimpleDateFormat sdf; /** Helper class to determine next rollover time */ private RollingCalendar rc = new RollingCalendar(); private long maxFileSize = 10 * 1024 * 1024; private int maxSizeRollBackups = 0; private int curSizeRollBackups = 0; private int maxTimeRollBackups = -1; private int curTimeRollBackups = 0; private int countDirection = -1; private int rollingStyle = BY_COMPOSITE; private boolean rollDate = true; private boolean rollSize = true; private boolean staticLogFileName = true; private String baseFileName; private boolean compress = false; private boolean compressAsync = false; private boolean zeroBased = false; private String backupFilesToPath = null; private final ConcurrentLinkedQueue _compress = new ConcurrentLinkedQueue(); private AtomicBoolean _compressing = new AtomicBoolean(false); private static final String COMPRESS_EXTENSION = ".gz"; /** The default constructor does nothing. */ public QpidCompositeRollingAppender() { } /** * Instantiate a CompositeRollingAppender and open the file designated by filename. 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 filename. The opened filename * will become the ouput destination for this appender. * *

If the append parameter is true, the file will be appended to. Otherwise, the file desginated by * filename 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 filename. 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 filename. The opened filename * will become the output destination for this appender. * *

The file will be appended to. DatePattern is default. */ public QpidCompositeRollingAppender(Layout layout, String filename) throws IOException { super(layout, filename); } /** * The DatePattern 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 DatePattern option. */ public String getDatePattern() { return datePattern; } /** There is zero backup files by default. */ /** Returns the value of the maxSizeRollBackups 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; } /** *

Set the maximum number of backup files to keep around based on file size. * *

The MaxSizeRollBackups 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 MaxFileSize. 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. * *

The maximum applies 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. * *

This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter * taking a long argument from the setter taking a String 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. * *

This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter * taking a long argument from the setter taking a String 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. * *

In configuration files, the MaxFileSize 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 verbatim 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); 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 staticLogFileName 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()); } } /** * By default newer files have lower numbers. (countDirection {@literal <} 0) ie. log.1 is most recent, log.5 is the 5th * backup, etc... countDirection {@literal >} 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 {@literal >} 0 to reduce rollOver costs. */ public int getCountDirection() { return countDirection; } public void setCountDirection(int direction) { countDirection = direction; } /** Style of rolling to Use. BY_SIZE (1), BY_DATE(2), BY COMPOSITE(3) */ 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 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; } /** Path provided in configuration. Used for moving backup files to */ 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 existing conditions at time of activateOptions. The following is done:
*
A) determine curSizeRollBackups
B) determine curTimeRollBackups (not implemented)
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 * existingInit 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(); 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 from to file to. 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; } if (backupFilesToPath != null) { to = backupFilesToPath + System.getProperty("file.separator") + new File(to).getName(); } 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 the given file that is prepended with the relative path to the log * directory. * * Compress is enabled check for file with COMPRESS_EXTENSION(.gz) * * if backupFilesToPath is set then check in this directory not the * main log directory. */ protected void deleteFile(String relativeFileName) { String fileName=""; // If we have configured a backup location then we should look in there // for the file we are trying to delete if (backupFilesToPath != null) { File file = new File(relativeFileName); fileName = backupFilesToPath + System.getProperty("file.separator") + file.getName(); } // If we are compressing the at the extension if (compress) { fileName += COMPRESS_EXTENSION; } File file = new File(fileName); if (file.exists()) { file.delete(); } } /** * Implements roll overs base on file size. * *

If the maximum number of size based backups is reached (curSizeRollBackups {@literal ==} maxSizeRollBackups) * then the oldest file is deleted -- it's index determined by the sign of countDirection.
If * countDirection {@literal <} 0, then files {File.1, ..., File.curSizeRollBackups -1} * are renamed to {File.2, ..., File.curSizeRollBackups}. Moreover, File is * renamed File.1 and closed.
* * A new file is created to receive further log output. * *

If maxSizeRollBackups is equal to zero, then the File is truncated with no backup * files created. * *

If maxSizeRollBackups {@literal <} 0, then File is renamed if needed and no files are deleted. */ // synchronization not necessary since doAppend is already 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: * * .[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 . * * * @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; toFile = to.getPath() + 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)); try { // Open the input file FileInputStream in = new FileInputStream(from); try { // 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); } } finally { in.close(); } // Complete the GZIP file out.finish(); } finally { out.close(); } // Remove old file. from.delete(); } catch (IOException e) { if (target.exists()) { target.delete(); } rollFile(from.getPath(), to.getPath(), false); } } /** The default maximum file size is 10MB. */ protected long getMaxFileSize() { return maxFileSize; } /** How many sized based backups have been made so far */ protected int getCurSizeRollBackups() { return curSizeRollBackups; } protected void setCurSizeRollBackups(int curSizeRollBackups) { this.curSizeRollBackups = curSizeRollBackups; } /** not yet implemented */ protected int getMaxTimeRollBackups() { return maxTimeRollBackups; } protected void setMaxTimeRollBackups(int maxTimeRollBackups) { this.maxTimeRollBackups = maxTimeRollBackups; } protected int getCurTimeRollBackups() { return curTimeRollBackups; } protected void setCurTimeRollBackups(int curTimeRollBackups) { this.curTimeRollBackups = curTimeRollBackups; } protected boolean isRollDate() { return rollDate; } protected void setRollDate(boolean rollDate) { this.rollDate = rollDate; } protected boolean isRollSize() { return rollSize; } protected void setRollSize(boolean rollSize) { this.rollSize = rollSize; } /** * 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 isStaticLogFileName() { return staticLogFileName; } /** FileName provided in configuration. Used for rolling properly */ protected String getBaseFileName() { return baseFileName; } protected void setBaseFileName(String baseFileName) { this.baseFileName = baseFileName; } /** Do we want to .gz our backup files. */ protected boolean isCompress() { return compress; } protected void setCompress(boolean compress) { this.compress = compress; } /** Do we want to use a second thread when compressing our backup files. */ protected boolean isCompressAsync() { return compressAsync; } /** Do we want to start numbering files at zero. */ protected boolean isZeroBased() { return zeroBased; } protected void setBackupFilesToPath(String backupFilesToPath) { this.backupFilesToPath = backupFilesToPath; } private static class CompressJob { private File _from, _to; CompressJob(File from, File to) { _from = from; _to = to; } File getFrom() { return _from; } File getTo() { return _to; } } private Compressor compressor = null; private 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); } } } } } }