package org.geogebra.common.util.debug;
import java.util.Set;
import java.util.TreeSet;
//import org.geogebra.common.main.Feature;
/**
* Common logging class
*
* @author Zoltan Kovacs <zoltan@geogebra.org>
*/
public abstract class Log {
/** logger */
private static volatile Log logger;
private static Object lock = new Object();
/**
* Logging level
*/
public static class Level {
/**
* Log level priority
*/
int priority;
/**
* Category text
*/
String text;
/**
* Creates a logging level
*
* @param priority
* Log level priority
* @param text
* Category text
*/
Level(int priority, String text) {
this.priority = priority;
this.text = text;
}
/**
* Message priority, the lower the more problematic.
*
* @return the priority
*/
public int getPriority() {
return priority;
}
}
private static Set<String> reportedImplementationNeeded = new TreeSet<String>();
/** emergency */
public final Level EMERGENCY = new Level(0, "EMERGENCY");
/** alert */
public final Level ALERT = new Level(1, "ALERT");
/** critical */
public final Level CRITICAL = new Level(2, "CRITICAL");
/** error */
public final Level ERROR = new Level(3, "ERROR");
/** warning */
public final Level WARN = new Level(4, "WARN");
/** notice */
public final Level NOTICE = new Level(5, "NOTICE");
/** information */
public final Level INFO = new Level(7, "INFO");
/** debugging (default) */
public final Level DEBUG = new Level(8, "DEBUG");
/** trace */
public final Level TRACE = new Level(9, "TRACE");
/** in case keepLog = true, this sets max length of in-memory log */
public static final int LOGFILE_MAXLENGTH = 10000;
private final StringBuilder memoryLog = new StringBuilder();
/**
* The entire log since starting the application.
*
* @return the entire log
*/
final public static StringBuilder getEntireLog() {
return logger.getEntireLogImpl();
}
private StringBuilder getEntireLogImpl() {
return memoryLog;
}
/**
* Logging destinations
*/
public enum LogDestination {
/**
* Send logging to file. A file name must also be set by using the
* setLogFile() method.
*/
FILE,
/**
* Sends logging to console. Messages <= ERROR will be written to
* STDERR, others to STDOUT in desktop mode; sends log messages via
* GWT.log to the Eclipse console in web development mode.
*/
CONSOLE
}
private Level logLevel = DEBUG; // default
private LogDestination logDestination = LogDestination.CONSOLE; // default;
private boolean timeShown = true; // default
private boolean callerShown = true; // default
private boolean levelShown = true; // default
/** whether to keep log in memory */
protected boolean keepLog = false;
/**
* Sets the current logging level
*
* @param logLevel
* the logging level to set
*/
public void setLogLevel(Level logLevel) {
this.logLevel = logLevel;
}
/**
* Sets the current logging level
*
* @param logLevel
* the logging level to set
*/
final public static void setLogLevel(String logLevel) {
logger.setLogLevelImpl(logLevel);
}
private void setLogLevelImpl(String logLevel) {
if (logLevel == null) {
return;
}
if ("ALERT".equals(logLevel)) {
this.logLevel = ALERT;
}
if ("EMERGENCY".equals(logLevel)) {
this.logLevel = EMERGENCY;
}
if ("CRITICAL".equals(logLevel)) {
this.logLevel = CRITICAL;
}
if ("ERROR".equals(logLevel)) {
this.logLevel = ERROR;
}
if ("WARN".equals(logLevel)) {
this.logLevel = WARN;
}
if ("INFO".equals(logLevel)) {
this.logLevel = INFO;
}
if ("NOTICE".equals(logLevel)) {
this.logLevel = NOTICE;
}
if ("DEBUG".equals(logLevel)) {
this.logLevel = DEBUG;
}
if ("TRACE".equals(logLevel)) {
this.logLevel = TRACE;
}
}
/**
* Returns the current logging level
*
* @return the current level
*/
public Level getLogLevel() {
return logLevel;
}
/**
* Sets the logger destination (FILE, CONSOLE, WEB_CONSOLE, CONSOLES)
*
* @param logDestination
* the destination
*/
final public static void setLogDestination(LogDestination logDestination) {
logger.setLogDestinationImpl(logDestination);
}
protected void setLogDestinationImpl(LogDestination logDestination) {
this.logDestination = logDestination;
}
/**
* Returns the logger destination (FILE, CONSOLE, WEB_CONSOLE, CONSOLES)
*
* @return the destination
*/
final public static LogDestination getLogDestination() {
return logger.getLogDestinationImpl();
}
protected LogDestination getLogDestinationImpl() {
return logDestination;
}
/**
* Reports if the timestamp is printed for logging
*
* @return if the names are printed
*/
public boolean isTimeShown() {
return timeShown;
}
/**
* Sets if the report time of the log message should be printed for logging.
* May not be available for all platforms (returns empty string when not).
*
* @param timeShown
* if the timestamp should be printed
*/
final public static void setTimeShown(boolean timeShown) {
logger.setTimeShownImpl(timeShown);
}
protected void setTimeShownImpl(boolean timeShown) {
this.timeShown = timeShown;
}
/**
* Reports if the caller class and method names are printed for logging
*
* @return if the names are printed
*/
public boolean isCallerShown() {
return callerShown;
}
/**
* Sets if the caller class and method names should be printed for logging
*
* @param callerShown
* if the names should be printed
*/
public static void setCallerShown(boolean callerShown) {
logger.callerShown = callerShown;
}
/**
* @return the levelShown
*/
public boolean isLevelShown() {
return levelShown;
}
/**
* @param levelShown
* the levelShown to set
*/
public static void setLevelShown(boolean levelShown) {
logger.levelShown = levelShown;
}
/**
* Prints a log message if the logLevel is set to <= level and stores those
* classes which have no implementation (simply checks if the message starts
* with "implementation needed")
*
* @param level
* logging level
* @param logMessage
* the log message
* @param depth
* depth in stacktrace
*/
public void log(Level level, String logMessage, int depth) {
String message = logMessage;
if (message == null) {
message = "*null*";
}
if (logLevel.getPriority() >= level.getPriority()) {
String caller = "";
if (callerShown) {
caller = getCaller(depth);
handleImplementationNeeded(caller, message);
caller += ": ";
}
String timeInfo = "";
if (timeShown) {
timeInfo = getTimeInfo();
if (!"".equals(timeInfo)) {
timeInfo += " ";
}
}
// Creating logEntry
String logEntry = timeInfo;
if (levelShown) {
logEntry += level.text + ": ";
}
logEntry += caller + message;
print(logEntry, level);
// In desktop logging, preserve the entire log in memory as well:
if (keepLog) {
if (memoryLog.length() > LOGFILE_MAXLENGTH) {
memoryLog.setLength(0);
}
memoryLog.append(logEntry);
memoryLog.append("\n");
}
}
}
private static void handleImplementationNeeded(String caller,
String message) {
if (message.length() >= 21
&& message.toLowerCase().substring(0, 21)
.equals("implementation needed")) {
if (!reportedImplementationNeeded.contains(caller)) {
reportedImplementationNeeded.add(caller);
}
}
}
/**
* Prints the log entry, which is usually the full message with timestamp,
* the logging level and the caller class
*
* @param logEntry
* the full log entry
* @param level
* logging level
*/
protected abstract void print(String logEntry, Level level);
/**
* Sets the log file name (if FILE logging is available)
*
* @param logFileName
* the name of the log file
*/
final public static void setLogFile(String logFileName) {
logger.setLogFileImpl(logFileName);
}
protected void setLogFileImpl(String logFileName) {
// overridden in some implementations
}
/**
* Returns the current time in human readable format (for debugging)
*
* @return the timestamp
*/
final public static String getTimeInfo() {
return logger.getTimeInfoImpl();
}
protected String getTimeInfoImpl() {
return "";
// Implementation overrides this in some applications.
}
/**
* Returns some memory related information (for debugging)
*
* @return the memory info text
*/
public String getMemoryInfo() {
return "";
// Implementation overrides this in some applications.
}
/**
* Returns the caller class and method names
*
* @param depth
* depth in stacktrace
*
* @return the full Java class and method name
*/
public String getCaller(int depth) {
String callerMethodName = null;
String callerClassName = null;
int callerLineNumber;
try {
Throwable t = new Throwable();
StackTraceElement[] elements = t.getStackTrace();
// String calleeMethod = elements[0].getMethodName();
callerMethodName = elements[depth].getMethodName();
callerClassName = elements[depth].getClassName();
callerLineNumber = elements[depth].getLineNumber();
if ("Unknown".equals(callerClassName)) {
/*
* In web production mode the GWT compile rewrites the code very
* thoroughly. We are doing some intuitive hacking here to
* explode the method name; since other information (class name,
* line number) is unavailable.
*/
// PRETTY style
// safari:
if ("$fillInStackTrace".equals(callerMethodName)) {
if (elements.length < 10) {
return "?";
}
return elements[9].getMethodName();
}
// gecko1_8
if ("fillInStackTrace".equals(callerMethodName)) {
if (elements.length < 11) {
return "?";
}
return elements[10].getMethodName();
}
// TODO: Maybe other user agents could be supported.
// OBFUSCATED style
return callerMethodName;
}
} catch (Throwable t) {
// do nothing here; we are probably running Web in Opera
return "?";
}
return callerClassName + "." + callerMethodName + "[" + callerLineNumber
+ "]";
}
/**
* Prints debugging message, level DEBUG
*
* @param message
* message to be printed
*/
public static void debug(String message) {
if (logger != null) {
logger.log(logger.DEBUG, message);
}
}
public static void debug(Object f, String message) {
if (logger != null) {
logger.log(logger.DEBUG, "[" + f + "] " + message);
}
}
/**
* @param message
* debug message
* @param depth
* stacktace depth in which to look (4 if you want to see direct
* caller, 5 for one above)
*/
public static void debug(String message, int depth) {
if (logger != null) {
logger.log(logger.DEBUG, message, depth);
}
}
/**
* @param message
* error message
* @param depth
* stacktace depth in which to look (4 if you want to see direct
* caller, 5 for one above)
*/
public static void error(String message, int depth) {
if (logger != null) {
logger.log(logger.ERROR, message, depth);
}
}
/**
* Prints debugging message, level NOTICE
*
* @param message
* message to be printed
*/
public static void notice(String message) {
if (logger != null) {
logger.log(logger.NOTICE, message);
}
}
/**
* Prints debugging message, level DEBUG Special debugging format is used
* for expression values
*
* @param s
* object to be printed
*/
public static void debug(Object s) {
if (s instanceof double[]) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ((double[]) s).length; i++) {
sb.append(((double[]) s)[i]);
sb.append(',');
}
debug(sb.toString());
return;
}
if (s instanceof HasDebugString) {
debug(((HasDebugString) s).getDebugString(), 4);
return;
}
if (s instanceof Throwable && logger != null) {
logger.doPrintStacktrace((Throwable) s);
return;
}
if (s == null) {
debug("<null>", 5);
} else {
debug(s.toString(), 5);
}
}
/**
* @param s
* exception
*/
protected void doPrintStacktrace(Throwable s) {
s.printStackTrace();
}
private void log(Level level, String message) {
log(level, message, 4);
}
/**
* Prints debugging message, level INFO
*
* @param message
* message to be printed
*/
public static void info(String message) {
if (logger != null) {
logger.log(logger.INFO, message);
}
}
/**
* Prints debugging message, level ERROR
*
* @param message
* message to be printed
*/
public static void error(String message) {
if (logger != null) {
logger.log(logger.ERROR, message);
}
}
/**
* Prints debugging message, level WARN
*
* @param message
* message to be printed
*/
public static void warn(String message) {
if (logger != null) {
logger.log(logger.WARN, message);
}
}
/**
* Prints debugging message, level EMERGENCY
*
* @param message
* message to be printed
*/
public static void emergency(String message) {
if (logger != null) {
logger.log(logger.EMERGENCY, message);
}
}
/**
* Prints debugging message, level ALERT
*
* @param message
* message to be printed
*/
public static void alert(String message) {
if (logger != null) {
logger.log(logger.ALERT, message);
}
}
/**
* Prints debugging message, level TRACE
*
* @param message
* message to be printed
*/
public static void trace(String message) {
if (logger != null) {
logger.log(logger.TRACE, message);
}
}
/**
* Prints debugging message, level CRITICAL
*
* @param message
* message to be printed
*/
public static void critical(String message) {
if (logger != null) {
logger.log(logger.CRITICAL, message);
}
}
/**
*
* @param message
* content to be printed on top of the trace
*/
public static void printStacktrace(Object message) {
if (logger != null) {
logger.doPrintStacktrace(
message == null ? "*null*" : message.toString());
}
}
/**
* @param message
* message at the top of the trace
*/
public abstract void doPrintStacktrace(String message);
/**
* @param log
* sets the logger to this
*/
public static void setLogger(Log log) {
synchronized (lock) {
logger = log;
}
}
public static Log getLogger() {
return logger;
}
}