/* Copyright (c) 2001-2010, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb.lib; import java.util.Enumeration; import java.util.logging.Level; import java.util.logging.ConsoleHandler; import java.util.logging.Logger; import java.util.logging.LogManager; import java.util.Map; import java.io.File; import java.io.FileInputStream; import java.util.Properties; import java.io.IOException; import java.util.HashMap; import java.lang.reflect.Method; /** * A logging framework wrapper that supports java.util.logging and log4j. * <P/> * Logger hierarchies are stored at the Class level. * Log4j will be used if the Log4j system (not necessarily config files) are * found in the runtime classpath. * Otherwise, java.util.logging will be used. * <P/> * This is pretty safe because for use cases where multiple hierarchies * are desired, classloader hierarchies will effectively isolate multiple * class-level Logger hierarchies. * <P/> * Sad as it is, the java.util.logging facility lacks the most basic * developer-side and configuration-side capabilities. * Besides having a non-scalable discovery system, the designers didn't * comprehend the need for a level between WARNING and SEVERE! * Since we don't want to require log4j in Classpath, we have to live * with these constraints. * <P/> * As with all the popular logging frameworks, if you want to capture a * stack trace, you must use the two-parameters logging methods. * I.e., you must also pass a String, or only toString() from your * throwable will be captured. * <P/> * Usage example:<CODE><PRE> * private static FrameworkLogger logger = * FrameworkLogger.getLog(SqlTool.class); * ... * logger.finer("Doing something log-worthy"); * </PRE> </CODE> * * @author Blaine Simpson (blaine dot simpson at admc dot com) * @version 1.9.0 * @since 1.9.0 */ public class FrameworkLogger { static private Map loggerInstances = new HashMap(); static private Map jdkToLog4jLevels = new HashMap(); static private Method log4jGetLogger; static private Method log4jLogMethod; private Object log4jLogger; private Logger jdkLogger; static private boolean haveLoadedOurDefault; static private ConsoleHandler consoleHandler = new ConsoleHandler(); // No need for more than one static, since we have only one console static { reconfigure(); } static void reconfigure() { Class log4jLoggerClass = null; loggerInstances.clear(); try { log4jLoggerClass = Class.forName("org.apache.log4j.Logger"); } catch (Exception e) { log4jLoggerClass = null; } if (log4jLoggerClass == null) try { log4jGetLogger = null; log4jLogMethod = null; LogManager lm = LogManager.getLogManager(); if (haveLoadedOurDefault || isDefaultJdkConfig()) { haveLoadedOurDefault = true; consoleHandler.setFormatter( new BasicTextJdkLogFormatter(false)); consoleHandler.setLevel(Level.INFO); lm.readConfiguration( FrameworkLogger.class.getResourceAsStream( "/org/hsqldb/resources/jdklogging-default.properties")); Logger cmdlineLogger = Logger.getLogger("org.hsqldb.cmdline"); cmdlineLogger.addHandler(consoleHandler); cmdlineLogger.setUseParentHandlers(false); } else { // Do not intervene. Use JDK logging exactly as configured by // user. lm.readConfiguration(); // The only bad thing about doing this is that if the app has // programmatically changed the logging config after starting // the program but before using FrameworkLogger, we will // clobber those customizations. } } catch (Exception e) { throw new RuntimeException( "<clinit> failure initializing JDK logging system", e); } else try { Method log4jToLevel = Class.forName( "org.apache.log4j.Level").getMethod( "toLevel", new Class[]{ String.class }); jdkToLog4jLevels.put(Level.ALL, log4jToLevel.invoke(null, new Object[]{ "ALL" })); jdkToLog4jLevels.put(Level.FINER, log4jToLevel.invoke(null, new Object[]{ "DEBUG" })); jdkToLog4jLevels.put(Level.WARNING, log4jToLevel.invoke(null, new Object[]{ "ERROR" })); jdkToLog4jLevels.put(Level.SEVERE, log4jToLevel.invoke(null, new Object[]{ "FATAL" })); jdkToLog4jLevels.put(Level.INFO, log4jToLevel.invoke(null, new Object[]{ "INFO" })); jdkToLog4jLevels.put(Level.OFF, log4jToLevel.invoke(null, new Object[]{ "OFF" })); jdkToLog4jLevels.put(Level.FINEST, log4jToLevel.invoke(null, new Object[]{ "TRACE" })); jdkToLog4jLevels.put(Level.WARNING, log4jToLevel.invoke(null, new Object[]{ "WARN" })); log4jLogMethod = log4jLoggerClass.getMethod("log", new Class[] { String.class, Class.forName("org.apache.log4j.Priority"), Object.class, Throwable.class }); log4jGetLogger = log4jLoggerClass.getMethod("getLogger", new Class[]{ String.class }); // This last object is what we toggle on to generate either // Log4j or Jdk Logger objects (to wrap). } catch (Exception e) { throw new RuntimeException( "<clinit> failure instantiating present Log4j system", e); } } /** * User may not use the constructor. */ private FrameworkLogger(String s) { if (log4jGetLogger == null) { jdkLogger = Logger.getLogger(s); } else { try { log4jLogger = log4jGetLogger.invoke(null, new Object[]{ s }); } catch (Exception e) { throw new RuntimeException( "Failed to instantiate Log4j Logger", e); } } loggerInstances.put(s, this); } /** * User's entry-point into this logging system. * <P/> * You normally want to work with static (class-level) pointers to * logger instances, for performance efficiency. * See the class-level JavaDoc for a usage example. * * @see FrameworkLogger */ public static FrameworkLogger getLog(Class c) { return getLog(c.getName()); } /** * This method just defers to the getLog(Class) method unless default * (no local configuration) JDK logging is being used; * In that case, this method assures that the returned logger has an * associated FileHander using the supplied String identifier. */ public static FrameworkLogger getLog(Class c, String contextId) { return (contextId == null) ? getLog(c) : getLog(contextId + '.' + c.getName()); } /** * Alternative entry-point into this logging sytem, for cases where * your want to share a single logger instance among multiple classes, * or you want to use multiple logger instances from a single class. * * @see #getLog(Class) */ public static FrameworkLogger getLog(String s) { if (loggerInstances.containsKey(s)) { return (FrameworkLogger) loggerInstances.get(s); } return new FrameworkLogger(s); } /** * Just like FrameworkLogger.log(Level, String), * but also logs a stack trace. * * @param level java.util.logging.Level level to filter and log at * @param message Message to be logged * @param t Throwable whose stack trace will be logged. * @see #log(Level, String) * @see Logger#log(Level, String) * @see Level */ public void log(Level level, String message, Throwable t) { privlog(level, message, t, 2, FrameworkLogger.class); } /** * The "priv" prefix is historical. * This is for special usage when you need to modify the reported call * stack. * If you don't know that you want to do this, then you should not use * this method. */ public void privlog(Level level, String message, Throwable t, int revertMethods, Class skipClass) { if (log4jLogger == null) { StackTraceElement elements[] = new Throwable().getStackTrace(); String c = elements[revertMethods].getClassName(); String m = elements[revertMethods].getMethodName(); if (t == null) { jdkLogger.logp(level, c, m, message); } else { jdkLogger.logp(level, c, m, message, t); } } else { try { log4jLogMethod.invoke(log4jLogger, new Object[] { skipClass.getName(), jdkToLog4jLevels.get(level), message, t }); } catch (Exception e) { throw new RuntimeException( "Logging failed when attempting to log: " + message, e); } } } public void enduserlog(Level level, String message) { /* This method is SqlTool-specific, which is where this class began at. * Need to move this back there, but it needs access to the logging * structures private to this class. Thinking... */ if (log4jLogger == null) { String c = FrameworkLogger.class.getName(); String m = "\\l"; jdkLogger.logp(level, c, m, message); } else { try { log4jLogMethod.invoke(log4jLogger, new Object[] { FrameworkLogger.class.getName(), jdkToLog4jLevels.get(level), message, null }); // Test where SqlFile correct here. } catch (Exception e) { throw new RuntimeException( "Logging failed when attempting to log: " + message, e); } } } // Wrappers /** * @param level java.util.logging.Level level to filter and log at * @param message Message to be logged * @see Logger#log(Level, String) * @see Level */ public void log(Level level, String message) { privlog(level, message, null, 2, FrameworkLogger.class); } /** * @param message Message to be logged * @see Logger#finer(String) */ public void finer(String message) { privlog(Level.FINER, message, null, 2, FrameworkLogger.class); } /** * @param message Message to be logged * @see Logger#warning(String) */ public void warning(String message) { privlog(Level.WARNING, message, null, 2, FrameworkLogger.class); } /** * @param message Message to be logged * @see Logger#severe(String) */ public void severe(String message) { privlog(Level.SEVERE, message, null, 2, FrameworkLogger.class); } /** * @param message Message to be logged * @see Logger#info(String) */ public void info(String message) { privlog(Level.INFO, message, null, 2, FrameworkLogger.class); } /** * @param message Message to be logged * @see Logger#finest(String) */ public void finest(String message) { privlog(Level.FINEST, message, null, 2, FrameworkLogger.class); } /** * This is just a wrapper for FrameworkLogger.warning(), because * java.util.logging lacks a method for this critical purpose. * * @param message Message to be logged * @see #warning(String) */ public void error(String message) { privlog(Level.WARNING, message, null, 2, FrameworkLogger.class); } /** * Just like FrameworkLogger.finer(String), but also logs a stack trace. * * @param t Throwable whose stack trace will be logged. * @see #finer(String) */ public void finer(String message, Throwable t) { privlog(Level.FINER, message, t, 2, FrameworkLogger.class); } /** * Just like FrameworkLogger.warning(String), but also logs a stack trace. * * @param t Throwable whose stack trace will be logged. * @see #warning(String) */ public void warning(String message, Throwable t) { privlog(Level.WARNING, message, t, 2, FrameworkLogger.class); } /** * Just like FrameworkLogger.severe(String), but also logs a stack trace. * * @param t Throwable whose stack trace will be logged. * @see #severe(String) */ public void severe(String message, Throwable t) { privlog(Level.SEVERE, message, t, 2, FrameworkLogger.class); } /** * Just like FrameworkLogger.info(String), but also logs a stack trace. * * @param t Throwable whose stack trace will be logged. * @see #info(String) */ public void info(String message, Throwable t) { privlog(Level.INFO, message, t, 2, FrameworkLogger.class); } /** * Just like FrameworkLogger.finest(String), but also logs a stack trace. * * @param t Throwable whose stack trace will be logged. * @see #finest(String) */ public void finest(String message, Throwable t) { privlog(Level.FINEST, message, t, 2, FrameworkLogger.class); } /** * Just like FrameworkLogger.error(String), but also logs a stack trace. * * @param t Throwable whose stack trace will be logged. * @see #error(String) */ public void error(String message, Throwable t) { privlog(Level.WARNING, message, t, 2, FrameworkLogger.class); } /** * Whether this JVM is configured with java.util.logging defaults. * * If the JRE-provided config file is not in the expected place, then * we return false. */ static public boolean isDefaultJdkConfig() { File globalCfgFile = new File(System.getProperty("java.home"), "lib/logging.properties"); if (!globalCfgFile.isFile()) { return false; } FileInputStream fis = null; LogManager lm = LogManager.getLogManager(); try { fis = new FileInputStream(globalCfgFile); Properties defaultProps = new Properties(); defaultProps.load(fis); Enumeration names = defaultProps.propertyNames(); int i = 0; String name; String liveVal; while (names.hasMoreElements()) { i++; name = (String) names.nextElement(); liveVal = lm.getProperty(name); if (liveVal == null) { return false; } if (!lm.getProperty(name).equals(liveVal)) { return false; } } return true; } catch (IOException ioe) { return false; } finally { if (fis != null) try { fis.close(); } catch (IOException ioe) { // Intentional no-op } } } }