Changed to use RollingFileAppender to allow for log file names to end with a log...
authorlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Wed, 22 Jun 2005 23:50:04 +0000 (23:50 +0000)
committerlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Wed, 22 Jun 2005 23:50:04 +0000 (23:50 +0000)
Fixes bug 371

git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@1662 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/edu/internet2/middleware/shibboleth/log/LoggingInitializer.java
src/edu/internet2/middleware/shibboleth/log/RollingFileAppender.java [new file with mode: 0644]

index 357dbd0..7aff252 100644 (file)
@@ -19,7 +19,6 @@ package edu.internet2.middleware.shibboleth.log;
 import java.io.IOException;
 
 import org.apache.log4j.ConsoleAppender;
-import org.apache.log4j.DailyRollingFileAppender;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 import org.apache.log4j.PatternLayout;
@@ -50,6 +49,11 @@ import edu.internet2.middleware.shibboleth.idp.IdPConfig;
  */
 public class LoggingInitializer {
 
+    /**
+     * The log file extension
+     */
+    private static String logFileExtension = ".log";
+    
        /**
         * Log message layout pattern for the transaction log
         */
@@ -155,12 +159,12 @@ public class LoggingInitializer {
                if (location == null) { throw new ShibbolethConfigurationException(
                                "No log file location attribute specified in TransactionLog element"); }
 
-               DailyRollingFileAppender appender = null;
+               RollingFileAppender appender = null;
                try {
                        String logPath = new ShibResource(location, LoggingInitializer.class).getFile().getCanonicalPath();
                        PatternLayout messageLayout = new PatternLayout(txLogLayoutPattern);
 
-                       appender = new DailyRollingFileAppender(messageLayout, logPath, txLogAppenderDatePattern);
+                       appender = new RollingFileAppender(messageLayout, logPath, txLogAppenderDatePattern, logFileExtension);
                        appender.setName("shibboleth-transaction");
                } catch (Exception e) {
                        throw new ShibbolethConfigurationException("<TransactionLog location=\"" + location
@@ -211,12 +215,12 @@ public class LoggingInitializer {
                if (location == null) { throw new ShibbolethConfigurationException(
                                "No log file location attribute specified in ErrorLog element"); }
 
-               DailyRollingFileAppender appender = null;
+               RollingFileAppender appender = null;
                try {
                        String logPath = new ShibResource(location, LoggingInitializer.class).getFile().getCanonicalPath();
                        PatternLayout messageLayout = new PatternLayout(sysLogLayoutPattern);
 
-                       appender = new DailyRollingFileAppender(messageLayout, logPath, sysLogAppenderDatePattern);
+                       appender = new RollingFileAppender(messageLayout, logPath, sysLogAppenderDatePattern, logFileExtension);
                        appender.setName("shibboleth-error");
                } catch (Exception e) { // catch any exception
                        throw new ShibbolethConfigurationException("<ErrorLog location=\"" + location
diff --git a/src/edu/internet2/middleware/shibboleth/log/RollingFileAppender.java b/src/edu/internet2/middleware/shibboleth/log/RollingFileAppender.java
new file mode 100644 (file)
index 0000000..454a34f
--- /dev/null
@@ -0,0 +1,381 @@
+
+package edu.internet2.middleware.shibboleth.log;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.log4j.FileAppender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * A minor refactoring of Log4J's DailyRollingFileAppender. The log4j appender does not provide an easy mechanism for
+ * having a file name of name.date.extension, instead it wants to do name.extension.date which on some platforms can be
+ * a pain. This appender is meant to handle this case.
+ * 
+ * The file appender will create a file called filename.extension, then it will roll over the files to
+ * filename.date.extension. The default date pattern is "'.'yyyy-MM-dd" (i.e. daily roll over) and the default file
+ * extnsion is '.log'.
+ * 
+ * @author Chad La Joie
+ */
+/*
+ * This class borrows extensively from the Log4J DailyRollingFileAppender written by Eirik Lygre and Ceki Gulcu.
+ */
+public class RollingFileAppender
+        extends FileAppender {
+
+    // The code assumes that the following 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;
+
+    /**
+     * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" meaning daily rollover.
+     */
+    private String datePattern = "'.'yyyy-MM-dd";
+
+    /**
+     * The log file will be renamed to the value of the scheduledFilename variable when the next interval is entered.
+     * For example, if the rollover period is one hour, the log file will be renamed to the value of "scheduledFilename"
+     * at the beginning of the next hour.
+     * 
+     * The precise time when a rollover occurs depends on logging activity.
+     */
+    private String scheduledFilename;
+
+    /**
+     * The next time we estimate a rollover should occur.
+     */
+    private long nextCheck = System.currentTimeMillis() - 1;
+
+    /**
+     * Current date
+     */
+    private Date now = new Date();
+
+    /**
+     * The date pattern
+     */
+    private SimpleDateFormat sdf;
+
+    /**
+     * Calendar for determing roll over information
+     */
+    private RollingCalendar rc = new RollingCalendar();
+
+    /**
+     * How often to check for roll over
+     */
+    int checkPeriod = TOP_OF_TROUBLE;
+
+    /**
+     * The gmtTimeZone is used only in computeCheckPeriod() method.
+     */
+    static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
+
+    /**
+     * The file extension
+     */
+    private String fileExtension = ".log";
+
+    /**
+     * The file name without the date or extension components.
+     */
+    private String simpleFileName;
+
+    /**
+     * Default constructor
+     */
+    public RollingFileAppender() {
+
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param layout the log entry layout pattern
+     * @param filename the file name
+     * @param datePattern the date pattern used to determine rolling behavior
+     * @param fileExtension the file extension to post-pend to log file name
+     * 
+     * @throws IOException thrown if the file can not be created
+     */
+    public RollingFileAppender(Layout layout, String filename, String datePattern, String fileExtension)
+            throws IOException {
+        super(layout, filename + fileExtension, true);
+        simpleFileName = filename;
+        setDatePattern(datePattern);
+        setFileExtension(fileExtension);
+        activateOptions();
+    }
+
+    /**
+     * Gets the extension post-pended to the file name.
+     * 
+     * @return the log file's extension
+     */
+    public String getFileExtension() {
+        return fileExtension;
+    }
+
+    /**
+     * Sets the extension post-pended to the file name.
+     * 
+     * @param extension the log file's extension
+     */
+    public void setFileExtension(String extension) {
+        fileExtension = extension;
+    }
+
+    /**
+     * The <b>DatePattern</b> takes a string in the same format as expected by {@link SimpleDateFormat}. This options
+     * determines the rollover schedule.
+     * 
+     * @param pattern the rollover date pattern
+     */
+    public void setDatePattern(String pattern) {
+        datePattern = pattern;
+    }
+
+    /**
+     * Returns the value of the <b>DatePattern</b> option.
+     * 
+     * @return the rollover date pattern
+     */
+    public String getDatePattern() {
+        return datePattern;
+    }
+
+    public void activateOptions() {
+        super.activateOptions();
+        if (datePattern != null && fileName != null) {
+            now.setTime(System.currentTimeMillis());
+            sdf = new SimpleDateFormat(datePattern);
+            int type = computeCheckPeriod();
+            printPeriodicity(type);
+            rc.setType(type);
+            File file = new File(fileName);
+            scheduledFilename = simpleFileName + sdf.format(new Date(file.lastModified())) + fileExtension;
+
+        } else {
+            LogLog.error("Either File or DatePattern options are not set for appender [" + name + "].");
+        }
+    }
+
+    void printPeriodicity(int type) {
+        switch (type) {
+            case TOP_OF_MINUTE:
+                LogLog.debug("Appender [" + name + "] to be rolled every minute.");
+                break;
+            case TOP_OF_HOUR:
+                LogLog.debug("Appender [" + name + "] to be rolled on top of every hour.");
+                break;
+            case HALF_DAY:
+                LogLog.debug("Appender [" + name + "] to be rolled at midday and midnight.");
+                break;
+            case TOP_OF_DAY:
+                LogLog.debug("Appender [" + name + "] to be rolled at midnight.");
+                break;
+            case TOP_OF_WEEK:
+                LogLog.debug("Appender [" + name + "] to be rolled at start of week.");
+                break;
+            case TOP_OF_MONTH:
+                LogLog.debug("Appender [" + name + "] to be rolled at start of every month.");
+                break;
+            default:
+                LogLog.warn("Unknown periodicity for appender [" + name + "].");
+        }
+    }
+
+    // This method computes the roll over period by looping over the
+    // periods, starting with the shortest, and stopping when the r0 is
+    // different from from r1, where r0 is the epoch formatted according
+    // the datePattern (supplied by the user) and r1 is the
+    // epoch+nextMillis(i) formatted according to datePattern. All date
+    // formatting is done in GMT and not local format because the test
+    // logic is based on comparisons relative to 1970-01-01 00:00:00
+    // GMT (the epoch).
+
+    int computeCheckPeriod() {
+        RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.ENGLISH);
+        // 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++) {
+                SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
+                simpleDateFormat.setTimeZone(gmtTimeZone); // do all date formatting in GMT
+                String r0 = simpleDateFormat.format(epoch);
+                rollingCalendar.setType(i);
+                Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
+                String r1 = simpleDateFormat.format(next);
+                // System.out.println("Type = "+i+", r0 = "+r0+", r1 = "+r1);
+                if (r0 != null && r1 != null && !r0.equals(r1)) {
+                    return i;
+                }
+            }
+        }
+        return TOP_OF_TROUBLE; // Deliberately head for trouble...
+    }
+
+    /**
+     * Rollover the current file to a new file.
+     */
+    void rollOver() {
+
+        /* Compute filename, but only if datePattern is specified */
+        if (datePattern == null) {
+            errorHandler.error("Missing DatePattern option in rollOver().");
+            return;
+        }
+
+        String datedFilename = simpleFileName + sdf.format(now) + fileExtension;
+        // It is too early to roll over because we are still within the
+        // bounds of the current interval. Rollover will occur once the
+        // next interval is reached.
+        if (scheduledFilename.equals(datedFilename)) {
+            return;
+        }
+
+        // close current file, and rename it to datedFilename
+        this.closeFile();
+
+        File target = new File(scheduledFilename);
+        if (target.exists()) {
+            target.delete();
+        }
+
+        File file = new File(fileName);
+        boolean result = file.renameTo(target);
+        if (result) {
+            LogLog.debug(fileName + " -> " + scheduledFilename);
+        } else {
+            LogLog.error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "].");
+        }
+
+        try {
+            // This will also close the file. This is OK since multiple
+            // close operations are safe.
+            this.setFile(fileName, false, this.bufferedIO, this.bufferSize);
+        }
+        catch (IOException e) {
+            errorHandler.error("setFile(" + fileName + ", false) call failed.");
+        }
+        scheduledFilename = datedFilename;
+    }
+
+    /**
+     * This method differentiates DailyRollingFileAppender from its super class.
+     * 
+     * <p>
+     * Before actually logging, this method will check whether it is time to do a rollover. If it is, it will schedule
+     * the next rollover time and then rollover.
+     */
+    protected void subAppend(LoggingEvent event) {
+        long n = System.currentTimeMillis();
+        if (n >= nextCheck) {
+            now.setTime(n);
+            nextCheck = rc.getNextCheckMillis(now);
+            rollOver();
+        }
+        super.subAppend(event);
+    }
+}
+
+/**
+ * RollingCalendar is a helper class to DailyRollingFileAppender. Given a periodicity type and the current time, it
+ * computes the start of the next interval.
+ */
+class RollingCalendar
+        extends GregorianCalendar {
+
+    int type = RollingFileAppender.TOP_OF_TROUBLE;
+
+    RollingCalendar() {
+        super();
+    }
+
+    RollingCalendar(TimeZone tz, Locale locale) {
+        super(tz, locale);
+    }
+
+    void setType(int type) {
+        this.type = type;
+    }
+
+    public long getNextCheckMillis(Date now) {
+        return getNextCheckDate(now).getTime();
+    }
+
+    public Date getNextCheckDate(Date now) {
+        this.setTime(now);
+
+        switch (type) {
+            case RollingFileAppender.TOP_OF_MINUTE:
+                this.set(Calendar.SECOND, 0);
+                this.set(Calendar.MILLISECOND, 0);
+                this.add(Calendar.MINUTE, 1);
+                break;
+            case RollingFileAppender.TOP_OF_HOUR:
+                this.set(Calendar.MINUTE, 0);
+                this.set(Calendar.SECOND, 0);
+                this.set(Calendar.MILLISECOND, 0);
+                this.add(Calendar.HOUR_OF_DAY, 1);
+                break;
+            case RollingFileAppender.HALF_DAY:
+                this.set(Calendar.MINUTE, 0);
+                this.set(Calendar.SECOND, 0);
+                this.set(Calendar.MILLISECOND, 0);
+                int hour = get(Calendar.HOUR_OF_DAY);
+                if (hour < 12) {
+                    this.set(Calendar.HOUR_OF_DAY, 12);
+                } else {
+                    this.set(Calendar.HOUR_OF_DAY, 0);
+                    this.add(Calendar.DAY_OF_MONTH, 1);
+                }
+                break;
+            case RollingFileAppender.TOP_OF_DAY:
+                this.set(Calendar.HOUR_OF_DAY, 0);
+                this.set(Calendar.MINUTE, 0);
+                this.set(Calendar.SECOND, 0);
+                this.set(Calendar.MILLISECOND, 0);
+                this.add(Calendar.DATE, 1);
+                break;
+            case RollingFileAppender.TOP_OF_WEEK:
+                this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek());
+                this.set(Calendar.HOUR_OF_DAY, 0);
+                this.set(Calendar.SECOND, 0);
+                this.set(Calendar.MILLISECOND, 0);
+                this.add(Calendar.WEEK_OF_YEAR, 1);
+                break;
+            case RollingFileAppender.TOP_OF_MONTH:
+                this.set(Calendar.DATE, 1);
+                this.set(Calendar.HOUR_OF_DAY, 0);
+                this.set(Calendar.SECOND, 0);
+                this.set(Calendar.MILLISECOND, 0);
+                this.add(Calendar.MONTH, 1);
+                break;
+            default:
+                throw new IllegalStateException("Unknown periodicity type.");
+        }
+        return getTime();
+    }
+}
\ No newline at end of file