/*
* Javolution - Java(TM) Solution for Real-Time and Embedded Systems
* Copyright (C) 2006 - Javolution (http://javolution.org/)
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software is
* freely granted, provided that this notice is preserved.
*/
package javolution.context;
import java.lang.CharSequence;
import javolution.lang.Configurable;
import javolution.text.Text;
import javolution.text.TextBuilder;
import javolution.util.StandardLog;
/**
* <p> This class represents a context for object-based/thread-based logging
* capabilities.</p>
*
* <p> LogContext removes low level code dependency with the logging framework.
* The same code can run using system out/err, standard logging
* (<code>java.util.logging</code>), Log4J or even OSGI Log services.
* Selection can be done at run-time through {@link #DEFAULT
* configuration}).</p>
*
* <p> The {@link #DEFAULT default} logging context is {@link
* StandardLog StandardLog} to leverage <code>java.util.logging</code>
* capabilities.</p>
*
* <p> Logging a message is quite simple:(code)
* LogContext.info("my message");(/code]
* Because string formatting can be slow, we also find:[code]
* if (LogContext.isInfoLogged())
* LogContext.info("message part 1" + aVar + "message part 2");[/code]
* Or equivalent but simpler:[code]
* LogContext.info("message part 1", aVar, "message part 2");[/code]</p>
*
* <p> Logging can be temporarily altered on a thread or object basis.
* For example:[code]
* public static main(String[] args) {
* LogContext.enter(LogContext.NULL); // Temporarily disables logging.
* try {
* ClassInitializer.initializeAll(); // Initializes bootstrap, extensions and classpath classes.
* } finally {
* LogContext.exit(LogContext.NULL); // Goes back to default logging.
* }
* ...
* }[/code]</p>
*
* <p> Applications may extend this base class to address specific logging
* requirements. For example:[code]
* // This class allows for custom logging of session events.
* public abstract class SessionLog extends LogContext {
* public static void start(Session session) {
* LogContext log = LogContext.current();
* if (log instanceof SessionLog.Loggable) {
* ((SessionLog.Loggable)log).logStart(session);
* } else if (log.infoLogged()){
* log.logInfo("Session " + session.id() + " started");
* }
* }
* public static void end(Session session) { ... }
* public interface Loggable {
* void logStart(Session session);
* void logEnd(Session session);
* }
* }[/code]</p>
*
* <p> The use of interfaces (such as <code>Loggable</code> above) makes it easy
* for any context to support customs logging events.
* For example:[code]
* class MyLog extends StandardLog implements SessionLog.Loggable, DatabaseLog.Loggable {
* ... // Specialized logging for session and database events.
* }
* MyLog myLog = new MyLog();
* LogContext.enter(myLog);
* try {
* ...
* LogContext.info("Informative message"); // Standard logging.
* ...
* DatabaseLog.fail(transaction); // Database custom logging.
* ...
* SessionLog.start(session); // Session custom logging.
* ...
* } finally {
* LogContext.exit(myLog);
* }[/code]</p>
*
* @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
* @version 5.3, March 5, 2009
*/
public abstract class LogContext extends Context {
/**
* Holds the default logging context instance.
*/
private static volatile LogContext _Default = new StandardLog();
/**
* Holds the logging context implementation forwarding log events to the
* root <code>java.util.logging.Logger</code> (default logging context).
* The debug/info/warning/error events are mapped to the
* debug/info/warning/severe log levels respectively.
*/
public static final Class <? extends LogContext> STANDARD = StandardLog.class;
/**
* Holds a logging context implementation ignoring logging events.
*/
public static final Class <? extends LogContext> NULL = Null.class;
/**
* Holds a context logging debug/informative/warning/error messages
* to <code>System.out</code>.
*/
public static final Class <? extends LogContext> SYSTEM_OUT = SystemOut.class;
/**
* Holds a context logging debug/informative/warnings/errors events to
* the system console (JVM 1.6+).
*/
public static final Class <? extends LogContext> CONSOLE = Console.class;
/**
* Holds the logging context default implementation (configurable,
* default value {@link #STANDARD}).
*/
public static final Configurable <Class<? extends LogContext>> DEFAULT = new Configurable(STANDARD) {
protected void notifyChange(Object oldValue, Object newValue) {
_Default = (LogContext) ObjectFactory.getInstance((Class) newValue).object();
}
};
/**
* Default constructor.
*/
protected LogContext() {
}
/**
* Returns the current logging context. If the current thread has not
* entered any logging context the {@link #getDefault()} is returned.
*
* @return the current logging context.
*/
public static LogContext getCurrentLogContext() {
for (Context ctx = Context.getCurrentContext(); ctx != null; ctx = ctx.getOuter()) {
if (ctx instanceof LogContext)
return (LogContext) ctx;
}
return LogContext._Default;
}
/**
* Returns the default instance ({@link #DEFAULT} implementation).
*
* @return the default instance.
*/
public static LogContext getDefault() {
return LogContext._Default;
}
/**
* Indicates if debug messages are currently logged.
*
* @return <code>true</code> if debug messages are logged;
* <code>false</code> otherwise.
*/
public static boolean isDebugLogged() {
return ((LogContext) LogContext.getCurrentLogContext()).isLogged("debug");
}
/**
* Logs the specified debug message if debug messages are logged.
*
* @param message the debug message being logged.
* @see #logDebug(CharSequence)
*/
public static void debug(CharSequence message) {
((LogContext) LogContext.getCurrentLogContext()).logDebug(message);
}
/**
* Equivalent to {@link #debug(CharSequence)} except that formatting
* is done only if debug is logged.
*
* @param message the message to log.
*/
public static void debug(Object message) {
LogContext logContext = (LogContext) LogContext.getCurrentLogContext();
if (!logContext.isLogged("debug"))
return;
logContext.logDebug(Text.valueOf(message));
}
/**
* Equivalent to {@link #debug(CharSequence)} except that formatting
* is done only if debug is logged.
*
* @param messages the messages to log.
*/
public static void debug(Object... messages) {
LogContext logContext = (LogContext) LogContext.getCurrentLogContext();
if (!logContext.isLogged("debug"))
return;
Text tmp = Text.valueOf(messages[0]);
for (int i=1; i < messages.length; i++) {
tmp = tmp.plus(messages[i]);
}
logContext.logDebug(tmp);
}
/**/
/**
* Indicates if info messages are currently logged.
*
* @return <code>true</code> if info messages are logged;
* <code>false</code> otherwise.
*/
public static boolean isInfoLogged() {
return ((LogContext) LogContext.getCurrentLogContext()).isLogged("info");
}
/**
* Logs the specified informative message.
*
* @param message the informative message being logged.
* @see #logInfo(CharSequence)
*/
public static void info(CharSequence message) {
((LogContext) LogContext.getCurrentLogContext()).logInfo(message);
}
/**
* Equivalent to {@link #info(CharSequence)} except that formatting
* is done only if info is logged.
*
* @param message the message to log.
*/
public static void info(Object message) {
LogContext logContext = (LogContext) LogContext.getCurrentLogContext();
if (!logContext.isLogged("info"))
return;
logContext.logInfo(Text.valueOf(message));
}
/**
* Equivalent to {@link #info(CharSequence)} except that formatting
* is done only if info is logged.
*
* @param messages the messages to log.
*/
public static void info(Object... messages) {
LogContext logContext = (LogContext) LogContext.getCurrentLogContext();
if (!logContext.isLogged("info"))
return;
Text tmp = Text.valueOf(messages[0]);
for (int i = 1; i < messages.length; i++) {
tmp = tmp.plus(messages[i]);
}
logContext.logInfo(tmp);
}
/**/
/**
* Indicates if warning messages are currently logged.
*
* @return <code>true</code> if warning messages are logged;
* <code>false</code> otherwise.
*/
public static boolean isWarningLogged() {
return ((LogContext) LogContext.getCurrentLogContext()).isLogged("warning");
}
/**
* Logs the specified warning message.
*
* @param message the warning message being logged.
* @see #logWarning(CharSequence)
*/
public static void warning(CharSequence message) {
((LogContext) LogContext.getCurrentLogContext()).logWarning(message);
}
/**
* Equivalent to {@link #warning(CharSequence)} except that formatting
* is done only if warning is logged.
*
* @param message the message to log.
*/
public static void warning(Object message) {
LogContext logContext = (LogContext) LogContext.getCurrentLogContext();
if (!logContext.isLogged("warning"))
return;
logContext.logWarning(Text.valueOf(message));
}
/**
* Equivalent to {@link #warning(CharSequence)} except that formatting
* is done only if warning is logged.
*
* @param messages the messages to log.
*/
public static void warning(Object... messages) {
LogContext logContext = (LogContext) LogContext.getCurrentLogContext();
if (!logContext.isLogged("warning"))
return;
Text tmp = Text.valueOf(messages[0]);
for (int i = 1; i < messages.length; i++) {
tmp = tmp.plus(messages[i]);
}
logContext.logWarning(tmp);
}
/**/
/**
* Indicates if error messages are currently logged.
*
* @return <code>true</code> if error messages are logged;
* <code>false</code> otherwise.
*/
public static boolean isErrorLogged() {
return ((LogContext) LogContext.getCurrentLogContext()).isLogged("error");
}
/**
* Logs the specified error to the current logging context.
*
* @param error the error being logged.
*/
public static void error(Throwable error) {
((LogContext) LogContext.getCurrentLogContext()).logError(error, null);
}
/**
* Logs the specified error and error message to the current logging
* context.
*
* @param error the error being logged.
* @param message the supplementary message.
*/
public static void error(Throwable error, CharSequence message) {
((LogContext) LogContext.getCurrentLogContext()).logError(error, message);
}
/**
* Equivalent to {@link #error(Throwable, CharSequence)} except that
* formatting is done only if error is logged.
*
* @param error the error being logged.
* @param message the supplementary message.
*/
public static void error(Throwable error, Object message) {
LogContext logContext = (LogContext) LogContext.getCurrentLogContext();
if (!logContext.isLogged("error"))
return;
logContext.logError(error, Text.valueOf(message));
}
/**
* Equivalent to {@link #error(Throwable, CharSequence)}
* except that formatting is done only if error is logged.
*
* @param error the error being logged.
* @param messages the supplementary messages.
*/
public static void error(Throwable error, Object... messages) {
LogContext logContext = (LogContext) LogContext.getCurrentLogContext();
if (!logContext.isLogged("error"))
return;
Text tmp = Text.valueOf(messages[0]);
for (int i = 1; i < messages.length; i++) {
tmp = tmp.plus(messages[i]);
}
logContext.logError(error, tmp);
}
/**/
/**
* Logs the specified error message to the current logging
* context.
*
* @param message the error message being logged.
*/
public static void error(CharSequence message) {
((LogContext) LogContext.getCurrentLogContext()).logError(null, message);
}
/**
* Equivalent to {@link #error(CharSequence)} except that formatting
* is done only if error is logged.
*
* @param message the message to log.
*/
public static void error(Object message) {
LogContext logContext = (LogContext) LogContext.getCurrentLogContext();
if (!logContext.isLogged("error"))
return;
logContext.logError(null, Text.valueOf(message));
}
/**
* Equivalent to {@link #error(CharSequence)}
* except that formatting is done only if error is logged.
*
* @param messages the messages to log.
*/
public static void error(Object... messages) {
LogContext logContext = (LogContext) LogContext.getCurrentLogContext();
if (!logContext.isLogged("error"))
return;
Text tmp = Text.valueOf(messages[0]);
for (int i = 1; i < messages.length; i++) {
tmp = tmp.plus(messages[i]);
}
logContext.logError(null, tmp);
}
/**/
/**
* Logs the message of specified category (examples of category are
* "debug", "info", "warning", "error").
*
* @param category an identifier of the category of the messages logged.
* @param message the message itself.
*/
protected abstract void logMessage(String category, CharSequence message);
/**
* Indicates if the messages of the specified category are being logged
* (default <code>true</code> all messages are being logged).
*
* <p>Note: This method is an indicator only, not a directive.
* It allows users to bypass the logging processing if no
* actual logging is performed. If the category is not
* known then this method should return <code>true</code>
* (no optimization performed).</p>
*
* @param category an identifier of the category for the messages logged.
* @return <code>true</code> if the messages of the specified category
* are being logged; <code>false</code> otherwise.
*/
protected boolean isLogged(String category) {
return true;
}
/**
* Logs the specified debug message.
*
* @param message the debug message to be logged.
* @see #logMessage
*/
protected void logDebug(CharSequence message) {
logMessage("debug", message);
}
/**
* Logs the specified informative message.
*
* @param message the informative message to be logged.
*/
protected void logInfo(CharSequence message) {
logMessage("info", message);
}
/**
* Logs the specified warning message.
*
* @param message the warning message to be logged.
*/
protected void logWarning(CharSequence message) {
logMessage("warning", message);
}
/**
* Logs the specified error.
* The default implementation logs the message and the error stack trace
* (calls <code>logMessage("", message + stackTrace)</code>.
*
* @param error the error being logged or <code>null</code> if none.
* @param message the associated message or <code>null</code> if none.
*/
protected void logError(Throwable error, CharSequence message) {
TextBuilder tmp = TextBuilder.newInstance();
try {
if (error != null) {
tmp.append(error.getClass().getName());
tmp.append(" - ");
}
if (message != null)
tmp.append(message);
else if (error != null) // Use the error message if no message specified.
tmp.append(error.getMessage());
if (error != null) { // Outputs error stack trace.
/**/
StackTraceElement[] trace = error.getStackTrace();
for (int i = 0; i < trace.length; i++) {
tmp.append("\n\tat ");
tmp.append(trace[i]);
}
/**/
}
logMessage("error", tmp);
} finally {
TextBuilder.recycle(tmp);
}
}
// Implements Context abstract method.
protected void enterAction() {
// Do nothing.
}
// Implements Context abstract method.
protected void exitAction() {
// Do nothing.
}
/**
* This class represents the system logging context.
*/
private static class SystemOut extends LogContext {
protected void logMessage(String category, CharSequence message) {
System.out.print("[");
System.out.print(category);
System.out.print("] ");
System.out.println(message);
}
}
/**
* This class represents a non-logging context.
*/
private static final class Null extends SystemOut {
protected boolean isLogged(String category) {
return false;
}
protected void logMessage(String category, CharSequence message) {
// Do nothing.
}
}
/**
* This class represents the console logging context.
*/
private static class Console extends SystemOut {
/**/
final java.io.PrintWriter writer;
Console() {
Object console = null;
writer = null;
//java.io.Console console = System.console();
//writer = console != null ? console.writer() : null;
}
@Override
protected void logMessage(String category, CharSequence message) {
if (writer == null) {
super.logMessage(category, message);
} else {
writer.print("[");
writer.print(category);
writer.print("] ");
writer.println(message);
}
}
/**/
}
// Allows instances of private classes to be factory produced.
static {
ObjectFactory.setInstance(new ObjectFactory() {
protected Object create() {
return new Console();
}
}, CONSOLE);
ObjectFactory.setInstance(new ObjectFactory() {
protected Object create() {
return new Null();
}
}, NULL);
ObjectFactory.setInstance(new ObjectFactory() {
protected Object create() {
return new SystemOut();
}
}, SYSTEM_OUT);
}
}