//============================================================================== // Logger.java //------------------------------------------------------------------------------ package tribble.io; // System imports import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.lang.IllegalArgumentException; import java.lang.NullPointerException; import java.lang.String; import java.lang.StringBuffer; import java.util.Date; import java.util.HashMap; // Local imports import tribble.io.LoggerI; import tribble.io.SuspendableWriterI; /******************************************************************************* * Message logger (logging output stream). * *
* A logging output stream is used to write diagnostic log messages to during the
* execution of one or more programs. Typically, the output stream is a log file
* on disk that is opened for a given application and execution date.
*
* @version $Revision: 1.4 $ $Date: 2003/03/10 03:06:42 $
* @since 2003-01-06
* @author
* David R. Tribble,
* david@tribble.com.
*
* Copyright
* ©2003 by David R. Tribble, all rights reserved.
*
* Permission is granted to freely use and distribute this source code
* provided that the original copyright and authorship notices remain
* intact.
*/
public class Logger
implements LoggerI, SuspendableWriterI
{
// Identification
/** Revision information. */
static final String REV =
"@(#)tribble/io/Logger.java $Revision: 1.4 $ $Date: 2003/03/10 03:06:42 $\n";
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Protected constants
//----------------------------------
// Message severity level names
/** Severity name: Unknown. */
protected static final String SEV_N_UNKNOWN = "?: ";
/** Severity name: Debug. */
protected static final String SEV_N_DEBUG = "debug: ";
/** Severity name: Informational. */
protected static final String SEV_N_INFO = "";
/** Severity name: Warning. */
protected static final String SEV_N_WARNING = "warning: ";
/** Severity name: Error. */
protected static final String SEV_N_ERROR = "error: ";
/** Severity name: Fatal (unrecoverable) error. */
protected static final String SEV_N_FATAL = "fatal: ";
/** Severity name: Internal (unrecoverable) fatal error. */
protected static final String SEV_N_INTERNAL = "internal: ";
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Private static variables
/**
* Global message loggers list.
* Contains all of the registered named {@link Logger} objects.
*/
private static HashMap s_loggers;
/** Default (unnamed) message logger. */
private static Logger s_default;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Static class initializers
static
{
// Initialize the default message logger
s_default = new Logger(new PrintWriter(System.out, true));
// Initialize the global logger list
s_loggers = new HashMap();
s_loggers.put("", s_default);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Public static methods
/***************************************************************************
* Retrieve a named message logger from the global logger list.
*
* @param id
* The name that a message logger was registered with.
*
* @return
* A named logger, or null if no logger was registered with the given name.
*
* @since 1.3, 2003-03-08
*
* @see #register
*/
public static Logger getLogger(String id)
{
// Check args
if (id == null)
return (null);
synchronized (s_loggers)
{
// Get the named logger
return ((Logger) s_loggers.get(id));
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Protected static methods
/***************************************************************************
* Return the string corresponding to a given logging message severity
* level.
*
* @param sev
* The severity level of the message, which should be one of the
* SEV_XXX constants.
*
* @return
* A string corresponding to the message level sev.
*
* @since 1.1, 2003-01-06
*/
protected static String severityToString(int sev)
{
// Convert a severity level into a string
switch (sev)
{
case SEV_DEBUG:
return (SEV_N_DEBUG);
case SEV_INFO:
return (SEV_N_INFO);
case SEV_WARNING:
return (SEV_N_WARNING);
case SEV_ERROR:
return (SEV_N_ERROR);
case SEV_FATAL:
return (SEV_N_FATAL);
case SEV_INTERNAL:
return (SEV_N_INTERNAL);
default:
// Unknown
return (SEV_N_UNKNOWN);
}
}
/***************************************************************************
* Register a named message logger in the global logger list.
*
*
* Note that if the logger is currently registered with a different name, it * will first be unregistered under that name and then re-registered with the * new name. * * @param log * A message logger. * * @param id * The name that the message logger is to be registered with. * * @throws NullPointerException (unchecked) * Thrown if log or id is null. * * @throws IllegalArgumentException (unchecked) * Thrown if a message logger is already registered with the given name. * * @since 1.3, 2003-03-08 * * @see #getLogger * @see #unregisterLogger * @see #register */ protected static void registerLogger(Logger log, String id) { // Cannot register null or unnamed loggers if (log == null) throw new NullPointerException("Null logger"); if (id == null) throw new NullPointerException("Null logger name"); synchronized (s_loggers) { Logger old; // Check if the logger is already registered with a different name if (log.m_id != null) { old = (Logger) s_loggers.get(log.m_id); if (old == log) s_loggers.remove(log); } // Rename and register the logger log.m_id = id; old = (Logger) s_loggers.put(id, log); // Check for a previously registered logger with the same name if (old != log && old != null) { s_loggers.put(id, old); throw new IllegalArgumentException( "Logger name already registered: \"" + id + "\""); } } } /*************************************************************************** * Unregister a named message logger from the global logger list. * * @param log * A message logger. * If the logger has no name or is not currently registered, no operation is * performed. * Note that the default (unnamed) message logger cannot be unregistered. * * @since 1.3, 2003-03-08 * * @see #close * @see #registerLogger * @see #unregister */ protected static void unregisterLogger(Logger log) { // Cannot unregister unnamed loggers if (log.m_id == null) return; // Do not unregister the default logger if (log.m_id.length() == 0) return; synchronized (s_loggers) { Logger old; // Remove the named logger from the global list old = (Logger) s_loggers.remove(log.m_id); if (old != log && old != null) s_loggers.put(old, old.m_id); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Protected variables /** Underlying output stream. */ protected PrintWriter m_out; /** Suspended output stream. */ protected PrintWriter m_sus; /** Name of this message logger. */ protected String m_id; /** Message prefixing bitflags. */ protected int m_pfx = PFX__DFL; /** Maximum message verbosity filtering level. */ protected int m_filter = 10; /** Output text line buffer. */ protected StringBuffer m_buf = new StringBuffer(80); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Public constructors /*************************************************************************** * Default constructor. * This creates a message logger with an initially empty (null) output * stream. * * @since 1.1, 2003-01-06 * * @see #setOutput */ public Logger() { // Do nothing } /*************************************************************************** * Constructor. * * @param out * An output stream to be the underlying output stream for this message * logger. * * @since 1.1, 2003-01-06 */ public Logger(Writer out) { // Establish the underlying output stream setOutput(out); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Public methods /*************************************************************************** * Establish the underlying output stream for this message logger. * * @param out * A logging output stream. * This can be null. * If the stream is not an instance of a java.io.PrintWriter object, * one will be created internally. * * @since 1.3, 2003-03-08 */ public void setOutput(Writer out) //implements tribble.io.LoggerI { // Establish the underlying output stream if (out == null) m_sus = null; else if (out instanceof PrintWriter) m_sus = (PrintWriter) out; else if (out != null) m_sus = new PrintWriter(out, true); m_out = m_sus; } /*************************************************************************** * Retrieve the underlying output stream for this message logger. * * @return * The underlying output stream for this logging output stream. * The stream can be null, which effectively disables logging output for this * logger. * * @since 1.1, 2003-01-06 */ public PrintWriter getOutput() //implements tribble.io.LoggerI { // Retrieve the underlying output stream return (m_sus); } /*************************************************************************** * Close this message logger. * *
* Note that the default (unnamed) message logger cannot be closed. * *
* Although this operation close the underlying output stream for this * logger, it does not unregister it. To change the underlying output stream * for this logger without closing the current output stream, use * {@link #setOutput} instead of this method. * *
* Note that this method does not throw any checked exceptions, especially * java.io.IOException. * * @since 1.1, 2003-01-06 * * @see #setOutput * @see #unregister */ public void close() //implements tribble.io.LoggerI //implements tribble.io.SuspendableWriterI { // Check for an open output stream if (m_sus == null) return; // Do not close the default logger if (this == s_default) return; // Close the output stream synchronized (m_sus) { m_out = null; m_sus.flush(); m_sus.close(); // Disassociate the output stream from this logger m_sus = null; } } /*************************************************************************** * Suspend subsequent output written to this logging stream. * *
* Output to this stream can be resumed by calling {@link #resume}. * *
* This method may be called multiple times without any intervening call to * {@link #resume} without any ill effects. * * @since 1.4, 2003-03-09 * * @see #resume */ public void suspend() { // Suspend the current output stream m_out = null; } /*************************************************************************** * Resume subsequent output written to this logging stream. * *
* Output to this stream can be suspended by calling {@link #suspend}. * *
* This method may be called multiple times without any previous or * intervening call to {@link #suspend} without any ill effects. * * @since 1.4, 2003-03-09 * * @see #suspend */ public void resume() { // Resume the current output stream m_out = m_sus; } /*************************************************************************** * Determine if this logging stream is suspended or not. * * @return * True if this logging stream is currently suspended, otherwise false. * * @since 1.4, 2003-03-09 * * @see #suspend */ public boolean isSuspended() { // Check if the output stream is suspended (or closed) return (m_out == null); } /*************************************************************************** * Retrieve the name of this message logger. * * @return * The name of this logger, which may be null. * If the logger is registered in the global logger list, this is the name * with which it was registered. * Note that the default (unnamed) message logger has an empty name * (""). * * @since 1.3, 2003-03-08 */ public String getName() { // Retrieve the name of this logger return (m_id); } /*************************************************************************** * Register this message logger in the global logger list. * *
* Note that if this logger is currently registered with a different name, it * will first be unregistered under that name and then re-registered with the * new name. * * @param id * The name that the message logger is to be registered with. * The logger is assigned this name (until it is registered with a different * name). * * @throws NullPointerException (unchecked) * Thrown if log or id is null. * * @throws IllegalArgumentException (unchecked) * Thrown if a message logger is already registered with the given name. * * @since 1.3, 2003-03-08 * * @see #unregister * @see #getLogger * @see #getName */ public void register(String id) { // Register this logger registerLogger(this, id); } /*************************************************************************** * Unregister this named message logger from the global logger list. * *
* If this logger is not currently registered, no operation is performed. * *
* Note that the default (unnamed) message logger cannot be unregistered. * * @since 1.3, 2003-03-08 * * @see #register * @see #close */ public void unregister() { // Unregister this logger unregisterLogger(this); } /*************************************************************************** * Establish the prefixes to be prepended to messages written to this message * logger. * * @param pfx * Zero or more of the * PFX_XXX * prefixing bitflag constants or-ed together. * * @return * The previous prefix setting. * * @since 1.1, 2003-01-08 */ public int setPrefix(int pfx) //implements tribble.io.LoggerI { int prev; // Establish the message prefixing flags prev = m_pfx; m_pfx = pfx; return (prev); } /*************************************************************************** * Establish the verbosity filtering level for this message logger. * * @param sev * The severity of the messages to be filtered. * This should be one of the SEV_XXX * constants, or zero to specify all message severities. * * @param max * The maximum verbosity filtering level for messages written to * this message logger. * Log messages with a verbosity level not greater than this value will be * written to the logging output stream; those with a verbosity level greater * than this value will be ignored. * * @return * The previous verbosity level setting. * * @since 1.2, 2003-02-16 */ public int setVerbosity(int sev, int max) //implements tribble.io.LoggerI { int prev; // Establish the message verbosity filtering level prev = m_filter; m_filter = max; return (prev); } /*************************************************************************** * Write a formatted message to this message logger. * *
* Note that this method does not throw any checked exceptions, specifically * java.io.IOException. * * @param sev * The severity of the message, which should be one of the * SEV_XXX constants. * * @param verbos * The verbosity level of the message. * The higher the level, the more verbose the message (i.e., the more * frequently the message is logged) and thus the more likely it will be * filtered and not written to the output stream; messages with a level of * zero will always be written to the output stream. * * @param loc * The name of the location (typically a thread or class name) from where the * log message originates. This serves as a label to prefix the message with * in the logging output stream. This can be empty ("") or null, in * which case no location is written. * * @param msg * A text message to log. * * @since 1.2, 2003-01-16 */ public void log(int sev, int verbos, String loc, String msg) //implements tribble.io.LoggerI { printMessage(sev, verbos, loc, msg); } /*************************************************************************** * Write a formatted debugging message to this message logger. * Debugging messages have an implied severity of {@link SEV_DEBUG}. * *
* Note that this method does not throw any checked exceptions, specifically * java.io.IOException. * * @param verbos * The verbosity level of the message. * The higher the level, the more verbose the message (i.e., the more * frequently the message is logged) and thus the more likely it will be * filtered and not written to the output stream; messages with a level of * zero will always be written to the output stream. * * @param loc * The name of the location (typically a thread or class name) from where the * log message originates. This serves as a label to prefix the message with * in the logging output stream. This can be empty ("") or null, in * which case no location is written. * * @param msg * A text message to log. * * @since 1.2, 2003-01-16 */ public void debug(int verbos, String loc, String msg) //implements tribble.io.LoggerI { printMessage(SEV_DEBUG, verbos, loc, msg); } /*************************************************************************** * Write a formatted informational message to this message logger. * Informational messages have an implied severity of {@link SEV_INFO}. * *
* Note that this method does not throw any checked exceptions, specifically * java.io.IOException. * * @param verbos * The verbosity level of the message. * The higher the level, the more verbose the message (i.e., the more * frequently the message is logged) and thus the more likely it will be * filtered and not written to the output stream; messages with a level of * zero will always be written to the output stream. * * @param loc * The name of the location (typically a thread or class name) from where the * log message originates. This serves as a label to prefix the message with * in the logging output stream. This can be empty ("") or null, in * which case no location is written. * * @param msg * A text message to log. * * @since 1.2, 2003-01-16 */ public void info(int verbos, String loc, String msg) //implements tribble.io.LoggerI { printMessage(SEV_INFO, verbos, loc, msg); } /*************************************************************************** * Write a formatted warning message to this message logger. * Warning messages have an implied severity of {@link SEV_WARNING}. * *
* Note that this method does not throw any checked exceptions, specifically * java.io.IOException. * * @param verbos * The verbosity level of the message. * The higher the level, the more verbose the message (i.e., the more * frequently the message is logged) and thus the more likely it will be * filtered and not written to the output stream; messages with a level of * zero will always be written to the output stream. * * @param loc * The name of the location (typically a thread or class name) from where the * log message originates. This serves as a label to prefix the message with * in the logging output stream. This can be empty ("") or null, in * which case no location is written. * * @param msg * A text message to log. * * @since 1.2, 2003-01-16 */ public void warning(int verbos, String loc, String msg) //implements tribble.io.LoggerI { printMessage(SEV_WARNING, verbos, loc, msg); } /*************************************************************************** * Write a formatted error message to this message logger. * Error messages have an implied severity of {@link SEV_ERROR}. * *
* Note that this method does not throw any checked exceptions, specifically * java.io.IOException. * * @param loc * The name of the location (typically a thread or class name) from where the * log message originates. This serves as a label to prefix the message with * in the logging output stream. This can be empty ("") or null, in * which case no location is written. * * @param msg * A text message to log. * * @since 1.2, 2003-01-16 */ public void error(String loc, String msg) //implements tribble.io.LoggerI { printMessage(SEV_ERROR, 0, loc, msg); } /*************************************************************************** * Write a formatted fatal error message to this message logger. * Fatal error messages have an implied severity of {@link SEV_FATAL}. * *
* Note that this method does not throw any checked exceptions, specifically * java.io.IOException. * * @param loc * The name of the location (typically a thread or class name) from where the * log message originates. This serves as a label to prefix the message with * in the logging output stream. This can be empty ("") or null, in * which case no location is written. * * @param msg * A text message to log. * * @since 1.2, 2003-01-16 */ public void fatal(String loc, String msg) //implements tribble.io.LoggerI { printMessage(SEV_FATAL, 0, loc, msg); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Protected methods /*************************************************************************** * Write a formatted message to this message logger. * *
* Note that this method does not throw any checked exceptions, especially * java.io.IOException. * * @param sev * The severity level of the message, which should be one of the * SEV_XXX constants. * * @param verbos * The verbosity level of the message. * The higher the level, the more verbose the message (i.e., the more * frequently the message is logged) and thus the more likely it will be * filtered and not written to the output stream; messages with a level of * zero will always be written to the output stream. * * @param loc * The name of the location (typically a thread or class name) from where the * log message originates. This serves as a label to prefix the message with * in the logging output stream. This can be empty ("") or null, in * which case no prefix is written. * * @param msg * A text message to log. * Note that this message should not have a terminating newline * character. * * @since 1.1, 2003-01-06 */ protected void printMessage(int sev, int verbos, String loc, String msg) { // Check for an open output stream if (m_out == null) return; // Filter the message based on its verbosity if (verbos > m_filter) return; // Format the log message synchronized (m_buf) { m_buf.setLength(0); // Write the formatted log message synchronized (m_out) { if ((m_pfx & (PFX_DATE|PFX_TIME)) != 0) { Date now; if ((m_pfx & PFX_DATE) != 0) { // Show the current date /*+++INCOMPLETE ...; +++*/ } if ((m_pfx & PFX_TIME) != 0) { // Show the current time /*+++INCOMPLETE ...; +++*/ } } if ((m_pfx & PFX_LOC) != 0) { // Show the location name/title /*+++INCOMPLETE ...; +++*/ } // Write the message m_out.println(m_buf); } // Attempt to optimize memory use if (m_buf.capacity() > 1000) m_buf = new StringBuffer(200); } } } // End Logger.java