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

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

The newest version!
//
// Copyright 2001 - 2005, 2013 Charles W. Rapp
//
// Licensed 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 net.sf.eBus.util.logging;

import com.google.common.base.Strings;
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.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
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 data. // //----------------------------------------------------------- // Constants. // /** * Log files are placed in the application's current working * directory ({@value}) by default. */ public static final String DEFAULT_DIRECTORY = "."; /** * The log file's default base name is {@value}. */ public static final String DEFAULT_BASENAME = "Logger"; /** * The log file's default data format is {@value}. * For July 4, 1776 the formatted string is "17760704". */ public static final String DEFAULT_DATE_FORMAT = "yyyyMMdd"; /** * The log file handler default level is {@link Level#INFO}. */ public static final Level DEFAULT_LEVEL = Level.INFO; /** * The default file extension is {@value}. */ public static final String DEFAULT_EXTENSION = "log"; /** * The minimum number of days a log file is kept is {@value} * 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 {@value}. * 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 {@value} days by default. */ public static final int DEFAULT_DAYS_KEPT = 10; /** * Timeout callback method. */ private static final String TIMEOUT_METHOD_NAME = "handleTimeout"; // 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"; //----------------------------------------------------------- // Statics. // private static Timer sTimer = new Timer("LogRollTimer", true); //----------------------------------------------------------- // Locals. // // The log file's info. private final String mDirectory; private final String mBasename; private final String mDateFormat; private final String mExtension; private final int mDaysKept; // When this timer expires, roll the log files into the // gutter. private TimerTask mLogRollTimer; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new {@link CalendarFileHandler} and configures * it according to {@code LogManager} configuration * properties. * @throws IllegalArgumentException * if {@code LogManager} configuration properties contains * invalid or incorrect settings. * @throws IOException * if defined log directory is invalid. * @throws ClassNotFoundException * if defined formatter is not a known Java class. * @throws NoSuchMethodException * if defined formatter does not have a default * constructor defined. * @throws InstantiationException * if defined formatter instantiation failed. * @throws IllegalAccessException * if defined formatter default constructor is * inaccessible. * @throws InvocationTargetException * if formatter instantiation failed. */ public CalendarFileHandler() throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { this (getProperty(BASENAME_KEY, DEFAULT_BASENAME), getProperty(PATTERN_KEY, DEFAULT_DATE_FORMAT), getProperty(EXTENSION_KEY, DEFAULT_EXTENSION), getDefaultDaysKept()); } // 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. *
  • *
* @throws IllegalArgumentException * if any of the given parameters is set to an invalid * value. * @throws IOException * if defined log directory is invalid. * @throws ClassNotFoundException * if defined formatter is not a known Java class. * @throws NoSuchMethodException * if defined formatter does not have a default * constructor defined. * @throws InstantiationException * if defined formatter instantiation failed. * @throws IllegalAccessException * if defined formatter default constructor is * inaccessible. * @throws InvocationTargetException * if formatter instantiation failed. */ public CalendarFileHandler(final String baseName, final String datePattern, final String extension, final int daysKept) throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { super (); if (Strings.isNullOrEmpty(baseName)) { throw ( new IllegalArgumentException( "baseName is either null or an empty string")); } if (Strings.isNullOrEmpty(datePattern)) { throw ( new IllegalArgumentException( "datePattern is either null or an empty string")); } if (Strings.isNullOrEmpty(extension)) { throw ( new IllegalArgumentException( "extension is either null or an empty string")); } mDirectory = dirname(baseName); mBasename = basename(baseName); mDateFormat = datePattern; mExtension = extension; mDaysKept = daysKept; mLogRollTimer = new TimerTask(this); // Get the handler property settings. final Formatter formatter = createFormatter(); if (formatter != null) { setFormatter(formatter); } // Set the log level. setLevel(getDefaultLevel()); // Finish up by setting the handler's output stream as // per the configuration. final OutputStream logStream = openLogStream( mDirectory, mBasename, mDateFormat, mExtension); if (logStream != null) { setOutputStream(logStream); } deleteLogFiles(); startMidnightTimer(mLogRollTimer); } // 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( mDirectory, mBasename, mDateFormat, mExtension); LogRecord logRecord; logRecord = new LogRecord(Level.INFO, "Rolling log file."); logRecord.setSourceClassName( CalendarFileHandler.class.getName()); logRecord.setSourceMethodName(TIMEOUT_METHOD_NAME); 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(TIMEOUT_METHOD_NAME); 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. mLogRollTimer = new TimerTask(this); startMidnightTimer(mLogRollTimer); } // 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 logRecord Publish this log record to the log file. */ @Override public synchronized void publish(final LogRecord logRecord) { super.publish(logRecord); super.flush(); } // 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(mDirectory); 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, (mDaysKept * -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, mBasename + ".", mDateFormat, "." + mExtension, 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 StringBuilder buffer = new StringBuilder(); LogRecord logRecord; int index; Path path; buffer.append("Deleted the following log files:"); // Go through each of the log files. for (index = 0; index < logFiles.length; ++index) { buffer.append("\n ") .append(logFiles[index].getName()); try { path = (FileSystems.getDefault()).getPath( logFiles[index].getPath()); Files.delete(path); } catch (IOException jex) { buffer.append(" failed"); } } logRecord = new LogRecord(Level.INFO, buffer.toString()); logRecord.setSourceClassName( CalendarFileHandler.class.getName()); logRecord.setSourceMethodName(TIMEOUT_METHOD_NAME); publish(logRecord); } } // 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) { 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 number of days back which the calendar file * handler will keep files. Log files matching the * configured directory, base name, and date format which * are older than this number of days are deleted. * @return days back for keeping log files. * @throws NumberFormatException * if property is set to an invalid number. * @throws IllegalArgumentException * if the property is set to a value outside of * [{@link #MIN_DAYS_KEPT}, {@link #MAX_DAYS_KEPT}]. */ private static int getDefaultDaysKept() { final String value = getProperty(DAYS_KEPT_KEY, String.valueOf(DEFAULT_DAYS_KEPT)); int retval = DEFAULT_DAYS_KEPT; if (!Strings.isNullOrEmpty(value)) { retval = Integer.parseInt(value); if (retval < MIN_DAYS_KEPT || retval > MAX_DAYS_KEPT) { throw ( new IllegalArgumentException( String.format( "%,d is either < %d or > %d", retval, MIN_DAYS_KEPT, MAX_DAYS_KEPT))); } } return (retval); } // end of getDefaultDaysKept() /** * Creates a formatter instance based on the given class. * name. This class must extend {@link Formatter} and * have a {@code public}, no arguments default * constructor defined. * @param className {@link Formatter} subclass name. * @return {@code this Builder} instance. * @throws IllegalArgumentException * if {@code className} is either {@code null} or an * empty string. * @throws ClassNotFoundException * if {@code className} is not a known Java class. * @throws NoSuchMethodException * if {@code className} does not have a default * constructor defined. * @throws InstantiationException * if {@code className} instantiation failed. * @throws IllegalAccessException * if {@code className} default constructor is * inaccessible. * @throws InvocationTargetException * if {@code className} instantiation failed. */ @SuppressWarnings ("unchecked") private static Formatter createFormatter() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { final String value = getProperty(FORMATTER_KEY, null); Formatter retval = null; // Was a formatter provided? if (!Strings.isNullOrEmpty(value)) { // Yes. Create an instance of the formatter class. final Class fc = (Class) Class.forName(value); final Constructor ctor = fc.getDeclaredConstructor(); retval = ctor.newInstance(); } // No. Return null. return (retval); } // end of createFormatter() /** * Returns the log manager property stored in the given key * as text. If there is no such property defined or is a * {@code null} or empty string then returns the default * value. * @param key property key. * @param defaultValue property default value. * @return property value. */ private static String getProperty(final String key, final String defaultValue) { final LogManager manager = LogManager.getLogManager(); String value = manager.getProperty(key); return (Strings.isNullOrEmpty(value) ? defaultValue : value); } // end of getProperty(String, String, LogManager) /** * Returns the log handler default log level. * @return default log level. * @throws IllegalArgumentException * if log manager log level property is set to an invalid * value. */ private Level getDefaultLevel() { final String value = getProperty(LEVEL_KEY, DEFAULT_LEVEL.getName()); Level retval = DEFAULT_LEVEL; if (!Strings.isNullOrEmpty(value)) { retval = Level.parse(value); } return (retval); } // end of getDefaultLevel() /** * Returns the log file directory. If undefined then returns * current working directory ("."). * @param logname extract log file directory from this name. * @return log file directory. * @throws IOException * if {@code baseName} contains an invalid directory. */ private static String dirname(final String baseName) throws IOException { final int index = baseName.lastIndexOf(File.separatorChar); final File dir; final String retval = (index < 0 ? DEFAULT_DIRECTORY : baseName.substring(0, index)); // Is this a valid directory? dir = new File(retval); if (!dir.exists() || !dir.isDirectory() || !dir.canWrite()) { throw ( new IOException( retval + " is an invalid directory")); } return (retval); } // end of dirname(String) /** * Returns log file base name. If there is no * {@link File#separatorChar} in {@code baseName} then * returns {@code baseName} as the log file base name. * @return log file base name. */ private static String basename(final String baseName) { final int index = baseName.lastIndexOf(File.separatorChar); return (index < 0 ? baseName : baseName.substring(index + 1)); } // 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); sTimer.schedule(task, calendar.getTime()); } // end of startMidnightTimer(TimerTask) //--------------------------------------------------------------- // 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 data. // //------------------------------------------------------- // Constructors. // // Place the log files in this directory. private final File mDirectory; // All log file names start with the basename. private final String mBasename; private final int mBasenameSize; // Parses the file names date format. private final SimpleDateFormat mDateFormatter; // All log file names end with this extension. private final String mExtension; private final int mExtensionSize; // Accept only log files older than this date. private final Date mExpiration; // Use this calendar to normalize the log file date. private final Calendar mCalendar; //----------------------------------------------------------- // 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) { mDirectory = directory; mBasename = basename; mBasenameSize = mBasename.length(); mDateFormatter = new SimpleDateFormat(dateFormat); mExtension = extension; mExtensionSize = mExtension.length(); mExpiration = expiration; mCalendar = Calendar.getInstance(); } // 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(mDirectory) && name.startsWith(mBasename) && name.endsWith(mExtension)); if (retval) { String dateSubstring; ParsePosition pos; Date logFileDate; // Get the date portion of the file name. dateSubstring = name.substring( mBasenameSize, (name.length() - mExtensionSize)); // Parse the log's name to get its date. pos = new ParsePosition(0); logFileDate = mDateFormatter.parse(dateSubstring, pos); if (logFileDate != null) { mCalendar.setTime(logFileDate); mCalendar.set(Calendar.HOUR_OF_DAY, 0); mCalendar.set(Calendar.MINUTE, 0); mCalendar.set(Calendar.SECOND, 0); mCalendar.set(Calendar.MILLISECOND, 0); // Is the log file beyond keeping? // Toss it if it is beyond expiration. retval = mCalendar.getTime().before(mExpiration); } } return (retval); } // end of accept(File, String) // // end of FilenameFilter Interface Implemenation. //------------------------------------------------------- } // end of class LogFileFilter } // end of class CalendarFileHandler




© 2015 - 2024 Weber Informatics LLC | Privacy Policy