/* Copyright (C) 2002-2003 Christoph Steinbeck <steinbeck@users.sf.net>
* 2002-2008 Egon Willighagen <egonw@users.sf.net>
*
* Contact: cdk-devel@lists.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.openscience.cdk.tools;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.StringReader;
import org.apache.log4j.Logger;
import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.annotations.TestMethod;
/**
* Useful for logging messages. Often used as a class static variable instantiated like:
* <pre>
* public class SomeClass {
* private static ILoggingTool logger =
* LoggingToolFactory.createLoggingTool(SomeClass.class);
* }
* </pre>
* There is no special reason not to make the logger private and static, as the logging
* information is closely bound to one specific Class, not subclasses and not instances.
*
* <p>The logger has five logging levels:
* <ul><dl>
* <dt>DEBUG
* <dd>Default mode. Used for information you might need to track down the cause of a
* bug in the source code, or to understand how an algorithm works.
* <dt>WARNING
* <dd>This indicates a special situation which is unlike to happen, but for which no
* special actions need to be taken. E.g. missing information in files, or an
* unknown atom type. The action is normally something user friendly.
* <dt>INFO
* <dd>For reporting informative information to the user that he might easily disregard.
* Real important information should be given to the user using a GUI element.
* <dt>FATAL
* <dd>This level is used for situations that should not have happened *and* that
* lead to a situation where this program can no longer function (rare in Java).
* <dt>ERROR
* <dd>This level is used for situations that should not have happened *and* thus
* indicate a bug.
* </dl></ul>
*
* <p>Consider that the debugging will not always be turned on. Therefore, it is better
* not to concatenate string in the logger.debug() call, but have the LoggingTool do
* this when appropriate. In other words, use:
* <pre>
* logger.debug("The String X has this value: ", someString);
* logger.debug("The int Y has this value: ", y);
* </pre>
* instead of:
* <pre>
* logger.debug("The String X has this value: " + someString);
* logger.debug("The int Y has this value: " + y);
* </pre>
*
* <p>For logging calls that require even more computation you can use the
* <code>isDebugEnabled()</code> method:
* <pre>
* if (logger.isDebugEnabled()) {
* logger.info("The 1056389822th prime that is used is: ",
* calculatePrime(1056389822));
* }
* </pre>
*
* <p>The class uses log4j as a backend if available, and System.out otherwise.
*
* @cdk.module log4j
* @cdk.githash
* @cdk.builddepends log4j.jar
*/
@TestClass("org.openscience.cdk.tools.LoggingToolTest")
public class LoggingTool implements ILoggingTool {
private boolean doDebug = false;
private boolean toSTDOUT = false;
private Logger log4jLogger;
private static ILoggingTool logger;
private String classname;
private int stackLength; // NOPMD
/** Default number of StackTraceElements to be printed by debug(Exception). */
public final int DEFAULT_STACK_LENGTH = 5;
/**
* Constructs a LoggingTool which produces log lines without any special
* indication which class the message originates from.
*/
public LoggingTool() {
this(LoggingTool.class);
}
/**
* Constructs a LoggingTool which produces log lines indicating them to be
* for the Class of the <code>Object</code>.
*
* @param object Object from which the log messages originate
*/
public LoggingTool(Object object) {
this(object.getClass());
}
/**
* Constructs a LoggingTool which produces log lines indicating them to be
* for the given Class.
*
* @param classInst Class from which the log messages originate
*/
public LoggingTool(Class<?> classInst) {
LoggingTool.logger = this;
stackLength = DEFAULT_STACK_LENGTH;
this.classname = classInst.getName();
try {
log4jLogger = Logger.getLogger(classname);
} catch (NoClassDefFoundError e) {
toSTDOUT = true;
logger.debug("Log4J class not found!");
} catch (NullPointerException e) { // NOPMD
toSTDOUT = true;
logger.debug("Properties file not found!");
} catch (Exception e) {
toSTDOUT = true;
logger.debug("Unknown error occured: ", e.getMessage());
}
/****************************************************************
* but some JVMs (i.e. MSFT) won't pass the SecurityException to
* this exception handler. So we are going to check the JVM
* version first
****************************************************************/
doDebug = false;
String strJvmVersion = System.getProperty("java.version");
if (strJvmVersion.compareTo("1.2") >= 0) {
// Use a try {} to catch SecurityExceptions when used in applets
try {
// by default debugging is set off, but it can be turned on
// with starting java like "java -Dcdk.debugging=true"
if (System.getProperty("cdk.debugging", "false").equals("true")) {
doDebug = true;
}
if (System.getProperty("cdk.debug.stdout", "false").equals("true")) {
toSTDOUT = true;
}
} catch (Exception e) {
logger.debug("guessed what happened: security exception thrown by applet runner");
logger.debug(" therefore, do not debug");
}
}
}
/**
* Forces the <code>LoggingTool</code> to configurate the Log4J toolkit.
* Normally this should be done by the application that uses the CDK library,
* but is available for convenience.
*/
@TestMethod("testConfigureLog4j")
public static void configureLog4j() {
LoggingTool localLogger = new LoggingTool(LoggingTool.class);
try { // NOPMD
org.apache.log4j.PropertyConfigurator.configure(
LoggingTool.class.getResource("/org/openscience/cdk/config/data/log4j.properties"));
} catch (NullPointerException e) { // NOPMD
localLogger.error("Properties file not found: ", e.getMessage());
localLogger.debug(e);
} catch (Exception e) {
localLogger.error("Unknown error occured: ", e.getMessage());
localLogger.debug(e);
}
}
/**
* Outputs system properties for the operating system and the java
* version. More specifically: os.name, os.version, os.arch, java.version
* and java.vendor.
*/
@TestMethod("testDumpSystemProperties")
public void dumpSystemProperties() {
debug("os.name : " + System.getProperty("os.name"));
debug("os.version : " + System.getProperty("os.version"));
debug("os.arch : " + System.getProperty("os.arch"));
debug("java.version : " + System.getProperty("java.version"));
debug("java.vendor : " + System.getProperty("java.vendor"));
}
/**
* Sets the number of StackTraceElements to be printed in DEBUG mode when
* calling <code>debug(Throwable)</code>.
* The default value is DEFAULT_STACK_LENGTH.
*
* @param length the new stack length
*
* @see #DEFAULT_STACK_LENGTH
*/
@TestMethod("testSetStackLength_int")
public void setStackLength(int length) {
this.stackLength = length;
}
/**
* Outputs the system property for java.class.path.
*/
@TestMethod("testDumpClasspath")
public void dumpClasspath() {
debug("java.class.path: " + System.getProperty("java.class.path"));
}
/**
* Shows DEBUG output for the Object. If the object is an instanceof
* Throwable it will output the trace. Otherwise it will use the
* toString() method.
*
* @param object Object to apply toString() too and output
*/
@TestMethod("testDebug_Object")
public void debug(Object object) {
if (doDebug) {
if (object instanceof Throwable) {
debugThrowable((Throwable)object);
} else {
debugString("" + object);
}
}
}
private void debugString(String string) {
if (toSTDOUT) {
printToSTDOUT("DEBUG", string);
} else {
log4jLogger.debug(string);
}
}
/**
* Shows DEBUG output for the given Object's. It uses the
* toString() method to concatenate the objects.
*
* @param object Object to apply toString() too and output
* @param objects Object[] to apply toString() too and output
*/
@TestMethod("testDebug_Object_int")
public void debug(Object object, Object... objects) {
if (doDebug) {
StringBuilder result = new StringBuilder();
result.append(object.toString());
for (Object obj : objects) {
result.append(obj.toString());
}
debugString(result.toString());
}
}
private void debugThrowable(Throwable problem) {
if (problem != null) {
if (problem instanceof Error) {
debug("Error: ", problem.getMessage());
} else {
debug("Exception: ", problem.getMessage());
}
java.io.StringWriter stackTraceWriter = new java.io.StringWriter();
problem.printStackTrace(new PrintWriter(stackTraceWriter));
String trace = stackTraceWriter.toString();
try {
BufferedReader reader = new BufferedReader(new StringReader(trace));
if (reader.ready()) {
String traceLine = reader.readLine();
int counter = 0;
while (reader.ready() && traceLine != null &&
(counter < stackLength)) {
debug(traceLine);
traceLine = reader.readLine();
counter++;
}
}
} catch (Exception ioException) {
error("Serious error in LoggingTool while printing exception stack trace: " +
ioException.getMessage());
logger.debug(ioException);
}
Throwable cause = problem.getCause();
if (cause != null) {
debug("Caused by: ");
debugThrowable(cause);
}
}
}
/**
* Shows ERROR output for the Object. It uses the toString() method.
*
* @param object Object to apply toString() too and output
*/
@TestMethod("testError_Object")
public void error(Object object) {
if (doDebug) {
errorString("" + object);
}
}
/**
* Shows ERROR output for the given Object's. It uses the
* toString() method to concatenate the objects.
*
* @param object Object to apply toString() too and output
* @param objects Object[] to apply toString() too and output
*/
@TestMethod("testError_Object_int")
public void error(Object object, Object... objects) {
if (doDebug) {
StringBuilder result = new StringBuilder();
result.append(object.toString());
for (Object obj : objects) {
result.append(obj.toString());
}
errorString(result.toString());
}
}
private void errorString(String string) {
if (toSTDOUT) {
printToSTDOUT("ERROR", string);
} else {
log4jLogger.error(string);
}
}
/**
* Shows FATAL output for the Object. It uses the toString() method.
*
* @param object Object to apply toString() too and output
*/
@TestMethod("testFatal_Object")
public void fatal(Object object) {
if (doDebug) {
if (toSTDOUT) {
printToSTDOUT("FATAL", object.toString());
} else {
log4jLogger.fatal("" + object.toString());
}
}
}
/**
* Shows INFO output for the Object. It uses the toString() method.
*
* @param object Object to apply toString() too and output
*/
@TestMethod("testInfo_Object")
public void info(Object object) {
if (doDebug) {
infoString("" + object);
}
}
/**
* Shows INFO output for the given Object's. It uses the
* toString() method to concatenate the objects.
*
* @param object Object to apply toString() too and output
* @param objects Object[] to apply toString() too and output
*/
@TestMethod("testInfo_Object_int")
public void info(Object object, Object... objects) {
if (doDebug) {
StringBuilder result = new StringBuilder();
result.append(object.toString());
for (Object obj : objects) {
result.append(obj.toString());
}
infoString(result.toString());
}
}
private void infoString(String string) {
if (toSTDOUT) {
printToSTDOUT("INFO", string);
} else {
log4jLogger.info(string);
}
}
/**
* Shows WARN output for the Object. It uses the toString() method.
*
* @param object Object to apply toString() too and output
*/
@TestMethod("testWarn_Object")
public void warn(Object object) {
if (doDebug) {
warnString("" + object);
}
}
private void warnString(String string) {
if (toSTDOUT) {
printToSTDOUT("WARN", string);
} else {
log4jLogger.warn(string);
}
}
/**
* Shows WARN output for the given Object's. It uses the
* toString() method to concatenate the objects.
*
* @param object Object to apply toString() too and output
* @param objects Object[] to apply toString() too and output
*/
@TestMethod("testWarn_Object_int")
public void warn(Object object, Object... objects) {
if (doDebug) {
StringBuilder result = new StringBuilder();
result.append(object.toString());
for (Object obj : objects) {
result.append(obj.toString());
}
warnString(result.toString());
}
}
/**
* Use this method for computational demanding debug info.
* For example:
* <pre>
* if (logger.isDebugEnabled()) {
* logger.info("The 1056389822th prime that is used is: ",
* calculatePrime(1056389822));
* }
* </pre>
*
* @return true, if debug is enabled
*/
@TestMethod("testIsDebugEnabled")
public boolean isDebugEnabled() {
return doDebug;
}
private void printToSTDOUT(String level, String message) {
System.out.print(classname);
System.out.print(" ");
System.out.print(level);
System.out.print(": ");
System.out.println(message);
}
/**
* Creates a new {@link LoggingTool} for the given class.
*
* @param sourceClass Class for which logging messages are recorded.
* @return A {@link LoggingTool}.
*/
@TestMethod("testCreate")
public static ILoggingTool create(Class<?> sourceClass) {
return new LoggingTool(sourceClass);
}
}