All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBus.util.logging.CalendarFileHandler Maven / Gradle / Ivy

//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later
// version.
//
// This library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
//
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA
// 02111-1307 USA
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright (C) 2001 - 2005, 2013. Charles W. Rapp.
// All Rights Reserved.
//

package net.sf.eBus.util.logging;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.StreamHandler;
import net.sf.eBus.util.TimerEvent;
import net.sf.eBus.util.TimerTask;
import net.sf.eBus.util.TimerTaskListener;

/**
 * Logs messages to a user-specified file, rolling over to a
 * new file at midnight. Log files are kept for only so many
 * days before {@code CalendarFileHandler} deletes them.
 * This retention limit is configurable but defaults to 10 days.
 * 

* The {@code CalendarFileHandler} uses three parameters to * generate the complete file name: *

 * <base name>.<date pattern>.<extension>
 * 
*
    *
  1. * Base Name: Store the log files here using this base * name. *

    * Example: {@code /var/log/app/app} *

  2. *
  3. * Date Pattern: Use this pattern to format the date * portion of the file name. *

    * The data pattern is passed to a * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)}. See * {@link java.text.SimpleDateFormat} for a detailed * explanation of valid date formats. *

  4. *
  5. * Extension: This is first part of the log file's * name. *

    * Example: log *

  6. *
*

* Given the base name {@code /var/log/eBus/eBus}, a date * pattern "ddMMyyyy" and extension {@code log}, the * July 15, 2001 log file name is * {@code /var/log/eBus/eBus.15072001.log} *

* Configuration: {@code CalendarFileHandler} * default configuration uses the following LogManager * properties. If the named properties are either not defined or * have invalid values, then the default settings are used. *

    *
  • * net.sf.eBus.util.logging.CalendarFileHandler.basename * (defaults to "./Logger") *
  • *
  • * net.sf.eBus.util.logging.CalendarFileHandler.pattern * (defaults to "yyyyMMdd") *
  • *
  • * net.sf.eBus.util.logging.CalendarFileHandler.extension * (defaults to "log") *
  • *
  • * net.sf.eBus.util.logging.CalendarFileHandler.days_kept * (defaults to 10 days) *
  • *
  • * net.sf.eBus.util.logging.CalendarFileHandler.formatter * (defaults to "net.sf.eBus.util.logging.PatternFormatter") *
  • *
  • * net.sf.eBus.util.logging.CalendarFileHandler.level * (defaults to system default) *
  • *
* * @author Charles Rapp */ public final class CalendarFileHandler extends StreamHandler implements TimerTaskListener { //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new {@link CalendarFileHandler} and configures * it according to {@code LogManager} configuration * properties. */ public CalendarFileHandler() { super (); _logRollTimer = new TimerTask(this); // Get the handler property settings. final LogManager manager = LogManager.getLogManager(); String value; int daysKept = DEFAULT_DAYS_KEPT; value = getProperty(BASENAME_KEY, DEFAULT_BASENAME, manager); _directory = dirname(value); _basename = basename(value); _extension = getProperty( EXTENSION_KEY, DEFAULT_EXTENSION, manager); _dateFormat = getProperty( PATTERN_KEY, DEFAULT_DATE_FORMAT, manager); // Get formatter class name and instantiate an instance. value = manager.getProperty(FORMATTER_KEY); if (value != null && value.length() > 0) { final Formatter formatter = createFormatter(value); if (formatter != null) { setFormatter(formatter); } } // Get the log level. Do nothing if not specified. value = manager.getProperty(LEVEL_KEY); if (value != null && value.length() > 0) { try { setLevel(Level.parse(value)); } catch (IllegalArgumentException | SecurityException jex) { // Ignore exceptions - use default level. } } // Get the days kept. Use default value if invalid. value = manager.getProperty(DAYS_KEPT_KEY); if (value != null && value.length() > 0) { try { daysKept = Integer.parseInt(value); } catch (Exception jex) {} } _daysKept = daysKept; // Finish up by setting the handler's output stream as // per the configuration. final OutputStream logStream = openLogStream( _directory, _basename, _dateFormat, _extension); if (logStream != null) { setOutputStream(logStream); } deleteLogFiles(); startMidnightTimer(_logRollTimer); } // end of CalendarFileHandler() /** * Creates a new {@link CalendarFileHandler} instance for * the specified base name, date format pattern, file name * extension and how long to keep the files around. * @param baseName where to put the log files. * @param datePattern date format * @param extension file name extension * @param daysKept how long the log files are kept around * (in days). * @exception IllegalArgumentException * if: *
    *
  • * if {@code baseName}, {@code datePattern} or * {@code extension} is {@code null}. *
  • *
  • * {@code baseName}, {@code datePattern} or * {@code extension} is an empty string. *
  • *
  • * {@code daysKept} is < {@link #MIN_DAYS_KEPT} * or > {@link #MAX_DAYS_KEPT}. *
  • *
  • * {@code datePattern} is an invalid date format * pattern as per * {@code java.text.SimpleDateFormat}. *
  • *
  • * {@code baseName} is in an unknown directory or * directory cannot be accessed. *
  • *
*/ public CalendarFileHandler(final String baseName, final String datePattern, final String extension, final int daysKept) throws IllegalArgumentException { super (); if (baseName == null) { throw ( new IllegalArgumentException("null baseName")); } else if (datePattern == null) { throw ( new IllegalArgumentException( "null datePattern")); } else if (extension == null) { throw ( new IllegalArgumentException("null extension")); } else if (baseName.length() == 0) { throw ( new IllegalArgumentException("empty baseName")); } else if (datePattern.length() == 0) { throw ( new IllegalArgumentException( "empty datePattern")); } else if (extension.length() == 0) { throw ( new IllegalArgumentException("empty extension")); } _directory = dirname(baseName); _basename = basename(baseName); _dateFormat = datePattern; _extension = extension; _daysKept = daysKept; _logRollTimer = new TimerTask(this); final File dir = new File(_directory); // Make sure the directory is valid. If not, then use // the current working directory. if (dir.exists() == false || dir.isDirectory() == false || dir.canWrite() == false) { throw ( new IllegalArgumentException( _directory + " is an invalid directory")); } // Get the handler property settings. final LogManager manager = LogManager.getLogManager(); String value; // Get formatter class name and instantiate an instance. value = manager.getProperty(FORMATTER_KEY); if (value != null && value.length() > 0) { final Formatter formatter = createFormatter(value); if (formatter != null) { setFormatter(formatter); } } // Get the log level. Do nothing if not specified. value = manager.getProperty(LEVEL_KEY); if (value != null && value.length() > 0) { try { setLevel(Level.parse(value)); } catch (IllegalArgumentException | SecurityException jex) { // Ignore exceptions - use default level. } } // Finish up by setting the handler's output stream as // per the configuration. final OutputStream logStream = openLogStream( _directory, _basename, _dateFormat, _extension); if (logStream != null) { setOutputStream(logStream); } deleteLogFiles(); startMidnightTimer(_logRollTimer); } // end of CalendarFileHandler(String, String, String, int) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // TimerTaskListener Interface Implementation. // /** * Time to roll over to the next log file. * @param task the roll file timer task. */ @Override public void handleTimeout(final TimerEvent task) { final OutputStream logStream = openLogStream( _directory, _basename, _dateFormat, _extension); LogRecord logRecord; logRecord = new LogRecord(Level.INFO, "Rolling log file."); logRecord.setSourceClassName( CalendarFileHandler.class.getName()); logRecord.setSourceMethodName("handleTimeout"); publish(logRecord); deleteLogFiles(); // Close the current log file and open the new log if (logStream != null) { setOutputStream(logStream); } logRecord = new LogRecord( Level.INFO, "Finished rolling log file."); logRecord.setSourceClassName( CalendarFileHandler.class.getName()); logRecord.setSourceMethodName("handleTimeout"); publish(logRecord); // Schedule the next log roll. // Why keep rescheduling? Why not schedule a repeating // timer task? // Because the local timezone might use // daylight savings time. _logRollTimer = new TimerTask(this); startMidnightTimer(_logRollTimer); return; } // end of handleTimeout(TimerEvent) // // end of TimerTaskListener Interface Implementation. //----------------------------------------------------------- /** * Flushes the output stream after {@link StreamHandler} * publishes the log record. {@code StreamHandler} does not * do this which means records are not seen in the log * file as they are published. * @param record Publish this log record to the log file. */ @Override public void publish(final LogRecord record) { super.publish(record); super.flush(); return; } // end of publish(LogRecord) // Performs the work of deleting out-of-date log files private void deleteLogFiles() { final Calendar calendar = Calendar.getInstance(); final File directory = new File(_directory); LogFileFilter logFilter; File[] logFiles; // Get today's date and subtract the days kept. // Then set the time to midnight (00:00:00 AM). // Delete any log files older than that date. calendar.add(Calendar.DAY_OF_MONTH, (_daysKept * -1)); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); // Create the log name filter. logFilter = new LogFileFilter( directory, _basename + ".", _dateFormat, "." + _extension, calendar.getTime()); // Now get the list of expired log files. logFiles = directory.listFiles(logFilter); // Are there any log files to be deleted? if (logFiles != null && logFiles.length > 0) { final java.util.Formatter buffer = new java.util.Formatter(); LogRecord logRecord; int index; buffer.format("Deleted the following log files:"); // Go through each of the log files. for (index = 0; index < logFiles.length; ++index) { buffer.format( "%n %s", logFiles[index].getName()); if (logFiles[index].delete() == false) { buffer.format(" failed"); } } logRecord = new LogRecord(Level.INFO, buffer.toString()); logRecord.setSourceClassName( CalendarFileHandler.class.getName()); logRecord.setSourceMethodName("handleTimeout"); publish(logRecord); } return; } // end of deleteLogFiles() // Opens an output stream for the calendar log file. private static OutputStream openLogStream( final String directory, final String basename, final String datePattern, final String extension) { OutputStream retval = null; try { final File logFile = new File( generateLogFilename( directory, basename, datePattern, extension)); retval = new FileOutputStream(logFile, true); } catch (IOException ioex) { // Return null. } return (retval); } // end of openLogStream(String, String, String, String) // Generates the log file name based on the given // information. private static String generateLogFilename( final String directory, final String basename, final String datePattern, final String extension) throws IOException { final SimpleDateFormat formatter = new SimpleDateFormat(datePattern); return ( String.format( "%s%c%s.%s.%s", directory, File.separatorChar, basename, formatter.format(new Date()), extension)); } // end of generateLogFilename(String, String, String) // Returns the named property or its default value. private static String getProperty(final String key, final String defaultValue, final LogManager manager) { String retval = manager.getProperty(key); if (retval == null || retval.length() == 0) { retval = defaultValue; } return (retval); } // end of getProperty(String, String, LogManager) // Creates this handler's formatter based on the specified // class name. @SuppressWarnings("unchecked") private static Formatter createFormatter( final String className) { Formatter retval = null; try { final Class formatterClass = (Class) Class.forName(className); final Constructor ctor = formatterClass.getDeclaredConstructor(); retval = ctor.newInstance(); } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException jex) { // Do nothing if any exception is caught. } return (retval); } // end of createFormatter(String) // Returns the directory portion of this baseName. private static String dirname(final String baseName) { final int index = baseName.lastIndexOf((int) '/'); String retval; if (index < 0) { retval = "."; } else { retval = baseName.substring(0, index); } return (retval); } // end of dirname(String) // Returns the file name portion. private static String basename(final String baseName) { final int index = baseName.lastIndexOf((int) '/'); String retval; if (index < 0) { retval = baseName; } else { retval = baseName.substring(index + 1); } return (retval); } // end of basename(String) // Starts the timer task to expire at midnight. When the // task executes, it rolls the log file to the new day // and deletes log files older than the configured days // kept. private void startMidnightTimer(final TimerTask task) { final Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_MONTH, 1); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); _Timer.schedule(task, calendar.getTime()); return; } // end of startMidnightTimer(TimerTask) //--------------------------------------------------------------- // Member data. // // The log file's info. private final String _directory; private final String _basename; private final String _dateFormat; private final String _extension; private final int _daysKept; // When this timer expires, roll the log files into the // gutter. private TimerTask _logRollTimer; //----------------------------------------------------------- // Statics. // private static Timer _Timer = new Timer("LogRollTimer", true); //----------------------------------------------------------- // Constants. // /** * Log files are placed in the application's current working * directory by default. */ public static final String DEFAULT_DIRECTORY = "."; /** * The log file's default base name is "Logger". */ public static final String DEFAULT_BASENAME = "Logger"; /** * The log file's default data format is "yyyyMMdd". * For July 4, 1776 the formatted string is "17760704". */ public static final String DEFAULT_DATE_FORMAT = "yyyyMMdd"; /** * The default file extension is "log". */ public static final String DEFAULT_EXTENSION = "log"; /** * The minimum number of days a log file is kept is 0. Which * means that the file is deleted as soon as the day ends. */ public static final int MIN_DAYS_KEPT = 0; /** * The maximum number of days a log file is kept is 96. * That's three months. That is plenty of time to * archive the file if necessary. */ public static final int MAX_DAYS_KEPT = 96; /** * Log files are kept for 10 days by default. */ public static final int DEFAULT_DAYS_KEPT = 10; // Configuration property keys. private static final String BASENAME_KEY = "net.sf.eBus.util.logging.CalendarFileHandler.basename"; private static final String PATTERN_KEY = "net.sf.eBus.util.logging.CalendarFileHandler.pattern"; private static final String EXTENSION_KEY = "net.sf.eBus.util.logging.CalendarFileHandler.extension"; private static final String DAYS_KEPT_KEY = "net.sf.eBus.util.logging.CalendarFileHandler.days_kept"; private static final String FORMATTER_KEY = "net.sf.eBus.util.logging.CalendarFileHandler.formatter"; private static final String LEVEL_KEY = "net.sf.eBus.util.logging.CalendarFileHandler.level"; //--------------------------------------------------------------- // Inner classes. // /** * This file name filter looks for log files in a specified * directory and whose names start and end with the specified * strings. */ private static final class LogFileFilter implements FilenameFilter { //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * All log files are placed in the same directory, have * the same base name and file name extension. * @param directory the log file directory. * @param basename the log file name's base name. * @param dateFormat the file name date format. * @param extension the log file name's extension. * @param expiration the log file expiration date. */ private LogFileFilter(final File directory, final String basename, final String dateFormat, final String extension, final Date expiration) { _directory = directory; _basename = basename; _basenameSize = _basename.length(); _dateFormatter = new SimpleDateFormat(dateFormat); _extension = extension; _extensionSize = _extension.length(); _expiration = expiration; _calendar = Calendar.getInstance(); return; } // end of LogFileFilter(File,String,String,String,Date) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // FilenameFilter Interface Implemenation. // /** * Returns {@code true} if {@code directory} * is the same as the logging directory and the file * {@code name} starts with the expected basename * and ends with the proper extension. * @param directory the file's directory. * @param name the file's name. * @return {@code true} if {@code directory} * is the same as the logging directory and the file * {@code name} starts with the expected basename * and ends with the proper extension. */ @Override public boolean accept(final File directory, final String name) { boolean retval = (directory.equals(_directory) == true && name.startsWith(_basename) && name.endsWith(_extension)); if (retval == true) { String dateSubstring; ParsePosition pos; Date logFileDate; // Get the date portion of the file name. dateSubstring = name.substring( _basenameSize, (name.length() - _extensionSize)); // Parse the log's name to get its date. pos = new ParsePosition(0); logFileDate = _dateFormatter.parse(dateSubstring, pos); if (logFileDate != null) { _calendar.setTime(logFileDate); _calendar.set(Calendar.HOUR_OF_DAY, 0); _calendar.set(Calendar.MINUTE, 0); _calendar.set(Calendar.SECOND, 0); _calendar.set(Calendar.MILLISECOND, 0); // Is the log file beyond keeping? // Toss it if it is beyond expiration. retval = _calendar.getTime().before(_expiration); } } return (retval); } // end of accept(File, String) // // end of FilenameFilter Interface Implemenation. //------------------------------------------------------- //----------------------------------------------------------- // Member data. // // Place the log files in this directory. private final File _directory; // All log file names start with the basename. private final String _basename; private final int _basenameSize; // Parses the file names date format. private final SimpleDateFormat _dateFormatter; // All log file names end with this extension. private final String _extension; private final int _extensionSize; // Accept only log files older than this date. private final Date _expiration; // Use this calendar to normalize the log file date. private final Calendar _calendar; } // end of class LogFileFilter } // end of class CalendarFileHandler // // CHANGE LOG // $Log: CalendarFileHandler.java,v $ // Revision 1.14 2006/10/01 17:29:16 charlesr // Updated EventThread start and halt methods. // // Revision 1.13 2005/11/05 15:56:30 charlesr // Gave _Timer a name: "LogRollTimer". // // Revision 1.12 2005/07/22 01:53:10 charlesr // Moved to Java 5: // + Added generics. // + Using Java enums for report frequency. // + Replaced StringBuffer with StringBuilder. // + Removed net.sf.eBus.util.ThreadListener. // // Revision 1.11 2005/04/02 14:09:39 charlesr // Check if the writer thread is halted before attempting to // enqueue a log message. // // Revision 1.10 2005/03/28 14:53:08 charlesr // + Replaced instantiating GregorianCalendar with // Calendar.getInstance(). // + Added new lines back into system log messages. // // Revision 1.9 2005/01/09 20:03:55 charlesr // Replace net.sf.eBus.util.TimerKeeper with java.util.Timer. // // Revision 1.8 2004/12/14 18:09:58 charlesr // Updated for ThreadListener interface change. // Replaced notifyAll() with notify(). // // Revision 1.7 2004/12/13 16:32:55 charlesr // Removed EOL from rollLogs(). // // Revision 1.6 2004/12/13 15:42:05 charlesr // Using java.nio to write to log file. // // Revision 1.5 2004/07/23 00:34:50 charlesr // Added default constructor which gets configuration from // java.util.logging.LogManager. // // Revision 1.4 2004/04/27 14:06:14 charlesr // Using net.sf.eBus.util.TimerKeeper's singleton java.util.Timer. // // Revision 1.3 2004/04/27 00:05:31 charlesr // Gave the writer thread a name. // // Revision 1.2 2004/02/28 14:50:20 charlesr // Removed EOL. // // Revision 1.1 2004/02/20 12:11:55 charlesr // Fixed log rolling which wasn't rolling. Also use handler's // formatter to format log roll message. // // Revision 1.0 2003/11/20 01:48:50 charlesr // Initial revision //




© 2015 - 2025 Weber Informatics LLC | Privacy Policy