//============================================================================== // 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