/* Log.java Purpose: The logging utilities Description: History: 2001/11/07 14:55:06, Create, Tom M. Yeh. Copyright (C) 2001 Potix Corporation. All Rights Reserved. {{IS_RIGHT This program is distributed under LGPL Version 2.1 in the hope that it will be useful, but WITHOUT ANY WARRANTY. }}IS_RIGHT */ package org.zkoss.util.logging; import java.util.Iterator; import java.util.Map; import java.util.List; import java.util.LinkedList; import java.util.Properties; import java.util.Locale; import java.util.logging.Logger; import java.util.logging.LogManager; import java.util.logging.Level; import java.util.logging.Handler; import java.io.ByteArrayInputStream; import org.zkoss.lang.Library; import org.zkoss.lang.Objects; import org.zkoss.lang.Exceptions; import org.zkoss.util.Locales; import org.zkoss.mesg.Messages; /** * The logger. Usage: * * <p><code>private static final Log log = Log.lookup(MyClass.class);<br> * ...<br> * if (log.debugable()) log.debug("the information to log:"+some);</code> * * <p>{@link Log} is designed to minimize the memory usage by avoiding * unnecessary allocation of java.util.logging.Logger. * In additions, it is possible to use different logger, e.g., log4j, * without affecting the client codes. * * <p>Since this object is very light-weight, it is OK to have * the following statement without using it.<br> * private static final Log log = Log.lookup(MyClass.class); * * <p>To log error or warning, simple use the error and warning method. * * <p>To log info, depending on the complexity you might want to test infoable * first.<br> * <pre><code>log.info("a simple info"); *if (log.infoable()) * log.info(a + complex + string + operation);</code></pre> * * <p>To log debug information, we usually also test with D.<br> * <pre><code>log.debug("a simple info"); *if (log.debugable()) * log.debug(a + complex + string + operation);</code></pre> * * <p>There is a special level called FINER whose priority is lower than DEBUG. * It is generally used * to log massive debug message under certain situation. In other words, * it is suggested to use the debug methods to log messages as follows: * * <pre><code>if (log.finerable()) { * ... do massive testing and/or printing (use finer) *}</code></pre> * * @author tomyeh * @deprecated As of release 7.0.0, use SLF4J API for logging instead. */ public class Log { /** All levels. */ public static final Level ALL = Level.ALL; /** The ERROR level. */ public static final Level ERROR = Level.SEVERE; /** The WARNING level. */ public static final Level WARNING = Level.WARNING; /** The INFO level. */ public static final Level INFO = Level.INFO; /** The DEBUG level. */ public static final Level DEBUG = Level.FINE; /** The FINER level. */ public static final Level FINER = Level.FINER; /** The OFF level used to turn of the logging. */ public static final Level OFF = Level.OFF; /** The default name when the loggers don't support hierarchy. */ private static final String DEFAULT_NAME = "org.zkoss"; //don't change its value since DHtmlLayoutServlet depends on it /** Whether the loggers supports the hierarchy. */ private static boolean _hierarchy; /** ZK-694: we have to store it to avoid being GCed. */ private static Logger[] _configedLoggers; private static Logger _default; /** The category that this log belongs. * Note: it is temporary and set to null when {@link #logger} is called. */ private String _name; /** Used if useHierachy() is true. */ private Logger _logger; /** * Configures based the properties. * * <p>The key is a logger name and the value is the level. * * @param props the properties * @since 6.0.0 */ public final static void configure(Properties props) { Log log = null; final StringBuffer sb = new StringBuffer(); String[] handlers = null; // read handlers first - ZK-1893 final String values = (String) props.remove("handlers"); if (values != null) { handlers = values.split("[, ]"); for (int j = handlers.length; --j >= 0;) { handlers[j] = handlers[j].trim(); handlers[j] = handlers[j].length() > 0 ? handlers[j] + '.' : null; } sb.append("handlers").append('=').append(values).append('\n'); } for (Iterator<Map.Entry<Object, Object>> it = props.entrySet().iterator(); it.hasNext();) { final Map.Entry<Object, Object> me = it.next(); final String key = (String)me.getKey(); final String val = (String)me.getValue(); boolean matched = false; if (handlers != null) { for (String h: handlers) { if (h != null && key.startsWith(h)) { matched = true; break; } } } if (matched) { sb.append(key).append('=').append(val).append('\n'); it.remove(); } } if (sb.length() > 0) { try { LogManager.getLogManager().readConfiguration( new ByteArrayInputStream(sb.toString().getBytes())); } catch (Throwable ex) { //GAE doesn't support LogManager lookup(Log.class).warningBriefly("Failed to configure LogManager", ex); } } final List<Logger> configed = new LinkedList<Logger>(); for (Map.Entry<Object, Object> me: props.entrySet()) { String key = (String)me.getKey(); final String val = ((String)me.getValue()).trim(); if (key.endsWith(".level")) //compatible with LogManager key = key.substring(0, key.length() - 6); final Level level = Log.getLevel(val); if (level != null) { final Logger logger = Logger.getLogger(key); logger.setLevel(level); configed.add(logger); } else { if (log == null) log = lookup(Log.class); log.warning("Illegal log level, "+val+", for "+key); } } //ZK-694: we have to store them. Otherwise, they will be GCed if (!configed.isEmpty()) { setHierarchy(true); //turn on the hierarchy _configedLoggers = configed.toArray(new Logger[configed.size()]); } } /** Returns whether the loggers support hierarchy. * If hierarchy is supported, a {@link Log} instance is mapped to * a {@link Logger} instance with the same name. Therefore, it * forms the hierarchical relationship among {@link Logger} instances. * It has the best resolution to control which logger to enable. * * <p>On the other hand, if the loggers don't support hierarchy, * all {@link Log} instances are actually mapped to the same * {@link Logger} called "org.zkoss". * The performance is better in this mode. * * <p>Default: false. * * <p>Note: {@link #configure} will invoke {@link #setHierarchy} with * true automatically to turn on the hierarchy support, if any level * is defined. */ public static final boolean isHierarchy() { return _hierarchy; } /** Sets whether to support the hierarchical loggers. */ public static final void setHierarchy(boolean hierarchy) { _hierarchy = hierarchy; } /** This property is deprecated in ZK 6, since it is longer required for GAE. * HOwever, we keep it here for backward compatibility. * We might remove it in the future. */ private static final boolean hierarchyDisabled() { if (_hierarchyDisabled == null) _hierarchyDisabled = Boolean.valueOf("true".equals( Library.getProperty("org.zkoss.util.logging.hierarchy.disabled"))); return _hierarchyDisabled.booleanValue(); } private static Boolean _hierarchyDisabled; /** * Gets the logger based on the class. * @param cls the class that identifies the logger. */ public static final Log lookup(Class cls) { return new Log(cls.getName()); } /** * Gets the logger based on the giving name. * <p>Since 5.0.7, this constructor, unlike others, ignores * {@link #isHierarchy} and always assumes the hierarchy name. * Notice the hierarchy is always disabled if a library property called * <code>org.zkoss.util.logging.hierarchy.disabled</code> is set to true. */ public static final Log lookup(String name) { return new HierLog(name); } /** Gets the logger based on the package. */ public static final Log lookup(Package pkg) { return new Log(pkg.getName()); } /** * The constructor. */ protected Log(String name) { if (name == null) throw new IllegalArgumentException(name); _name = name; } /** Returns the name of this logger. */ public final String getName() { return _name; } /** Returns the logger (never null). * <p>If not found, it created a new one. */ private final Logger getLogger() { if (!useHierarchy()) { if (_default != null) return _default; return _default = Logger.getLogger(DEFAULT_NAME); } if (_logger != null) return _logger; return _logger = Logger.getLogger(_name); //Note: we have to t save Logger.getLogger(). Otherwise, it will be GCed } /** * Returns the logging level. */ public final Level getLevel() { return getLogger().getLevel(); } /** * Sets the logging level. */ public final void setLevel(Level level) { getLogger().setLevel(level); } /** * Sets the logging level. * @since 5.0.7 */ public final void setLevel(String level) { Level l = getLevel(level); if (l == null) throw new IllegalArgumentException("Unknown level: "+level); setLevel(l); } /** Return the logging level of the specified string. * @return the level; null if no match at all */ public static final Level getLevel(String level) { if (level != null) { level = level.toUpperCase(); if (level.equals("DEBUG") || level.equals("FINE")) return Log.DEBUG; if (level.equals("ERROR") || level.equals("SEVERE")) return Log.ERROR; if (level.equals("FINER")) return Log.FINER; if (level.equals("INFO")) return Log.INFO; if (level.equals("WARNING")) return Log.WARNING; if (level.equals("OFF")) return Log.OFF; } return null; } /** * Tests whether the {@link #ERROR} level is loggable. */ public final boolean errorable() { return getLogger().isLoggable(ERROR); } /** * Tests whether the {@link #WARNING} level is loggable. */ public final boolean warningable() { return getLogger().isLoggable(WARNING); } /** * Tests whether the {@link #INFO} level is loggable. */ public final boolean infoable() { return getLogger().isLoggable(INFO); } /** * Tests whether the {@link #DEBUG} level is loggable. */ public final boolean debugable() { return getLogger().isLoggable(DEBUG); } /** * Tests whether the {@link #FINER} level is loggable. */ public final boolean finerable() { return getLogger().isLoggable(FINER); } /** * Logs a message and a throwable object at the giving level. * * <p>All log methods eventually invokes this method to log messages. * * @param t the throwable object; null to ignore */ public final void log(Level level, String msg, Throwable t) { final Logger logger = getLogger(); if (logger.isLoggable(level)) { //We have to unveil the stack frame to find the real source //Otherwise, Logger.log will report the wrong source //We cannot skip the first few frames because optimizer might //preserve all frames StackTraceElement[] stack = new Throwable().getStackTrace(); String cname = "", mname = ""; for (int j = 0; j < stack.length; ++j) { if (!stack[j].getClassName() .equals("org.zkoss.util.logging.Log")) { cname = stack[j].getClassName(); mname = stack[j].getMethodName() + ':' + stack[j].getLineNumber(); break; } } if (t != null) logger.logp(level, cname, mname, msg, t); else logger.logp(level, cname, mname, msg); } } /** * Logs any object and a throwable object at the giving level. * * @param obj the object whose toString method is called to get the message */ public final void log(Level level, Object obj, Throwable t) { log(level, Objects.toString(obj), t); } /** * Logs a message and a throwable object at the giving level * by giving a message code and multiple format arguments. * * @param t the throwable object; null to ignore */ public final void log(Level level, int code, Object[] fmtArgs, Throwable t) { final Locale l = Locales.setThreadLocal(null); try { log(level, Messages.get(code, fmtArgs), t); } finally { Locales.setThreadLocal(l); } } /** * Logs a message and a throwable object at the giving level * by giving a message code and ONE format argument. * * @param t the throwable object; null to ignore */ public final void log(Level level, int code, Object fmtArg, Throwable t) { final Locale l = Locales.setThreadLocal(null); try { log(level, Messages.get(code, fmtArg), t); } finally { Locales.setThreadLocal(l); } } /** * Logs a message and a throwable object at the giving level * by giving a message code and NO format argument. * * @param t the throwable object; null to ignore */ public final void log(Level level, int code, Throwable t) { final Locale l = Locales.setThreadLocal(null); try { log(level, Messages.get(code), t); } finally { Locales.setThreadLocal(l); } } private String format(String format,Object... args){ String d; try{ d = String.format(format, args); }catch(Exception x){ d = x.getMessage()+": "+format; } return d; } //-- ERROR --// /** Logs a debug message with a format and arguments. * The message is formatted by use of String.format. * @since 6.0.0 */ public void error(String format,Object... args){ error(format(format,args)); } /** * Logs an error message and a throwable object. * * @see #errorable */ public final void error(String msg, Throwable t) { log(ERROR, msg, t); } /** * Logs an error message. */ public final void error(String msg) { log(ERROR, msg, null); } /** * Logs an object, whose toString returns the error message, * and a throwable object. * * @param obj the object whose toString method is called to get the message */ public final void error(Object obj, Throwable t) { log(ERROR, obj, t); } /** * Logs an object, whose toString returns the error message. * * @param obj the object whose toString method is called to get the message */ public final void error(Object obj) { log(ERROR, obj, null); } /** * Logs an error throwable object. */ public final void error(Throwable t) { log(ERROR, "", t); } /** * Logs an error message and a throwable object by giving message code. */ public final void error(int code, Object[] fmtArgs, Throwable t) { log(ERROR, code, fmtArgs, t); } /** * Logs an error message and a throwable object by giving message code. */ public final void error(int code, Object fmtArg, Throwable t) { log(ERROR, code, fmtArg, t); } /** * Logs an error message and a throwable object by giving message code. */ public final void error(int code, Throwable t) { log(ERROR, code, t); } /** * Logs an error message by giving message code. */ public final void error(int code, Object[] fmtArgs) { log(ERROR, code, fmtArgs, null); } /** * Logs an error message by giving message code. */ public final void error(int code, Object fmtArg) { log(ERROR, code, fmtArg, null); } /** * Logs an error message by giving message code. */ public final void error(int code) { log(ERROR, code, null); } //-- WARNING --// /** Logs a warning message with a format and arguments. * The message is formatted by use of String.format. * @since 6.0.0 */ public void warning(String format,Object... args){ warning(format(format,args)); } /** * Logs a warning message and a throwable object. * * @see #warningable */ public final void warning(String msg, Throwable t) { log(WARNING, msg, t); } /** * Logs a warning message. */ public final void warning(String msg) { log(WARNING, msg, null); } /** * Logs an object, whose toString returns the warning message, * and a throwable object. * * @param obj the object whose toString method is called to get the message */ public final void warning(Object obj, Throwable t) { log(WARNING, obj, t); } /** * Logs an object, whose toString returns the warning message. * * @param obj the object whose toString method is called to get the message */ public final void warning(Object obj) { log(WARNING, obj, null); } /** * Logs a warning throwable object. */ public final void warning(Throwable t) { log(WARNING, "", t); } /** * Logs a warning message and a throwable object by giving message code. */ public final void warning(int code, Object[] fmtArgs, Throwable t) { log(WARNING, code, fmtArgs, t); } /** * Logs a warning message and a throwable object by giving message code. */ public final void warning(int code, Object fmtArg, Throwable t) { log(WARNING, code, fmtArg, t); } /** * Logs a warning message and a throwable object by giving message code. */ public final void warning(int code, Throwable t) { log(WARNING, code, t); } /** * Logs a warning message by giving message code. */ public final void warning(int code, Object[] fmtArgs) { log(WARNING, code, fmtArgs, null); } /** * Logs a warning message by giving message code. */ public final void warning(int code, Object fmtArg) { log(WARNING, code, fmtArg, null); } /** * Logs a warning message by giving message code. */ public final void warning(int code) { log(WARNING, code, null); } //-- INFO --// /** Logs an info message with a format and arguments. * The message is formatted by use of String.format. * @since 6.0.0 */ public void info(String format,Object... args){ info(format(format,args)); } /** * Logs an info message and a throwable object. * * @see #infoable */ public final void info(String msg, Throwable t) { log(INFO, msg, t); } /** * Logs an info message. */ public final void info(String msg) { log(INFO, msg, null); } /** * Logs an object, whose toString returns the info message, * and a throwable object. * * @param obj the object whose toString method is called to get the message */ public final void info(Object obj, Throwable t) { log(INFO, obj, t); } /** * Logs an object, whose toString returns the info message. * * @param obj the object whose toString method is called to get the message */ public final void info(Object obj) { log(INFO, obj, null); } /** * Logs an info throwable object. */ public final void info(Throwable t) { log(INFO, "", t); } /** * Logs an info message and a throwable object by giving message code. */ public final void info(int code, Object[] fmtArgs, Throwable t) { log(INFO, code, fmtArgs, t); } /** * Logs an info message and a throwable object by giving message code. */ public final void info(int code, Object fmtArg, Throwable t) { log(INFO, code, fmtArg, t); } /** * Logs an info message and a throwable object by giving message code. */ public final void info(int code, Throwable t) { log(INFO, code, t); } /** * Logs an info message by giving message code. */ public final void info(int code, Object[] fmtArgs) { log(INFO, code, fmtArgs, null); } /** * Logs an info message by giving message code. */ public final void info(int code, Object fmtArg) { log(INFO, code, fmtArg, null); } /** * Logs an info message by giving message code. */ public final void info(int code) { log(INFO, code, null); } //-- DEBUG --// /** Logs a debug message with a format and arguments. * The message is formatted by use of String.format. * @since 6.0.0 */ public void debug(String format,Object... args){ debug(format(format,args)); } /** * Logs a debug message and a throwable object. * * @since #debugable */ public final void debug(String msg, Throwable t) { log(DEBUG, msg, t); } /** * Logs a debug message. */ public final void debug(String msg) { log(DEBUG, msg, null); } /** * Logs an object, whose toString returns the debug message, * and a throwable object. * * @param obj the object whose toString method is called to get the message */ public final void debug(Object obj, Throwable t) { log(DEBUG, obj, t); } /** * Logs an object, whose toString returns the debug message. * * @param obj the object whose toString method is called to get the message */ public final void debug(Object obj) { log(DEBUG, obj, null); } /** * Logs a debug throwable object. */ public final void debug(Throwable t) { log(DEBUG, "", t); } /** * Logs a debug message and a throwable object by giving message code. */ public final void debug(int code, Object[] fmtArgs, Throwable t) { log(DEBUG, code, fmtArgs, t); } /** * Logs a debug message and a throwable object by giving message code. */ public final void debug(int code, Object fmtArg, Throwable t) { log(DEBUG, code, fmtArg, t); } /** * Logs a debug message and a throwable object by giving message code. */ public final void debug(int code, Throwable t) { log(DEBUG, code, t); } /** * Logs a debug message by giving message code. */ public final void debug(int code, Object[] fmtArgs) { log(DEBUG, code, fmtArgs, null); } /** * Logs a debug message by giving message code. */ public final void debug(int code, Object fmtArg) { log(DEBUG, code, fmtArg, null); } /** * Logs a debug message by giving message code. */ public final void debug(int code) { log(DEBUG, code, null); } //-- FINER --// /** Logs a finer message with a format and arguments. * The message is formatted by use of String.format. * @since 6.0.0 */ public void finer(String format,Object... args){ finer(format(format,args)); } /** * Logs a finer message and a throwable object. */ public final void finer(String msg, Throwable t) { log(FINER, msg, t); } /** * Logs a finer message. */ public final void finer(String msg) { log(FINER, msg, null); } /** * Logs an object, whose toString returns the finer message, * and a throwable object. * * @param obj the object whose toString method is called to get the message */ public final void finer(Object obj, Throwable t) { log(FINER, obj, t); } /** * Logs an object, whose toString returns the finer message. * * @param obj the object whose toString method is called to get the message */ public final void finer(Object obj) { log(FINER, obj, null); } /** * Logs a finer throwable object. */ public final void finer(Throwable t) { log(FINER, "", t); } /** * Logs a finer message and a throwable object by giving message code. */ public final void finer(int code, Object[] fmtArgs, Throwable t) { log(FINER, code, fmtArgs, t); } /** * Logs a finer message and a throwable object by giving message code. */ public final void finer(int code, Object fmtArg, Throwable t) { log(FINER, code, fmtArg, t); } /** * Logs a finer message and a throwable object by giving message code. */ public final void finer(int code, Throwable t) { log(FINER, code, t); } /** * Logs a finer message by giving message code. */ public final void finer(int code, Object[] fmtArgs) { log(FINER, code, fmtArgs, null); } /** * Logs a finer message by giving message code. */ public final void finer(int code, Object fmtArg) { log(FINER, code, fmtArg, null); } /** * Logs a finer message by giving message code. */ public final void finer(int code) { log(FINER, code, null); } /** Logs only the real cause of the specified exception. * It is useful because sometimes the stack trace is too big. */ public final void realCause(Throwable ex) { realCause(null, ex); } /** Logs only the real cause of the specified exception with an extra * message as an error message. */ public final void realCause(String message, Throwable ex) { realCause0(message, ex, true, 0); } /** Logs only the first few lines of the real cause as an error message. * <p>To control the number of lines to log, you can specify a library * property called org.zkoss.util.logging.realCauseBriefly.lines. * If not specified, 6 is assumed. If nonpositive is specified, the full stack * traces are logged. * <p>Notice that # of lines don't include packages starting with java, javax or sun. */ public final void realCauseBriefly(String message, Throwable ex) { realCause0(message, ex, true, Library.getIntProperty("org.zkoss.util.logging.realCauseBriefly", 6)); } /** Logs only the first few lines of the real cause as an error message. */ public final void realCauseBriefly(Throwable ex) { realCauseBriefly(null, ex); } private final void realCause0(String message, Throwable ex, boolean err, int maxcnt) { final StringBuffer sb = new StringBuffer(1024); if (message != null) sb.append(message).append('\n'); while (true) { Throwable cause = Exceptions.getCause(ex); if (cause == null) break; sb.append(">>").append(ex.getClass().getName()) .append(": ").append(ex.getMessage()).append('\n'); ex = cause; } String s = Exceptions.getExtraMessage(ex); if (s != null) sb.append(s).append('\n'); message = Exceptions.formatStackTrace(sb, ex, ">>", maxcnt).toString(); if (err) error(message); else warning(message); } /** Logs only the first few lines of the real cause as an warning message. * <p>To control the number of lines to log, you can specify a library * property called org.zkoss.util.logging.warningBriefly.lines. * If not specified, 3 is assumed. If nonpositive is specified, the full stack * traces are logged. * <p>Notice that # of lines don't include packages starting with java, javax or sun. */ public final void warningBriefly(String message, Throwable ex) { realCause0(message, ex, false, Library.getIntProperty("org.zkoss.util.logging.warningBriefly", 3)); } /** Lo only the first few lines of the real cause. */ public final void warningBriefly(Throwable ex) { warningBriefly(null, ex); } /** Logs an exception as an warning message about being eaten * (rather than thrown). */ public final void eat(String message, Throwable ex) { if (debugable()) { warningBriefly(message, ex); } else { warning( BAR+(message != null ? "\n"+message: "") +"\n"+"The exception:\n"+Exceptions.getMessage(ex)+"\n" +"If you want to see the stack trace, turn the DEBUG level on for "+_name+"\n"+BAR); } } private final String BAR = "------------"; /** Logs an exception as an warning message about being eaten * (rather than thrown). */ public final void eat(Throwable ex) { eat(null, ex); } /** Returns whether to use hierarchy. * <p>Default: {@link #isHierarchy}. */ /*package*/ boolean useHierarchy() { return _hierarchy && !hierarchyDisabled(); } public int hashCode() { return _name.hashCode(); } public boolean equals(Object o) { if (this == o) return true; return o instanceof Log && ((Log)o)._name.equals(_name); } public String toString() { return _name; } /** * Used internally to represent a hierarchical log. */ private static class HierLog extends Log { private HierLog(String name) { super(name); } /*package*/ boolean useHierarchy() { return !hierarchyDisabled(); } } }