package net.neoremind.sshxcute.core;
import java.io.*;
import java.util.*;
/**
* This class provides a shared logger for an application that is distributed
* between Java and Jython.<br>
* The first time Logger.setInstance() is used determines where the Logger instance is created,
* what it's name is, and where the log files are created. After that, all other
* Java and Jython code can get the Logger instance and then call putMsg() to output
* to the same Log and Debug files.<br>
* Subsequent setInstance() calls can change the Logger to a new name and location.<br>
* Java methods should use the putMsg(level,msg) interface so that the caller can be found
* using Java reflection methods. Since Java cannot follow the call stack in and out of
* the Jython application, the Jython methods must use the putMsg(caller,level,msg) interface
* so that the call stack entry can be passed in from Jython. The Jython class PyLogger helps
* do this.
*
* @author jbvoge@us.ibm.com
*
*/
public class Logger
{
private static Logger instance=null;
private static String pathToFiles;
private static String instanceName;
private static String logFile;
private static String dbgFile;
private static Map<String,String> classNames; // Keeps track of class knicknames
private static int classCount;
private static int logThreshold=Logger.INFO;
public static final int DIVIDER=0;
public static final int DEEP_DBUG=1;
public static final int MID_DBUG=2;
public static final int LITE_DBUG=3;
public static final int INFO=4;
public static final int WARN=5;
public static final int ERROR=6;
public static final int FATAL=7;
public static final int OFF=99;
/**
* Get the shared Logger instance.
* @return Logger instance, or null if one has not been set up
*/
public static Logger getLogger()
{
try {
instance = new Logger(getCaller());
} catch (Exception e) {
return null;
}
instance.setThreshold(INFO);
return instance;
}
// /**
// * Set the Logger instance to 'name' and set the path for log files.<br>
// * If the Logger is currently open, it is closed and new log files are started.
// * @param name
// * @param path
// * @return Logger instance
// */
// public static Logger setInstance( String name, String path, int threshold )
// throws Exception
// {
// return setInstance( getCaller(), name, path, threshold );
// }
//
//
// /**
// * Set the Logger instance to 'name' and set the path for log files.<br>
// * If the Logger is currently open, it is closed and new log files are started.
// * This method is generally for the PyLogger where the callstack must be passed in
// * @param name
// * @param path
// * @return Logger instance
// */
// public static Logger setInstance( String caller, String name, String path, int threshold )
// throws Exception
// {
// instance = new Logger(caller,name,path);
// instance.setThreshold(threshold);
// return instance;
// }
// /**
// * Set the logging threshold using the name of the level
// * @param newlevel
// */
// public void setThreshold( String newlevel )
// {
// int lvl = levelFmString(newlevel);
// setThreshold( getCaller(), lvl );
// }
/**
* Set the logging threshold using the enum value of the level
* @param newlevel
*/
public void setThreshold( int newThreshold )
{
setThreshold( getCaller(), newThreshold );
}
// /**
// * Set the logging threshold using the name of the level
// * This method is generally for the PyLogger class
// * @param caller
// * @param newlevel
// */
// public void setThreshold( String caller, String newThreshold )
// {
// int lvl = levelFmString(newThreshold);
// setThreshold( caller, lvl );
// }
/**
* Set the logging threshold using the enum value of the level
* This method is generally for the PyLogger class
* @param caller
* @param newThreshold
*/
public void setThreshold( String caller, int newThreshold )
{
DateCalendar d = new DateCalendar();
//String outp = "\n--------------- ["+d.toTimeStamp()+", "+caller+"] --- LogThreshold changed from "+levelToString(logThreshold)+" to "+levelToString(newThreshold)+" ----------\n";
logThreshold = newThreshold;
//printLog(outp,true);
//if (logThreshold < INFO )
//printDbg(outp,true);
}
/**
* Get the path where the Logger files are being stored
* @return logPath
*/
public String getLogpath() { return pathToFiles; }
/**
* This optional method will terminate the *.dbg file with the shortName cross-reference list.<br>
* To make the Logger debug files easier to read, each message line only contains the short Class
* name, with the package name part replaced with #n. Thus, each #n value is effectively a nickname
* for the fully-qualified package and class name. When the debug file is closed with this
* method, a cross-reference listing is provided to give the full name for each #n nickname.
*/
public void close()
{
if (logThreshold < INFO )
{
printDbg("----Classes----------------------",true);
Set<String> keys = classNames.keySet();
Iterator it=keys.iterator();
while(it.hasNext())
{
String k=(String)it.next();
String v = classNames.get(k);
printDbg(v+" = "+k,true);
}
}
}
/**
* Clears out the log and debug files
*/
public void clearLogger( )
{
clearLogger( getCaller() );
}
/**
* Clears out the log and debug files
* This method is generally for the PyLogger class
* @param caller
*/
public void clearLogger( String caller )
{
DateCalendar d = new DateCalendar();
String outp = "\n--------------- ["+d.toTimeStamp()+", "+caller+"] --- CLEARED ----------\n";
printLog(outp,false);
if (logThreshold < INFO )
printDbg(outp,false);
else
{
File f = new File(dbgFile);
f.delete();
}
}
/**
* Put a message to the *.log or *.dbg files based on the LogLevel given
* @param level
* @param msg
* @throws Exception
*/
public void putMsg(int level, String msg)
{
if (level == FATAL)
{
DateCalendar d = new DateCalendar();
String t = levelToString(level)+": ";
String outp = "["+d.timeString()+"] "+t.substring(0,9)+msg;
printLog(outp,true);
if (logThreshold < INFO )
printDbg(outp,true);
}
else if (level >= logThreshold || level==DIVIDER)
{
putMsg(getCaller(),level,msg);
}
}
/**
* Put a message to the *.log or *.dbg files based on the LogLevel given
* Generally for the PyLogger class
* @param caller
* @param level
* @param msg
* @throws Exception
*/
public void putMsg(String caller, int level, String msg)
{
if (level >= logThreshold || level == DIVIDER)
{
DateCalendar d = new DateCalendar();
if (level==DIVIDER)
{
String outp = "\n=============== ["+d.toTimeStamp()+", "+caller+"] ====================\n";
printLog(outp,true);
if (logThreshold < INFO)
printDbg(outp,true);
}
else if (level<INFO)
{
caller = shortHandCaller(caller);
String outp = "["+d.timeString()+" - "+caller+"] "+msg;
printDbg(outp,true);
}
else
{
String t = levelToString(level)+": ";
String outp = "["+d.timeString()+"] "+t.substring(0,9)+msg;
printLog(outp,true);
if (level != FATAL)
{
System.out.println(msg);
System.out.flush();
}
}
}
}
/**
* Scan the callstack to find the entry which called this method
* @return callStackEntry
*/
public static synchronized String getCaller()
{
String[] callStack = getCallStackAsStringArray();
for (int i=0; i<callStack.length; i++)
if (callStack[i].contains("getCaller:") )
return callStack[i+2];
return callStack[callStack.length-1];
}
/**
* Return a Logger enum level from the level's name
* @param level
* @return int
*/
public static int levelFmString( String level )
{
int lvl;
if (level.equals("DEEP_DBUG")) lvl=DEEP_DBUG;
else if (level.equals("MID_DBUG")) lvl=MID_DBUG;
else if (level.equals("LITE_DBUG")) lvl=LITE_DBUG;
else if (level.equals("INFO")) lvl=INFO;
else if (level.equals("WARN")) lvl=WARN;
else if (level.equals("FATAL")) lvl=FATAL;
else if (level.equals("OFF")) lvl=OFF;
else lvl=ERROR;
return lvl;
}
/**
* Return a Logger enum logging level as a String
* @param level
* @return string
*/
public static String levelToString( int level )
{
String result;
switch (level)
{
case DEEP_DBUG: result="DEEP_DBUG"; break;
case MID_DBUG: result="MID_DBUG"; break;
case LITE_DBUG: result="LITE_DBUG"; break;
case INFO: result="INFO"; break;
case WARN: result="WARN"; break;
case FATAL: result="FATAL"; break;
case OFF: result="OFF"; break;
case ERROR:
default: result="ERROR: "; break;
}
return result;
}
//===== P R I V A T E =====================================================
/**
* Creates a new Logger instance. use getInstance() instead
* The caller parm provided is recorded in the log file. This is generally for the PyLogger class
* @param name
*/
private Logger(String caller) throws Exception
{
if (instance!=null)
{
instance.close();
instance=null;
}
classNames = new HashMap<String,String>();
classCount=0;
instanceName = "sshxcute";
pathToFiles = System.getProperty("user.dir") ;
logFile=pathToFiles +"/"+"sshxcute"+".log";
dbgFile=pathToFiles +"/"+"sshxcute"+".dbg";
this.putMsg(caller,DIVIDER,"");
}
/**
* Strips the leading part of a classname from 'inp' and replaces it with
* the shorthand name (ie: #1) that has been saved in the classNames hash.
* If the shorthand name has not been seen yet, it is created and added to the hash
* This technique makes the *.dbg file easier to read
*/
private String shortHandCaller( String inp )
{
String caller;
String shortName;
String prefix;
int pos = inp.lastIndexOf("/");
if (pos==-1)
{
pos = inp.lastIndexOf(".");
prefix = inp.substring(0,pos);
pos = prefix.lastIndexOf(".");
}
prefix = inp.substring(0,pos);
shortName = classNames.get(prefix);
if (shortName == null)
{
classCount++;
shortName = "#"+classCount;
classNames.put(prefix,shortName);
}
caller = shortName+"/"+inp.substring(pos+1);
return caller;
}
/**
* Low-level interface to write the *.log file
* @param msg
* @param append
*/
private synchronized void printLog( String msg, boolean append )
{
try {
FileWriter w = new FileWriter(logFile,append);
w.write(msg+"\n");
w.flush();
w.close();
} catch (IOException e)
{
System.out.println("IOException in Logger, message: "+e.getMessage());
System.out.println(msg);
}
}
/**
* Low-level interface to write the *.dbg file
* @param msg
* @param append
*/
private synchronized void printDbg(String msg, boolean append )
{
try {
FileWriter w = new FileWriter(dbgFile,append);
w.write(msg+"\n");
w.flush();
w.close();
} catch (IOException e)
{
System.out.println("IOException on debug message: "+e.getMessage());
System.out.println("DEBUG: "+msg);
}
}
/**
* low-level interface to get the callstack as an array of strings
* @return
*/
private synchronized static String[] getCallStackAsStringArray() {
ArrayList<String> list = new ArrayList<String>();
String[] array = new String[1];
StackTraceElement[] stackTraceElements =
Thread.currentThread().getStackTrace();
for (int i = 0; i < stackTraceElements.length; i++) {
StackTraceElement element = stackTraceElements[i];
String classname = element.getClassName();
String methodName = element.getMethodName();
int lineNumber = element.getLineNumber();
String entry = classname + "." + methodName + ":" + lineNumber;
list.add(entry);
}
return list.toArray(array);
}
/**
* This class extends GregorianCalendar and provides some useful methods.
*/
private class DateCalendar extends GregorianCalendar
{
private static final long serialVersionUID = -98734585L;
/**
* Constructs a default DateCalendar using the current time in the default time zone with the default locale.
*
*/
public DateCalendar()
{
super();
}
/**
* Gets the value for a given time field.
*
* @param field the given time field.
* @return the value for the given time field.
*/
public int get(int field)
{
int result = (field == MONTH) ? (super.get(field) + 1) : (super.get(field));
return result;
}
/**
* Get a String representation of a GregorianCalendar (yyyy/mm/dd hh:mm:ss)
* @return TimeString A string formatted as yyyy.mm.dd.hh.mm.ss
*/
public String toString()
{
int year = this.get(DateCalendar.YEAR);
int month = this.get(DateCalendar.MONTH);
int day = this.get(DateCalendar.DAY_OF_MONTH);
int hour24 = this.get(DateCalendar.HOUR_OF_DAY);
int min = this.get(DateCalendar.MINUTE);
int sec = this.get(DateCalendar.SECOND);
String datetimeString = String.valueOf(year) + "." + String.valueOf(month) + "." + String.valueOf(day)
+ "." + String.valueOf(hour24) + "." + String.valueOf(min) + "." + String.valueOf(sec);
return datetimeString;
}
public String timeString()
{
return toTimeStamp().substring(11);
}
/**
* Get an ISO String representation of a GregorianCalendar
* @return TimeString A string formatted as yyyy/mm/dd hh:mm:ss
*/
public String toTimeStamp()
{
String year = String.valueOf(this.get(DateCalendar.YEAR));
String month = String.valueOf(this.get(DateCalendar.MONTH));
String day = String.valueOf(this.get(DateCalendar.DAY_OF_MONTH));
String hour24 = String.valueOf(this.get(DateCalendar.HOUR_OF_DAY));
String min = String.valueOf(this.get(DateCalendar.MINUTE));
String sec = String.valueOf(this.get(DateCalendar.SECOND));
if (month.length() == 1)
{
month = "0" + month;
}
if (day.length() == 1)
{
day = "0" + day;
}
if (hour24.length() == 1)
{
hour24 = "0" + hour24;
}
if (min.length() == 1)
{
min = "0" + min;
}
if (sec.length() == 1)
{
sec = "0" + sec;
}
String datetimeString = year + "/" + month + "/" + day + " " + hour24 + ":" + min + ":" + sec;
return datetimeString;
}
}
}
|