/*
* (C) Copyright IBM Corp. 2008
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb;
import java.io.File;
import java.io.PrintStream;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* @author DavidVyvyan
*/
public class Logger {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2008";
private static final Logger logger = new Logger( "Logger", 20 );
public static final String[] POSSIBLE_LEVELS = { "NONE", "LESS", "MORE", "ALL" };
public static final int LOG_NONE = 0;
public static final int LOG_LESS = 1;
public static final int LOG_MORE = 2;
public static final int LOG_ALL = 3;
public static final String LOG_EXCLUDE = "LOG_EXCLUDE:";
public static final int LOG_DEFAULT = LOG_NONE;
public static int logLevel = LOG_DEFAULT;
private static final String WARNING_PREFIX = "WARNING:\n\n\t********** ";
private static final String IMPORTANT_PREFIX = "******* ";
public static final String HORIZONTAL_RULE = "\n========================================================" +
"================================================================================================================\n";
private final String prefix;
private static boolean isLogTimeStamps = true;
public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
public static final String UNKNOWN_ERROR = "UNKNOWN_ERROR";
public static final String UNKNOWN_WARNING = "UNKNOWN_WARNING";
// private static final SimpleDateFormat sdf2 = new SimpleDateFormat("yyMMdd HH:mm:ss.SSS");
private static PrintStream printStream = System.out;
// private static ZKMStackTraceTranslate stackTraceTranslate;
//
// static {
//// dateFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
//// dateFormat.setTimeZone(TimeZone.getDefault());
// // Get change log file name & classpath to obfuscated bytecode & supporting classes from properties file.
//// ResourceBundle resourceBundle = ResourceBundle.getBundle("examples.log4j.ZKMSimpleFormatter");
// stackTraceTranslate = new ZKMStackTraceTranslate("ChangeLog.txt");
// }
public Logger() {
prefix = "";
}
public Logger( String kname, int tabchars ) {
// String kname = klass.getName();
// StringBuffer buf = new StringBuffer( kname.substring( kname.lastIndexOf('.')+1 ) );
StringBuffer buf = new StringBuffer( kname );
StringBuffer res = new StringBuffer();
for ( int i=0; i<tabchars-2; i++ ) {
if ( i == buf.length() ) res.append(' ');
else if ( i > buf.length() ) res.append('-');
else res.append( buf.charAt(i) );
}
res.append("> ");
prefix = res.toString();
}
public static void toggleTimeStampLogging( boolean isOn ) {
isLogTimeStamps = isOn;
}
public void logRaw( String s ) {
printStream.println( s );
}
private void logPrivate( String s ) {
if ( isLogTimeStamps ) {
// date.setTime( System.currentTimeMillis() );
// printStream.println( sdf.format( date ) + " " + prefix + s );
// Cannot use same date as method cd be multithreaded and we want to avoid having to synchronize
// Note: shift nanotime 21 to the right to obtain just the 6 digit microsecond precision.
long millis = System.currentTimeMillis();
long nanos = System.nanoTime();
// printStream.println( sdf.format(new Date(millis)) + "~" + nanos + " " + prefix + s );
// printStream.println( sdf.format(new Date(millis)) + "." + ((nanos<<1)>>1) + " " + prefix + s );
// printStream.println( sdf.format(new Date(millis)) + "." + ((nanos<<2)>>2) + " " + prefix + s );
// Keep this formatting!! - it is easily consumable by Derby: i.e. this works: values timestamp('2012-07-03 15:53:53.187')
printStream.println( sdf.format(new Date(millis)) + "~" + (nanos%1000000000) + " " + prefix + s ); // Get a precision of 10^9, to increase likelihood of seeing a useful variation
// printStream.println( sdf.format(new Date(millis)) /*+ "~" + (nanos%1000000000)*/ + " " + prefix + s );
} else
printStream.println( s );
// printStream.println( "---------------> " + s );
// printStream.println( System.currentTimeMillis() + " " + prefix + s );
// printStream.println( new Date( System.currentTimeMillis() ).toGMTString() + " " + prefix + s );
// printStream.println( new Timestamp(System.currentTimeMillis()) + " " + prefix + s );
}
// private void logThreadInfoPrivate( String s ) {
// logInfoPrivate( Thread.currentThread().getName() + ": " + s );
// }
private String getThreadInfo() {
return Thread.currentThread().getName() + ": ";
}
/////////////////////////////////////////////////////////////////
public static void setPrintStream( PrintStream ps ) {
printStream = ps;
}
// Attempt to wrap this class around a java Logger to add a handler to limit the size of the log file.
// public static void setJavaLogger( String file ) {
// java.util.logging.Logger jlogger = java.util.logging.Logger.getLogger(file);
// FileHandler handler = new FileHandler("myapp.log", 1000, 1);
// jlogger.addHandler(handler);
// }
public static void setLogLevel( int level ) {
logLevel = level;
}
public static boolean setLogLevel( String level ) {
int lvl = getLogLevel(level);
if ( -1 == lvl ) return false;
logLevel = lvl;
return true;
}
public static boolean isValidLogLevel( String level ) {
return -1 != getLogLevel(level);
}
/**
* @param level
* @return true if succeeded, false otherwise (i.e. invalid String)
*/
private static int getLogLevel( String level ) {
for (int i=0; i<POSSIBLE_LEVELS.length; i++)
if ( POSSIBLE_LEVELS[i].equalsIgnoreCase(level) ) return i;
return -1;
}
/////////////////////////////////////////////////////////////////
public void log( String s, int threshold ) {
if ( threshold <= logLevel )
switch ( threshold ) {
case LOG_NONE: logPrivate( WARNING_PREFIX + s + "\n" ); break;
default: if ( Logger.LOG_ALL > logLevel && s.startsWith(LOG_EXCLUDE) ) return; logPrivate( s );
}
}
public static String[] getLatestWarnings() {
synchronized ( distinctWarnings ) {
String[] warnings = distinctWarnings.values().toArray( new String[0] );
// userWarnings.clear();
// distinctUserWarnings.clear();
// removingOldestWarnings = false;
return warnings;
}
}
// Consider warnings as the same if they match or if they only have differing digit sequences following a non-word character
private static final Pattern numberAfterNonCharacter = Pattern.compile("(\\W)\\d+");
private static final Map<String, String> distinctWarnings = new HashMap<String, String>();
private static int getWarningRepeatCountAndRememberItsDetails( final String msg ) {
final String warningWithoutNumbers = numberAfterNonCharacter.matcher(msg).replaceAll("$1").intern();
final String timeNowString = new Timestamp(System.currentTimeMillis()).toString();
int repeatCount = 1;
synchronized ( distinctWarnings ) {
String w = distinctWarnings.get( warningWithoutNumbers );
if ( null == w )
w = timeNowString + 'x' + repeatCount + '~' + timeNowString + '#' + msg;
else {
int idx1 = w.indexOf('x'), idx2 = w.indexOf('~');
repeatCount += Integer.parseInt( w.substring(idx1+1, idx2) );
final String firstOccurenceTimeAndMessage = w.substring(idx2);
w = timeNowString + 'x' + (repeatCount+1) + firstOccurenceTimeAndMessage;
}
distinctWarnings.put( warningWithoutNumbers, w );
}
return repeatCount;
}
// private static LinkedList<String> userWarnings = new LinkedList<String>(), distinctUserWarnings = new LinkedList<String>();
// private static boolean removingOldestWarnings = false;
// private static void pushWarning( String w, int repeatCount ) {
//
// synchronized( userWarnings ) {
//
// userWarnings.addFirst(new Timestamp(System.currentTimeMillis()) + "#" + w);
// if ( 1 == repeatCount )
// distinctUserWarnings.addFirst(new Timestamp(System.currentTimeMillis()) + "#" + w); // delimit using '#' - this is split later
//
// if ( removingOldestWarnings ) userWarnings.removeLast();
// else removingOldestWarnings = userWarnings.size() >= 100;
// }
// }
//
// private static int getWarningRepeatCount( final String warningWithoutNumbers ) {
// int repeatCount = 0;
//
// synchronized( userWarnings ) {
// for ( String s : userWarnings )
// repeatCount += numberAfterNonCharacter.matcher(s.substring( s.indexOf('#')+1 )).
// replaceAll("$1").intern() == warningWithoutNumbers ? 1 : 0; //Util.getStringSimilarityPercentage(w, s) > 90 ? 1 : 0;
// }
//
// return repeatCount;
// }
private static String expandAndSaveWarning( String codeIn, String message, Throwable e ) {
String code = null != codeIn ? codeIn : null==e ? UNKNOWN_WARNING : UNKNOWN_ERROR;
// Warning queue
String msg = code + ": " + message + (null==e?"":e);
// final String warningWithoutNumbers = numberAfterNonCharacter.matcher(msg).replaceAll("$1").intern();
// int repeatCount = getWarningRepeatCount( warningWithoutNumbers ) + 1;
// pushWarning( msg, repeatCount ); //msg + (null==e?"":e) );
final int repeatCount = getWarningRepeatCountAndRememberItsDetails( msg );
String repeat = "";
if ( 9 < repeatCount ) {
if ( 10 == repeatCount ) repeat = " (10th repetition)";
else if ( 0 == repeatCount % 100 ) repeat = " (" + repeatCount + "th repetition)";
else return null; // avoid repeatedly logging the same warning
}
// Log Message
String doc = codeIn == null ? "" : "Documentation: " + findDocumentation(code);
long millis = System.currentTimeMillis();
return "WARNING:\n\n" +
sdf.format(new Date(millis)) + ' ' + "********** " + (null==e?"GDB_WARNING: ":"GDB_ERROR: ") + code + repeat + ": " + message
+ ( null != e ? Util.getExceptionAsString(e) : "\n" ) + "\n" +
sdf.format(new Date(millis)) + ' ' + doc +
"\n\n";
}
public void logThreadException( String errorCode, String message, Exception e) {
String digest = expandAndSaveWarning(errorCode, message, e);
if ( null != digest ) logPrivate( getThreadInfo() + digest);
}
public void logThreadWarning( String warningCode, String message) {
String digest = expandAndSaveWarning(warningCode, message, null);
if ( null != digest ) logPrivate( getThreadInfo() + digest); // Always log these
}
public void logThreadAlways( String s ) {
logPrivate( getThreadInfo() + s );
}
public void logThreadImportant( String s ) {
if ( Logger.LOG_LESS <= logLevel ) {
s = getThreadInfo() + s;
if ( Logger.LOG_ALL > logLevel && s.startsWith(LOG_EXCLUDE) ) return;
logPrivate( IMPORTANT_PREFIX + s );
}
}
public void logThreadInfo( String s ) {
if ( Logger.LOG_MORE <= logLevel ) {
s = getThreadInfo() + s;
if ( Logger.LOG_ALL > logLevel && s.startsWith(LOG_EXCLUDE) ) return;
logPrivate( s );
}
}
public void logThreadDetail( String s ) {
if ( Logger.LOG_ALL == logLevel )
logPrivate( getThreadInfo() + s );
}
/**
* Use this for unexpected errors -
* Use logWarning() instead for known conditions which can be logged fully in the warning text.
* logException() will print a stack trace which will make it easier to diagnose the issue.
*
* @param errorCode
* @param message
* @param e
*/
public void logException( String errorCode, String message, Throwable e) {
String digest = expandAndSaveWarning(errorCode, message, e);
// logPrivate("\n\n==>>>> Pushed warning: " + warningCode + ": " + message + e + "\n\n");
if ( null != digest ) logPrivate( digest); // Always log these
}
/**
* Use logWarning() for known conditions which can be logged fully in the warning text.
* Use logException() instead for unexpected errors - this will print a stack trace which will make it easier to diagnose the issue.
*
* @param errorCode
* @param message
* @param e
*/
public void logWarning( String warningCode, String message) {
String digest = expandAndSaveWarning(warningCode, message, null);
// logPrivate("\n\n==>>>> Pushed warning: " + warningCode + ": " + message + "\n\n");
if ( null != digest ) logPrivate( digest); // Always log these
}
public void logAlways( String s ) {
logPrivate( s );
}
public void logImportant( String s ) {
if ( Logger.LOG_LESS <= logLevel ) {
if ( Logger.LOG_ALL > logLevel && s.startsWith(LOG_EXCLUDE) ) return;
logPrivate( IMPORTANT_PREFIX + s );
}
}
public void logInfo( String s ) {
if ( Logger.LOG_MORE <= logLevel ) {
if ( Logger.LOG_ALL > logLevel && s.startsWith(LOG_EXCLUDE) ) return;
logPrivate( s );
}
}
public void logDetail( String s ) {
if ( Logger.LOG_ALL == logLevel )
logPrivate( s );
}
public static String findDocumentation(String code) {
if ( code.equals(UNKNOWN_WARNING) ) return "<unknown error/warning code>";
final String defaultPathPrefix = "<GaianDBDocumentationDirectory>/";
final String relativeDocPath = "javadoc-errors/com/ibm/gaiandb/diags/GDBMessages.html";
String installPath = Util.getInstallPath();
if ( null == installPath ) {
logger.logInfo("findDocumentation() unable to locate javadoc due to unresolved install path - path prefix will be masked");
return defaultPathPrefix + relativeDocPath + "#" + code;
}
String docPath = installPath + "/doc/" + relativeDocPath;
// System.out.println("installPath: " + installPath + ", docPath: " + docPath + ", docPathExists: " + new File(docPath).exists());
return new File(docPath).exists() ?
"file://" + ( Util.isWindowsOS ? "/" : "" ) + docPath + "#" + code :
defaultPathPrefix + relativeDocPath + "#" + code;
}
}