/** * Copyright 2007-2008 University Of Southern California * * 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 edu.isi.pegasus.common.logging; import edu.isi.pegasus.planner.common.PegasusProperties; import org.apache.log4j.Level; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Map; import java.util.Properties; /** * The logging class that to log messages at different levels. * Currently the following levels are supported.<p> * * Eventually, each of the level can have a different writer stream underneath. * * <p> * The messages can be logged at various levels. The various levels of logging * with increasing levels of verbosity are displayed in the following table. * * <p> * <table border="1"> * <tr align="left"><th>Logging Level</th><th>Description</th></tr> * <tr align="left"><th>FATAL</th> * <td>all fatal error messages are logged in this level.</td> * </tr> * <tr align="left"><th>ERROR</th> * <td>all non fatal error messages are logged in this level.</td> * </tr> * <tr align="left"><th>WARNING</th> * <td>all warning messages are logged in this level.</td> * </tr> * <tr align="left"><th>INFO</th> * <td>all information logging messages are logged in this level.</td> * </tr> * <tr align="left"><th>CONFIG</th> * <td>all configuration messages are logged in this level.</td> * </tr> * <tr align="left"><th>DEBUG</th> * <td>all debug messages are logged in this level.</td> * </tr> * </table> * * @author Karan Vahi * @author Gaurang Mehta * @version $Revision$ */ public abstract class LogManager { /** * The version of the Logging API */ public static final String VERSION = "2.1"; /** * Prefix for the property subset to use with the LogManager */ public static final String PROPERTIES_PREFIX = "pegasus.log.manager"; /** * Suffx for an event completion message. */ public static final String MESSAGE_DONE_PREFIX = " -DONE"; //level constants that loosely match Log4J and are used //to generate the appropriate mask values. /** * The level value, to indicate a FATAL error message. */ public static final int FATAL_MESSAGE_LEVEL = 0; /** * The level value, to indicate an ERROR message. */ public static final int ERROR_MESSAGE_LEVEL = 1; /** * The level value, to indicate a CONSOLE error message. */ public static final int CONSOLE_MESSAGE_LEVEL = 2; /** * The level value, to indicate a WARNING error message. */ public static final int WARNING_MESSAGE_LEVEL = 3; /** * The level value, to indicate a INFO message. */ public static final int INFO_MESSAGE_LEVEL = 4; /** * The level value, to indicate a CONFIG message. */ public static final int CONFIG_MESSAGE_LEVEL = 5; /** * The level value, to indicate a DEBUG message. */ public static final int DEBUG_MESSAGE_LEVEL = 6; /** * The level value, to indicate a DEBUG message. */ public static final int TRACE_MESSAGE_LEVEL = 7; /** * Ensures only one object is created always. Implements the Singleton. */ private static LogManager mLogger; /** * The default Logger */ public static final String DEFAULT_LOGGER = "Default"; /** * The Log4j logger. */ public static final String LOG4J_LOGGER = "Log4j"; /** * The debug level. Higher the level the more the detail is logged. At present * can be 0 or 1. This is set according to the option given by the user, whether * verbose or not. */ protected int mDebugLevel; /** * The LogFormatter to use to format the message. */ protected LogFormatter mLogFormatter; /** * The constructor. */ public LogManager(){ mDebugLevel = 0; } /** * To get a reference to the the object. * * @param logger the logger to use for logging * @param formatter the log formatter to use for formatting messages * * @return a singleton access to the object. */ public static LogManager getInstance( String logger, String formatter ){ if(mLogger == null){ mLogger = LogManagerFactory.loadSingletonInstance( PegasusProperties.nonSingletonInstance() ); /*if( logger == null || logger.equals( DEFAULT_LOGGER ) ){ mLogger = new Default(); } else if( logger.equals( LOG4J_LOGGER )){ mLogger = new Log4j(); } else{ throw new RuntimeException( "Unknown Logger Implementation Specified" + logger ); } */ } return mLogger; } /** * Sets the log formatter to use for formatting the messages. * * @param formatter the formatter to use. * @param properties properties that the underlying implementations understand */ public abstract void initialize( LogFormatter formatter, Properties properties ); /** * Checks the destination location for existence, if it can * be created, if it is writable etc. * * @param file is the file to write out to. * * @throws IOException in case of error while writing out files. */ protected static void sanityCheckOnFile( File file ) throws IOException{ if (file.exists()) { // location exists if (file.isFile()) { // ok, is a file if (file.canWrite()) { // can write, all is well return; } else { // all is there, but I cannot write to file throw new IOException("Cannot write to existing file " + file.getAbsolutePath()); } } else { // exists but not a file throw new IOException("File " + file.getAbsolutePath() + " already " + "exists, but is not a file."); } } else { // check to see if you can write to the parent directory //could have tried to do just a make dir on parent directory. sanityCheckOnDirectory( file.getParentFile()); } } /** * Checks the destination location for existence, if it can * be created, if it is writable etc. * * @param dir is the new base directory to optionally create. * * @throws IOException in case of error while writing out files. */ protected static void sanityCheckOnDirectory( File dir ) throws IOException{ if ( dir.exists() ) { // location exists if ( dir.isDirectory() ) { // ok, isa directory if ( dir.canWrite() ) { // can write, all is well return; } else { // all is there, but I cannot write to dir throw new IOException( "Cannot write to existing directory " + dir.getPath() ); } } else { // exists but not a directory throw new IOException( "Destination " + dir.getPath() + " already " + "exists, but is not a directory." ); } } else { // does not exist, try to make it if ( ! dir.mkdirs() ) { throw new IOException( "Unable to create directory destination " + dir.getPath() ); } } } /** * Sets the debug level. All those messages are logged which have a * level less than equal to the debug level. * * @param level the level to which the debug level needs to be set to. */ public void setLevel(Level level){ int value = level.toInt(); switch(value){ case Level.DEBUG_INT: value = LogManager.DEBUG_MESSAGE_LEVEL; break; case Level.INFO_INT: value = LogManager.INFO_MESSAGE_LEVEL; break; case Level.WARN_INT: value = LogManager.WARNING_MESSAGE_LEVEL; break; case Level.ERROR_INT: value = LogManager.ERROR_MESSAGE_LEVEL; break; default: value = LogManager.FATAL_MESSAGE_LEVEL; break; } setLevel(value,false); } /** * Sets the debug level. All those messages are logged which have a * level less than equal to the debug level. In addition the console messages * are always logged. * * @param level the level to which the debug level needs to be set to. */ public void setLevel(int level){ setLevel(level,true); } /** * Sets the debug level. All those messages are logged which have a * level less than equal to the debug level. In case the boolean info * is set, all the info messages are also logged. * * @param level the level to which the debug level needs to be set to. * @param info boolean denoting whether the CONSOLE messages need to be * logged or not. */ protected abstract void setLevel(int level, boolean info); /** * Returns the debug level. * * @return the level to which the debug level has been set to. */ public abstract int getLevel(); /** * Sets both the output writer and the error writer to the same * underlying writer. * * @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. * */ public abstract void setWriters( String out ); /** * Log the message represented by the internal log buffer. * The log buffer is populated via the add methods. * * @param level the level on which the message has to be logged. */ public void log( int level ){ this.log( mLogFormatter.createLogMessage(), level ); } /** * Creates a log message with the contents of the internal log buffer. * The log buffer is populated via the add methods. * It then resets the buffer before logging the log message * * * @param level the level on which the message has to be logged. */ public void logAndReset( int level ){ this.logAlreadyFormattedMessage( mLogFormatter.createLogMessageAndReset(), level ); } /** * Logs the exception on the appropriate queue if the level of the message * is less than or equal to the level set for the Logger. For INFO level * message, the boolean indicating that a completion message is to follow * is set to true always. * * @param message the message to be logged. * @param e the exception to be logged * @param level the level on which the message has to be logged. * * @see #setLevel(int) * @see #log(String,int) */ public abstract void log(String message, Exception e,int level); /** * A stop gap function . * * @param message already formatted message * @param level the level on which to log. */ protected abstract void logAlreadyFormattedMessage( String message, int level ); /** * Logs the message on the appropriate queue if the level of the message * is less than or equal to the level set for the Logger. For INFO level * message, the boolean indicating that a completion message is to follow * is set to true always. * * @param message the message to be logged. * @param level the level on which the message has to be logged. * * @see #setLevel(int) */ public void log ( String message, int level){ mLogFormatter.add( message ); this.logAlreadyFormattedMessage( mLogFormatter.createLogMessageAndReset(), level); } /** * Log an event start message to INFO level * * @param name the name of the event to be associated * @param entityName the primary entity that is associated with the event e.g. workflow * @param entityID the id of that entity. */ public void logEventStart( String name, String entityName, String entityID ){ logEventStart( name, entityName, entityID , LogManager.INFO_MESSAGE_LEVEL ); } /** * Log an event start message. * * @param name the name of the event to be associated * @param entityName the primary entity that is associated with the event e.g. workflow * @param entityID the id of that entity. * @param level the level at which event needs to be logged. */ public void logEventStart( String name, String entityName, String entityID , int level ){ mLogFormatter.addEvent( name, entityName, entityID ); this.logAlreadyFormattedMessage( mLogFormatter.getStartEventMessage() , level ); } /** * Log an event start message to the INFO Level * * @param name the name of the event to be associated * @param map Map indexed by entity name . The values is corresponding * EntityID * */ public void logEventStart( String name, Map<String,String> map ){ this.logEventStart( name, map, LogManager.INFO_MESSAGE_LEVEL ); } /** * Log an event start message. * * @param name the name of the event to be associated * @param map Map indexed by entity name . The values is corresponding * EntityID * @param level the level to log to */ public void logEventStart( String name, Map<String,String> map , int level ){ mLogFormatter.addEvent( name, map ); this.logAlreadyFormattedMessage( mLogFormatter.getStartEventMessage() , level ); } /** * Logs the completion message on the basis of the debug level. * * */ public void logEventCompletion( ){ //this.log( LogManager.INFO_MESSAGE_LEVEL ); this.logEventCompletion( LogManager.INFO_MESSAGE_LEVEL ); } /** * Logs the completion message on the basis of the debug level. * * @param level the debug level of the start message for whose completion * you want. */ public abstract void logEventCompletion( int level ); /** * Log a message that connects the parent entities with the * children. For e.g. can we use to create the log messages connecting the * jobs with the workflow they are part of. * * @param parentType the type of parent entity * @param parentID the id of the parent entity * @param childIDType the type of children entities * @param childIDs Collection of children id's * */ public void logEntityHierarchyMessage( String parentType, String parentID, String childIDType, Collection<String> childIDs ){ this.logEntityHierarchyMessage( parentType, parentID, childIDType, childIDs, LogManager.DEBUG_MESSAGE_LEVEL ); } /** * Log a message that connects the parent entities with the * children. For e.g. can we use to create the log messages connecting the * jobs with the workflow they are part of. * * @param parentType the type of parent entity * @param parentID the id of the parent entity * @param childIDType the type of children entities * @param childIDs Collection of children id's * @param level the logging level. * */ public void logEntityHierarchyMessage( String parentType, String parentID, String childIDType, Collection<String> childIDs, int level ) { this.logAlreadyFormattedMessage( mLogFormatter.createEntityHierarchyMessage(parentType, parentID, childIDType, childIDs), level ); } /** * Add to the internal log buffer message a value with the default key. * The buffer is logged later when the log() method is called. * * @param value * * @return self-reference */ public LogManager add( String value ){ return add( "msg", value ); } /** * Add to the internal log buffer message a value with the key oassed * The buffer is logged later when the log() method is called. * * * @param key * @param value * * @return Self-reference, so calls can be chained */ public LogManager add( String key, String value ){ mLogFormatter.add( key, value ); return this; } }