/* * This file or a portion of this file is licensed under the terms of * the Globus Toolkit Public License, found in file GTPL, or at * http://www.globus.org/toolkit/download/license.html. This notice must * appear in redistributions of this file, with or without modification. * * Redistributions of this Software, with or without modification, must * reproduce the GTPL in: (1) the Software, or (2) the Documentation or * some other similar material which is provided with the Software (if * any). * * Copyright 1999-2004 University of Chicago and The University of * Southern California. All rights reserved. */ package org.griphyn.vdl.util; import edu.isi.pegasus.common.util.Currently; import java.io.*; import java.util.*; /** * Create a common interface to handle logging of messages, * debugging and streaming. In order to avoid conflicts with * JDK 1.4.*, this class is named Logging instead of Logger.<p> * * The logging mechanism works similar to syslog. There is an * arbitrary number of user-named queues, and a "default" queue. * Each queue has a level associated with it. The higher the level, * the less important the message. If the message to be logged * exceeds the level, it will not be logged. Level 0 will always * be logged, if a queue exists for it.<p> * * Usage is simple. Each queue has to be registered before use. The * registrations associated the output stream and maximum debug level.<p> * * Each log line will be prefixed by a time stamp. The logging class * maintains internal state for each queue, if it requested a line feed * to be printed. Thus, you are able to construct a message in several * pieces, or a multi-line message by smuggling line feeds within the * message.<p> * * @author Jens-S. Vöckler * @author Yong Zhao * @version $Revision$ * */ public class Logging { /** * Keeper of the Singleton. */ private static Logging m_instance = null; /** * maintains the map with associated output streams. */ private Hashtable m_queues = null; /** * maintains the map with the maximum debug level per queue. */ private Hashtable m_levels = null; /** * maintains the line feed state for each queue. */ private Hashtable m_newline = null; /** * This is used to format the time stamp. */ private static Currently m_formatter = null; /** * This is the verbose option. Any queue will be protocolled up * to the verbose level, iff the level is 0 or above. Verbose * messages are dumped on the stream associated with "default", * which defaults to stderr. */ private int m_verbose = -1; /** * implement the Singleton pattern */ public static Logging instance() { if ( m_instance == null ) m_instance = new Logging(); return m_instance; } /** * Ctor. */ private Logging() { this.m_queues = new Hashtable(); this.m_levels = new Hashtable(); this.m_newline = new Hashtable(); // Logging.m_formatter = new Currently( "yyyy-MM-dd HH:mm:ss.SSSZZZZZ: " ); Logging.m_formatter = new Currently( "yyyyMMdd'T'HHmmss.SSS: " ); register( "default", System.err, 0 ); } /** * Accessor: Obtains the default timestamp format for all queues. * * @return the currently active timestamp prefix format. */ public static Currently getDateFormat() { return Logging.m_formatter; } /** * Accessor: Sets the default timestamp format for all queues. * * @param format is the new timestamp prefix format. */ public static void setDateFormat( Currently format ) { if ( format != null ) Logging.m_formatter = format; } /** * Registers a stream with a name to use for logging. The queue * will be set up for maximum logging, e.g. virtually all levels * for this queue are logged. * * @param handle is the queue identifier * @param out is the name of a file to append to. Special names are * <code>stdout</code> and <code>stderr</code>, which map to the * system's respective streams. * @see #register( String, OutputStream, int ) */ public void register( String handle, String out ) { if ( out.equals("stdout") ) { this.register( handle, System.out, Integer.MAX_VALUE ); } else if ( out.equals("stderr") ) { this.register( handle, System.err, Integer.MAX_VALUE ); } else { try { FileOutputStream fout = new FileOutputStream(out,true); this.register( handle, new BufferedOutputStream(fout), Integer.MAX_VALUE ); } catch ( FileNotFoundException e ) { log( "default", 0, "unable to append \"" + handle + "\" to " + out + ": " + e.getMessage() ); } catch ( SecurityException e ) { log( "default", 0, "unable to append \"" + handle + "\" to " + out + ": " + e.getMessage() ); } } } /** * Registers a stream with a name to use for logging. The queue * will be set up for maximum logging, e.g. virtually all levels * for this queue are logged. * * @param handle is the queue identifier * @param out is the new output stream * @see #register( String, OutputStream, int ) */ public void register( String handle, OutputStream out ) { this.register( handle, out, Integer.MAX_VALUE ); } /** * Registers a stream with a name to use for logging. The queue * will be set up to use the output stream. If there was another * stream previously registered, it will be closed! * * @param handle is the queue identifier * @param out is the output stream associated with the queue * @param level is the maximum debug level to put into the queue */ public void register( String handle, OutputStream out, int level ) { PrintWriter previous = (PrintWriter) m_queues.put( handle, new PrintWriter(out,true) ); // don't close System.out nor System.err. So, rely on Java to close // if ( previous != null ) previous.close(); m_levels.put( handle, new Integer(level) ); m_newline.put( handle, new Boolean(true) ); } /** * Determines the maximum level up to which messages on the given * queue are protocolled. The associated stream is unaffected. * * @param handle is the queue identifier * @return the maximum inclusive log level, or -1 for error * @see #setLevel( String, int ) */ public int getLevel( String handle ) { if ( isUnset(handle) ) return -1; Integer i = (Integer) m_levels.get(handle); return (i != null ? i.intValue() : -1); } /** * Set the maximum level up to which messages on the given queue are * protocolled. The associated stream is unaffected. * * @param handle is the queue identifier * @param level is the new maximum log level (non-negative integer) * @see #setLevel( String, int ) */ public void setLevel( String handle, int level ) { if ( isUnset(handle) ) return; if ( level < 0 ) return; m_levels.put( handle, new Integer(level) ); } /** * Obtains the current verbosity level. * @return -1 for no verbosity, or the level up to which messages are logged. * @see #setVerbose( int ) */ public int getVerbose() { return this.m_verbose; } /** * Sets the maximum verbosity. * @see #resetVerbose() */ public void setVerbose() { this.m_verbose = Integer.MAX_VALUE; } /** * Deactivates any verbosity. * @see #setVerbose() */ public void resetVerbose() { this.m_verbose = -1; } /** * Sets or resets the verbosity level. * @param max is the maximum inclusive level to which messages on any * queue should be logged. A value of -1 (or any negative value) will * deactivate verbosity mode. * @see #getVerbose() */ public void setVerbose( int max ) { this.m_verbose = max; } /** * Prints a message on a previously registered stream. * * @param handle is the symbolic queue handle. * @param level is a verbosity level. The higher the level, the * more debug like the message. Messages of level 0 will always * be printed. * @param msg is the message to put onto the stream. Please note * that this function will automatically add the line break. */ public void log( String handle, int level, String msg ) { this.log( handle, level, msg, true ); } /** * Checks if a queue is free to be set up. This is important for * initialization to setup default queues, but allow user overrides. * * @param handle names the queue to check for a stream. * @return true, if the queue is not yet connected. */ public boolean isUnset( String handle ) { // sanity check if ( handle == null ) return false; return ( this.m_queues.get(handle) == null ); } /** * Prints a message on a previously registered stream. * * @param handle is the symbolic queue handle. * @param level is a verbosity level. The higher the level, the * more debug like the message. Messages of level 0 will always * be printed. * @param msg is the message to put onto the stream. * @param newline is a boolean, which will call invoke the println * method. */ public void log( String handle, int level, String msg, boolean newline ) { Integer maximum = (Integer) this.m_levels.get(handle); // do something, if verbosity if active boolean verbose = this.m_verbose >= 0 && level <= this.m_verbose; // do nothing, if we don't know about this level // do nothing, if the maximum level is below chosen debug level if ( verbose || ( maximum != null && ( level == 0 || level <= maximum.intValue() ) ) ) { // determine stream to dump message upon PrintWriter pw = (PrintWriter) this.m_queues.get( verbose ? "default" : handle); // if stream is known and without fault, dump message if ( pw != null && ! pw.checkError() ) { String prefix = new String(); // determine state of last message Boolean nl = (Boolean) this.m_newline.get(handle); // if last message had a newline attached, prefix with new timestamp if ( nl == null || nl.booleanValue() ) prefix += Logging.m_formatter.now() + '[' + handle + "] "; // print message if ( newline ) pw.println( prefix + msg ); else pw.print( prefix + msg ); // save new newline state for the stream. this.m_newline.put( handle, new Boolean(newline) ); } } } }