diff options
-rw-r--r-- | qpid/java/broker/etc/log4j.xml | 10 | ||||
-rw-r--r-- | qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java | 710 |
2 files changed, 719 insertions, 1 deletions
diff --git a/qpid/java/broker/etc/log4j.xml b/qpid/java/broker/etc/log4j.xml index 0cf2579137..e0c4aa98e8 100644 --- a/qpid/java/broker/etc/log4j.xml +++ b/qpid/java/broker/etc/log4j.xml @@ -21,9 +21,17 @@ --> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> - <appender name="FileAppender" class="org.apache.log4j.FileAppender"> + <appender name="FileAppender" class="org.apache.log4j.QpidCompositeRollingAppender"> + <param name="staticLogFileName" value="false"/> <param name="File" value="${QPID_WORK}/log/${logprefix}qpid${logsuffix}.log"/> <param name="Append" value="false"/> + <!-- Change the direction so newer files have bigger numbers --> + <!-- So log.1 is written then log.2 etc This prevents a lot of file renames at log rollover --> + <param name="CountDirection" value="1"/> + <!--param name="MaxFileSize" value="1000"/ Use default 10MB --> + <param name="DatePattern" value="'.'yyyy-MM-dd-HH-mm"/> + <!-- Unlimited number of backups --> + <param name="MaxSizeRollBackups" value="-1"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%t %-5p %c{2} - %m%n"/> diff --git a/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java b/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java new file mode 100644 index 0000000000..171e1cb155 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java @@ -0,0 +1,710 @@ +/* + * 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.OptionConverter; +import org.apache.log4j.helpers.CountingQuietWriter; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.spi.LoggingEvent; + +import java.util.Date; +import java.text.SimpleDateFormat; +import java.io.IOException; +import java.io.Writer; +import java.io.File; + +/** + * <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; + + /** 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)); + } + + /** + * 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() { + + curSizeRollBackups = 0; + 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(); + + super.activateOptions(); + + if (rollDate && fileName != null && scheduledFilename == null) + scheduledFilename = fileName + sdf.format(now); + } + + /** + Rollover the file(s) to date/time tagged file(s). + Opens the new file (through setFile) and resets curSizeRollBackups. + */ + protected void rollOverTime() { + + curTimeRollBackups++; + + //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); + } + + rollFile(fileName, scheduledFilename); + } + + 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 static void rollFile(String from, String to) { + File target = new File(to); + if (target.exists()) { + LogLog.debug("deleting existing target file: " + target); + target.delete(); + } + + File file = new File(from); + file.renameTo(target); + LogLog.debug(from +" -> "+ to); + } + + /** 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))); + } + + curSizeRollBackups++; + // Rename fileName to fileName.1 + rollFile(fileName, fileName + ".1"); + + } //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); + } + 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); + } + } + } + + 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); + } + } + +} |