/**
* 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.logger;
import edu.isi.pegasus.common.logging.LogManager;
import edu.isi.pegasus.common.logging.LogFormatter;
import edu.isi.pegasus.common.util.Currently;
import org.apache.log4j.Level;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Writer;
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 class Default extends LogManager{
//level constants that loosely match Log4J and are used
//to generate the appropriate mask values.
/**
* The type value to indicate a FATAL error message.
*/
private static final int FATAL_MESSAGE_TYPE = 0x1;
/**
* The type value to indicate an ERROR message.
*/
private static final int ERROR_MESSAGE_TYPE = 0x2;
/**
* The type value to indicate a CONSOLE message.
*/
private static final int CONSOLE_MESSAGE_TYPE = 0x4;
/**
* The type value to indicate a WARNING message.
*/
private static final int WARNING_MESSAGE_TYPE = 0x8;
/**
* The type value to indicate an INFORMATIVE message.
*/
private static final int INFO_MESSAGE_TYPE = 0x10;
/**
* The type value to indicate a CONFIG message.
*/
private static final int CONFIG_MESSAGE_TYPE = 0x20;
/**
* The type value to indicate a DEBUG message.
*/
private static final int DEBUG_MESSAGE_TYPE = 0x40;
/**
* The type value to indicate a DEBUG message.
*/
private static final int TRACE_MESSAGE_TYPE = 0x80;
/**
* Ensures only one object is created always. Implements the Singleton.
*/
private static Default logger;
/**
* The stream to which one writes. It is System.out by default for the
* current release. One can set it using setOutputWriter.
*
* @see #setOutputWriter
*/
private PrintStream mOutStream;
/**
* The stream to which all the error messages are logged.By default it is
* System.err
*/
private PrintStream mErrStream;
/**
* The mask that needs to be deployed to determine what messages are to be
* logged.
*/
private int mMask;
/**
* This is used to format the time stamp.
*/
private static Currently mFormatter ;
/**
* The constructor.
*/
public Default(){
mDebugLevel = 0;
mOutStream = new PrintStream(System.out,true);
mErrStream = new PrintStream(System.err,true);
Default.mFormatter = new Currently( "yyyy.MM.dd HH:mm:ss.SSS zzz: " );
//by default we are logging only CONSOLE
//and all message less than WARN
mMask = generateMask( WARNING_MESSAGE_LEVEL, false );
}
/**
* 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 void initialize( LogFormatter formatter, Properties properties ){
mLogFormatter = formatter;
}
/**
* 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 = Default.DEBUG_MESSAGE_LEVEL;
break;
case Level.INFO_INT:
value = Default.INFO_MESSAGE_LEVEL;
break;
case Level.WARN_INT:
value = Default.WARNING_MESSAGE_LEVEL;
break;
case Level.ERROR_INT:
value = Default.ERROR_MESSAGE_LEVEL;
break;
default:
value = Default.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.
*
* @param level the level to which the debug level needs to be set to.
*/
public void setLevel(int level){
setLevel(level, false );
}
/**
* 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 INFO messages need to be
* logged or not.
*/
protected void setLevel(int level, boolean info){
mDebugLevel = level;
mMask = generateMask(level,info);
}
/**
* Returns the debug level.
*
* @return the level to which the debug level has been set to.
*/
public int getLevel(){
return mDebugLevel;
}
/**
* 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.
*
* @see #setWriters(OutputStream)
*/
public void setWriters(String out){
try{
// mOutStream = (PrintStream)getPrintStream(out);
// mErrStream = mOutStream;
PrintStream ps = (PrintStream)getPrintStream(out);
System.setOut( ps );
System.setErr( ps );
mOutStream = System.out;
mErrStream = System.err;
}
catch(IOException e){
//log on the existing streams !!!
log("Unable to set streams for logging ",e,
this.WARNING_MESSAGE_LEVEL);
}
}
/**
* Sets both the output writer and the error writer to the same
* underlying writer.
*
* Note: The previous stream is not closed automatically.
*
* @param err the stream to which error messages are to be logged.
*/
/*
public void setWriters(OutputStream err){
mOutStream = new PrintWriter( err, true );
mErrStream = mOutStream;
}
*/
/**
* Sets the writer associated with the class to the one specified for all
* type of messages other than error messages.
*
* @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 #setOutputWriter(OutputStream)
*/
public void setOutputWriter(String out){
try{
mOutStream = (PrintStream)getPrintStream(out);
}
catch(IOException e){
//log on the existing streams !!!
log("Unable to set streams for logging ",e,
this.WARNING_MESSAGE_LEVEL);
}
}
/**
* Sets the writer associated with the class to the one specified for all
* type of messages other than error messages.
* By default it is System.out.
*
* @param out the stream to which the messages are logged.
*
* @see #setErrorWriter(OutputStream)
*/
public void setOutputWriter(OutputStream out){
mOutStream = new PrintStream( out, true );
}
/**
* Certains levels like FATAL, ERROR and WARN can be set to log to a
* different stream, than the default stream used for writing other messages.
* By default, these messages are logged to stderr.
* Note: The previous stream is not closed automatically.
*
* @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 #setErrorWriter(OutputStream)
*/
public void setErrorWriter(String out){
try{
mErrStream = (PrintStream)getPrintStream(out);
}
catch(IOException e){
//log on the existing streams !!!
log("Unable to set streams for logging ",e,
this.WARNING_MESSAGE_LEVEL);
}
}
/**
* Certains levels like FATAL, ERROR and WARN can be set to log to a
* different stream, than the default stream used for writing other messages.
* By default, these messages are logged to stderr.
* Note: The previous stream is not closed automatically.
*
* @param err the stream to which error messages are to be logged.
*/
public void setErrorWriter(OutputStream err){
mErrStream = new PrintStream( err, true );
}
/**
* 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 void log(String message, Exception e,int level){
StringBuffer msg = new StringBuffer();
msg.append(message).append(" ").append( e.getClass() ).append( ": ").append(e.getMessage());
log(msg.toString(),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)
* @see #log(String,int,boolean)
*/
public void logAlreadyFormattedMessage(String message, int level){
log(message,level,(level == this.INFO_MESSAGE_LEVEL) ?
true : false);
}
/**
* 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.
*
* @param message the message to be logged.
* @param level the level on which the message has to be logged.
* @param comp boolean indicating whether a completion message
* follows or not.
*
* @see #setLevel(int)
*/
private void log(String message, int level, boolean comp){
int type = (int)Math.pow(2, level);
if( (type & mMask) != 0x0 ){
//we need to log the message
//get hold of the writer to be used to logging the message.
PrintStream writer = getPrintStream(level);
writer.print(Default.mFormatter.now());
String prefix = getPrefix(type);
message = prefix + " " + message;
/*
*uncomment if we want commpetion message for INFO
*on same line
if(comp){
if((mMask & INFO_MESSAGE_TYPE) == INFO_MESSAGE_TYPE){
//we need to just print the message
writer.print(message);
}
else{
//write out on a new line and
//push the message to the stack
writer.println(message);
// mMsgStack.push(message);
}
}
else{
writer.println(message);
}
*/
writer.println(message);
writer.flush();
}
}
/**
* Gets the timestamp nicely formatted. It generates the date-timestamp
* in extended ISO 8601 format. It generates the timestamp using
* the local timezone not the UTC. An example of the date-timestamp
* generated would be 2003-06-06T14:31:27-07:00 where -07:00 denotes the timezone
* offset of the local timezone from UTC.
*
* @return the formattted timestamp;
*/
public String getTimeStamp(){
String st = Default.mFormatter.now();
st = Currently.iso8601(false);
return st;
}
/**
* 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 void logEventCompletion( int level ){
String message = mLogFormatter.getEndEventMessage();
mLogFormatter.popEvent();
int type = (int)Math.pow(2, level);
if( (type & mMask) != 0x0 ){
PrintStream writer = getPrintStream(level);
/*uncomment if we want commpetion message for INFO
on same line
if ( (mMask & INFO_MESSAGE_TYPE) == INFO_MESSAGE_TYPE) {
writer.println(" (completed)");
}
else {
writer.print(LogManager.mFormatter.now());
writer.println(message + " (completed)");
}
*/
String prefix = getPrefix(type);
message = prefix + " " + message;
writer.print(Default.mFormatter.now());
writer.println( message );
//writer.println(message + " (completed)");
}
}
/**
* Generates the appropriate mask value, corresponding to the level
* passed.
*
* @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.
*
* @return mask corresponding to the debug level passed.
*/
private int generateMask(int level,boolean info){
//construct the appropriate mask
int mask = 0x0;
for(int i = 0; i <= level; i++){
mask |= (int)Math.pow(2,i);
}
if(info){
mask |= CONSOLE_MESSAGE_TYPE;
}
return mask;
}
/**
* Returns the prefix that needs to be logged corresponding to a particular
* message type, when a message is being logged.
* Should be returning an enumerated data type.
*
* @param type the type for which prefix is required.
*
* @return the message type
*/
private String getPrefix(int type){
String result = null;
switch(type){
case FATAL_MESSAGE_TYPE:
result = "[FATAL ERROR]";
break;
case ERROR_MESSAGE_TYPE:
result = "[ERROR]";
break;
case CONSOLE_MESSAGE_TYPE:
result = "";
break;
case WARNING_MESSAGE_TYPE:
result = "[WARNING]";
break;
case INFO_MESSAGE_TYPE:
result = "[INFO]";
break;
case CONFIG_MESSAGE_TYPE:
result = "[CONFIG]";
break;
case DEBUG_MESSAGE_TYPE:
result = "[DEBUG]";
break;
case TRACE_MESSAGE_TYPE:
result = "[TRACE]";
break;
default:
result = "[UNKNOWN]";
}
return result;
}
/**
* Sets an internal writer to point to a particular stream.
*
* @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.
*
* @return the corresponding PrintStream.
*
* @throws IOException in case of being unable to open a stream.
*/
private PrintStream getPrintStream( String out ) throws IOException{
//check if value refers to any of the predefined streams
OutputStream stream;
if( out.equalsIgnoreCase("stdout")){ stream = System.out; }
else if( out.equalsIgnoreCase("stderr")){ stream = System.err; }
else{
//try to create an output stream to file specified
File f = new File( out );
//do some sanity checks on file
sanityCheckOnFile( f );
stream = new FileOutputStream( f);
}
return new PrintStream(stream);
}
/**
* Returns a PrintWriter stream on which to log the message. Later on
* this, function would return the appropriate LOG4J queue on which
* the message needs to be logged.
*
* @param level the level
*
* @return PrintWriter for logging the message.
*/
private PrintStream getPrintStream(int level){
return ( (level >= FATAL_MESSAGE_LEVEL && level < CONSOLE_MESSAGE_LEVEL)
|| level == WARNING_MESSAGE_LEVEL )?
mErrStream:
mOutStream;
}
}