| /* |
| * |
| * 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); |
| |
| /** 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); |
| if (countDirection > 0) |
| { |
| scheduledFilename = fileName = fileName + '.' + (++curSizeRollBackups); |
| } |
| } |
| |
| 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() |
| { |
| |
| if (zeroBased) |
| { |
| curSizeRollBackups = -1; |
| } |
| |
| curTimeRollBackups = 0; |
| |
| // part A starts here |
| String filter; |
| if (staticLogFileName || !rollDate) |
| { |
| filter = baseFileName + ".*"; |
| } |
| else |
| { |
| filter = scheduledFilename + ".*"; |
| } |
| |
| File f = new File(baseFileName); |
| f = f.getParentFile(); |
| if (f == null) |
| { |
| f = new File("."); |
| } |
| |
| LogLog.debug("Searching for existing files in: " + f); |
| String[] files = f.list(); |
| |
| if (files != null) |
| { |
| for (int i = 0; i < files.length; i++) |
| { |
| if (!files[i].startsWith(baseFileName)) |
| { |
| continue; |
| } |
| |
| int index = files[i].lastIndexOf("."); |
| |
| if (staticLogFileName) |
| { |
| int endLength = files[i].length() - index; |
| if ((baseFileName.length() + endLength) != files[i].length()) |
| { |
| // file is probably scheduledFilename + .x so I don't care |
| continue; |
| } |
| } |
| |
| try |
| { |
| int backup = Integer.parseInt(files[i].substring(index + 1, files[i].length())); |
| LogLog.debug("From file: " + files[i] + " -> " + backup); |
| if (backup > curSizeRollBackups) |
| { |
| curSizeRollBackups = backup; |
| } |
| } |
| catch (Exception e) |
| { |
| // this happens when file.log -> file.log.yyyy-mm-dd which is normal |
| // when staticLogFileName == false |
| LogLog.debug("Encountered a backup file not ending in .x " + files[i]); |
| } |
| } |
| } |
| |
| LogLog.debug("curSizeRollBackups starts at: " + curSizeRollBackups); |
| // 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. |
| |
| // delete the old stuff here |
| |
| if (staticLogFileName) |
| { |
| /* Compute filename, but only if datePattern is specified */ |
| if (datePattern == null) |
| { |
| errorHandler.error("Missing DatePattern option in rollOver()."); |
| |
| return; |
| } |
| |
| // is the new file name equivalent to the 'current' one |
| // something has gone wrong if we hit this -- we should only |
| // roll over if the new file will be different from the old |
| String dateFormat = sdf.format(now); |
| if (scheduledFilename.equals(fileName + dateFormat)) |
| { |
| errorHandler.error("Compare " + scheduledFilename + " : " + fileName + dateFormat); |
| |
| return; |
| } |
| |
| // close current file, and rename it to datedFilename |
| this.closeFile(); |
| |
| // we may have to roll over a large number of backups here |
| String from, to; |
| for (int i = 1; i <= curSizeRollBackups; i++) |
| { |
| from = fileName + '.' + i; |
| to = scheduledFilename + '.' + i; |
| rollFile(from, to, false); |
| } |
| |
| rollFile(fileName, scheduledFilename, compress); |
| } |
| else |
| { |
| if (compress) |
| { |
| compress(fileName); |
| } |
| } |
| |
| try |
| { |
| // This will also close the file. This is OK since multiple |
| // close operations are safe. |
| 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.debug("Attempting to compress file with same output name."); |
| } |
| |
| return; |
| } |
| |
| File target = new File(to); |
| if (target.exists()) |
| { |
| LogLog.debug("deleting existing target file: " + target); |
| target.delete(); |
| } |
| |
| File file = new File(from); |
| if (compress) |
| { |
| compress(file, target); |
| } |
| else |
| { |
| if (!file.getPath().equals(target.getPath())) |
| { |
| file.renameTo(target); |
| } |
| } |
| |
| LogLog.debug(from + " -> " + to); |
| } |
| |
| protected void compress(String file) |
| { |
| File f = new File(file); |
| compress(f, f); |
| } |
| |
| private void compress(File from, File target) |
| { |
| if (compressAsync) |
| { |
| synchronized (_compress) |
| { |
| _compress.offer(new CompressJob(from, target)); |
| } |
| |
| startCompression(); |
| } |
| else |
| { |
| doCompress(from, target); |
| } |
| } |
| |
| private void startCompression() |
| { |
| if (_compressing.compareAndSet(false, true)) |
| { |
| executor.execute(compressor); |
| } |
| } |
| |
| /** Delete's the specified file if it exists */ |
| protected static void deleteFile(String fileName) |
| { |
| File file = 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) |
| { |
| |
| if (countDirection < 0) |
| { |
| // Delete the oldest file, to keep Windows happy. |
| if (curSizeRollBackups == maxSizeRollBackups) |
| { |
| deleteFile(fileName + '.' + maxSizeRollBackups); |
| curSizeRollBackups--; |
| } |
| |
| // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} |
| for (int i = curSizeRollBackups; i >= 1; i--) |
| { |
| rollFile((fileName + "." + i), (fileName + '.' + (i + 1)), 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 |
| curSizeRollBackups++; |
| now.setTime(System.currentTimeMillis()); |
| scheduledFilename = fileName + sdf.format(now); |
| rollFile(fileName, scheduledFilename, compress); |
| } |
| else |
| { // countDirection > 0 |
| if ((curSizeRollBackups >= maxSizeRollBackups) && (maxSizeRollBackups > 0)) |
| { |
| // delete the first and keep counting up. |
| int oldestFileIndex = curSizeRollBackups - maxSizeRollBackups + 1; |
| deleteFile(fileName + '.' + oldestFileIndex); |
| } |
| |
| if (staticLogFileName) |
| { |
| curSizeRollBackups++; |
| rollFile(fileName, fileName + '.' + curSizeRollBackups, compress); |
| } |
| else |
| { |
| if (compress) |
| { |
| compress(fileName); |
| } |
| } |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| protected synchronized void doCompress(File from, File to) |
| { |
| String toFile; |
| if (backupFilesToPath == null) |
| { |
| toFile = to.getPath() + ".gz"; |
| } |
| else |
| { |
| toFile = backupFilesToPath + System.getProperty("file.separator") + to.getName() + ".gz"; |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| } |
| } |
| } |