/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.server;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import com.slamd.common.Constants;
import com.slamd.db.SLAMDDB;
import com.slamd.job.JobClass;
import com.slamd.parameter.BooleanParameter;
import com.slamd.parameter.Parameter;
import com.slamd.parameter.ParameterList;
import com.slamd.parameter.StringParameter;
/**
* This class handles all logging performed by the SLAMD server. It is a
* multithreaded component to allow changes to be logged with minimal impact on
* the performance of other components in the SLAMD server that use the logger
* (like the scheduler). However, configuration parameters make it possible to
* customize this behavior so that logging can be done synchronously if this is
* desired for some reason (e.g., debugging purposes). Note that minimal
* logging will be performed in this class to prevent logging loops, but any
* significant problems will be logged to standard error.
*
*
* @author Neil A. Wilson
*/
public class Logger
implements ConfigSubscriber
{
/**
* The name used to register the logger as a subscriber to the configuration
* handler.
*/
public static final String CONFIG_SUBSCRIBER_NAME = "SLAMD Logger";
// Variables that are used in the actual logging process
private boolean alwaysFlush;
protected boolean closeRequested;
protected boolean logAsynchronously;
private boolean loggerEnabled;
protected BufferedWriter logWriter;
private String logFilename;
protected final Object loggerMutex;
protected final Object writerMutex;
// The configuration database for the SLAMD server.
private SLAMDDB configDB;
// Variables related to asynchronous logging
private LoggerThread loggerThread;
protected ArrayList<String> logBuffer;
// The SLAMD server with which this logger is associated
private SLAMDServer slamdServer;
/**
* Creates a new instance of a logger to work with the provided SLAMD server.
*
* @param slamdServer The SLAMD server instance with which this logger is to
* be associated.
*
* @throws SLAMDServerException If a problem is encountered while creating
* the logger.
*/
public Logger(SLAMDServer slamdServer)
throws SLAMDServerException
{
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"Entering Logger constructor");
// Initialize all of the appropriate instance variables
this.slamdServer = slamdServer;
alwaysFlush = Constants.DEFAULT_LOG_ALWAYS_FLUSH;
closeRequested = false;
logAsynchronously = Constants.DEFAULT_LOG_ASYNCHRONOUSLY;
loggerEnabled = Constants.DEFAULT_LOGGER_ENABLED;
logFilename = Constants.DEFAULT_LOG_FILENAME;
loggerMutex = new Object();
writerMutex = new Object();
// Retrieve the configuration handler and register as a subscriber
configDB = slamdServer.getConfigDB();
configDB.registerAsSubscriber(this);
// Read the values of the appropriate configuration parameters
String paramValue =
configDB.getConfigParameter(Constants.PARAM_LOGGER_ENABLED);
if ((paramValue != null) && (paramValue.length() > 0))
{
loggerEnabled =
(! paramValue.equalsIgnoreCase(Constants.CONFIG_VALUE_FALSE));
}
paramValue = configDB.getConfigParameter(Constants.PARAM_LOG_FILENAME);
if ((paramValue != null) && (paramValue.length() > 0))
{
logFilename = paramValue;
}
paramValue = configDB.getConfigParameter(Constants.PARAM_LOG_ALWAYS_FLUSH);
if ((paramValue != null) && (paramValue.length() > 0))
{
alwaysFlush =
(! paramValue.equalsIgnoreCase(Constants.CONFIG_VALUE_FALSE));
}
paramValue =
configDB.getConfigParameter(Constants.PARAM_LOG_ASYNCHRONOUSLY);
if ((paramValue != null) && (paramValue.length() > 0))
{
logAsynchronously =
(! paramValue.equalsIgnoreCase(Constants.CONFIG_VALUE_FALSE));
}
// Create the log writer for logging to the appropriate file
try
{
logWriter = new BufferedWriter(new FileWriter(logFilename, true));
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"Opened log file " + logFilename);
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ioe));
throw new SLAMDServerException("Error opening log file " + logFilename +
" -- " + ioe, ioe);
}
// If the logger is to operate asynchronously, then create the log buffer,
// logger mutex, and logging thread
if (logAsynchronously)
{
logBuffer = new ArrayList<String>();
loggerThread = new LoggerThread(slamdServer, this);
loggerThread.start();
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"Configured asynchronous logging");
}
else
{
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"Using synchronous logging");
}
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"Leaving Logger constructor");
slamdServer.setLoggerInitialized(true);
}
/**
* Performs the work of actually logging the specified message. If the logger
* is configured to operate synchronously, then it is written immediately. If
* the logger is configured to operate asynchronously, then the message is
* written to the log buffer to be picked up by the logger thread.
*
* @param message The message to be written to the log.
*/
public void logMessage(String message)
{
// If the logger is disabled, then do nothing.
if (! loggerEnabled)
{
return;
}
// No logging in this method (for obvious reasons)
synchronized (loggerMutex)
{
if (closeRequested)
{
return;
}
if (logAsynchronously)
{
logBuffer.add(message);
}
else
{
try
{
synchronized (writerMutex)
{
logWriter.write(message);
logWriter.newLine();
if (alwaysFlush)
{
logWriter.flush();
}
}
}
catch (IOException ioe)
{
System.err.println(slamdServer.getTimestamp() +
"Error writing log message \"" + message + "\"--" +
ioe);
}
}
}
}
/**
* Sets a flag that indicates that the logger should stop operating. If the
* logger is working synchronously, then it actually closes the log file.
* otherwise, it waits for the logging thread to complete. This function will
* not return until the logging subsystem has completely shut down.
*/
public void closeLogger()
{
synchronized (loggerMutex)
{
// Set the flag indicating that the logger is to be shut down
closeRequested = true;
// If logging is done synchronously, then flush and close the log file and
// exit from this method
if (! logAsynchronously)
{
try
{
synchronized (writerMutex)
{
logWriter.flush();
logWriter.close();
}
} catch (IOException ioe) {}
}
}
// If we get here, then the logger is operating asynchronously. Join the
// logging thread to wait for it to finish. We can't hold the logger mutex
// at this time, because the logger thread needs to get it.
if (logAsynchronously)
{
try
{
loggerThread.interrupt();
loggerThread.join();
} catch (InterruptedException ie) {}
}
// Indicate to the SLAMD server that the logger is no longer active. We can
// log a message indicating that the logger is shut down because it will now
// be written to standard error if applicable
slamdServer.setLoggerInitialized(false);
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"Logging system has been shut down");
}
/**
* Retrieves the name that the logger uses to subscribe to the configuration
* handler in order to be notified of configuration changes.
*
* @return The name that the logger uses to subscribe to the configuration
* handler in order to be notified of configuration changes.
*/
public String getSubscriberName()
{
return CONFIG_SUBSCRIBER_NAME;
}
/**
* Retrieves the set of configuration parameters associated with this
* configuration subscriber.
*
* @return The set of configuration parameters associated with this
* configuration subscriber.
*/
public ParameterList getSubscriberParameters()
{
BooleanParameter loggerEnabledParameter =
new BooleanParameter(Constants.PARAM_LOGGER_ENABLED, "Logger Enabled",
"Indicates whether the logger will be used to " +
"record events in a log file.", loggerEnabled);
StringParameter logFileParameter =
new StringParameter(Constants.PARAM_LOG_FILENAME, "Log File Name",
"The absolute path to the SLAMD log file.",
true, logFilename);
BooleanParameter logFlushParameter =
new BooleanParameter(Constants.PARAM_LOG_ALWAYS_FLUSH,
"Always Flush to Disk",
"Indicates whether information written to the " +
"log file should be immediately flushed to disk.",
alwaysFlush);
BooleanParameter logAsynchParameter =
new BooleanParameter(Constants.PARAM_LOG_ASYNCHRONOUSLY,
"Log Asynchronously",
"Indicates whether information logged will be " +
"immediately written to the file or put into " +
"to be logged later for better performance " +
"(changes require a restart to take effect).",
logAsynchronously);
Parameter[] params = new Parameter[]
{
loggerEnabledParameter,
logFileParameter,
logFlushParameter,
logAsynchParameter
};
return new ParameterList(params);
}
/**
* Re-reads all configuration information used by the logger. In this
* case, this is the name of the file to which to log and the flag indicating
* whether synchronous logging should always flush after writing. It is not
* possible to dynamically switch between synchronous and asynchronous
* logging.
* <p>
* If the name of the log file changes, then the logger writer will be closed
* and a new one opened with the specified name. There is no guarantee that
* any messages currently in the log buffer will be written to the original
* log file before it is closed (they may be written to the new log file
* instead).
*
* @throws SLAMDServerException If a new log filename is detected and there
* is a problem while closing the existing log
* file and/or opening the new one.
*/
public void refreshSubscriberConfiguration()
throws SLAMDServerException
{
// Determine whether the logger should be enabled.
String paramValue =
configDB.getConfigParameter(Constants.PARAM_LOGGER_ENABLED);
if ((paramValue != null) && (paramValue.length() > 0))
{
loggerEnabled =
(! paramValue.equalsIgnoreCase(Constants.CONFIG_VALUE_FALSE));
}
else
{
loggerEnabled = Constants.DEFAULT_LOGGER_ENABLED;
}
// Read the name of the log file to use. If it is different than the
// current log file, then switch the logger to use the new file.
String origLogFilename = logFilename;
paramValue = configDB.getConfigParameter(Constants.PARAM_LOG_FILENAME);
if ((paramValue != null) && (paramValue.length() > 0))
{
logFilename = paramValue;
if ((logFilename == null) || (logFilename.length() == 0))
{
logFilename = origLogFilename;
}
if (! origLogFilename.equals(logFilename))
{
synchronized (writerMutex)
{
// It is possible that the logger has not yet been opened, so don't
// log any errors if this fails.
try
{
logWriter.flush();
logWriter.close();
} catch (IOException ioe) {}
// Open the new log file so it will be used for future messages
try
{
logWriter = new BufferedWriter(new FileWriter(logFilename, true));
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"Opened log file " + logFilename);
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ioe));
throw new SLAMDServerException("Error opening log file " +
logFilename + " -- " + ioe, ioe);
}
}
}
}
else
{
logFilename = Constants.DEFAULT_LOG_FILENAME;
}
// Read the indicator that determines whether to always flush after
// synchronous writes
paramValue = configDB.getConfigParameter(Constants.PARAM_LOG_ALWAYS_FLUSH);
if ((paramValue != null) && (paramValue.length() > 0))
{
alwaysFlush = ! paramValue.equalsIgnoreCase(Constants.CONFIG_VALUE_FALSE);
}
else
{
alwaysFlush = Constants.DEFAULT_LOG_ALWAYS_FLUSH;
}
}
/**
* Re-reads the configuration for the specified parameter, if it is applicable
* to the logger. Only the name of the log file and the flag indicating
* whether synchronous logging should always flush after writing may be
* dynamically reconfigured. It is not possible to dynamically switch between
* synchronous and asynchronous logging.
* <p>
* If the name of the log file changes, then the logger writer will be closed
* and a new one opened with the specified name. There is no guarantee that
* any messages currently in the log buffer will be written to the original
* log file before it is closed (they may be written to the new log file
* instead).
*
* @param parameterName The name of the parameter to be re-read from the
* configuration.
*
* @throws SLAMDServerException If a new log filename is detected and there
* is a problem while closing the existing log
* file and/or opening the new one.
*/
public void refreshSubscriberConfiguration(String parameterName)
throws SLAMDServerException
{
// Read the indicator that specifies whether logging should be used.
if (parameterName.equalsIgnoreCase(Constants.PARAM_LOGGER_ENABLED))
{
String paramValue =
configDB.getConfigParameter(Constants.PARAM_LOGGER_ENABLED);
if ((paramValue != null) && (paramValue.length() > 0))
{
loggerEnabled =
(! paramValue.equalsIgnoreCase(Constants.CONFIG_VALUE_FALSE));
}
else
{
loggerEnabled = Constants.DEFAULT_LOGGER_ENABLED;
}
}
// Read the name of the log file to use. If it is different than the
// current log file, then switch the logger to use the new file.
if (parameterName.equalsIgnoreCase(Constants.PARAM_LOG_FILENAME))
{
String origLogFilename = logFilename;
String paramValue =
configDB.getConfigParameter(Constants.PARAM_LOG_FILENAME);
if ((paramValue != null) && (paramValue.length() > 0))
{
logFilename = paramValue;
if ((logFilename == null) || (logFilename.length() == 0))
{
logFilename = origLogFilename;
}
if (! origLogFilename.equals(logFilename))
{
synchronized (writerMutex)
{
// It is possible that the logger has not yet been opened, so don't
// log any errors if this fails.
try
{
logWriter.flush();
logWriter.close();
} catch (Exception ioe) {}
// Open the new log file so it will be used for future messages
try
{
logWriter = new BufferedWriter(new FileWriter(logFilename, true));
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"Opened log file " + logFilename);
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ioe));
throw new SLAMDServerException("Error opening log file " +
logFilename + " -- " + ioe, ioe);
}
}
}
}
else
{
logFilename = Constants.DEFAULT_LOG_FILENAME;
}
}
// Read the indicator that determines whether to always flush after
// synchronous writes
if (parameterName.equalsIgnoreCase(Constants.PARAM_LOG_ALWAYS_FLUSH))
{
String paramValue =
configDB.getConfigParameter(Constants.PARAM_LOG_ALWAYS_FLUSH);
if ((paramValue != null) && (paramValue.length() > 0))
{
alwaysFlush =
(! paramValue.equalsIgnoreCase(Constants.CONFIG_VALUE_FALSE));
}
else
{
alwaysFlush = Constants.DEFAULT_LOG_ALWAYS_FLUSH;
}
}
}
}