/* * Logging.java * Copyright 2003 (C) Jonas Karlsson <jujutsunerd@sf.net> * * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Created on April 12, 2003, 3:20 AM */ package pcgen.util; import pcgen.rules.context.LoadContext; import pcgen.system.LanguageBundle; import java.awt.Toolkit; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.net.URI; import java.text.NumberFormat; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import org.apache.commons.lang3.SystemUtils; import pcgen.core.SettingsHandler; /** * This contains logging functions. It is a proxy for the * Java logging API. * * @author Jonas Karlsson <jujutsunerd@sf.net> */ public class Logging { private static boolean debugMode = false; private static final Toolkit s_TOOLKIT = Toolkit.getDefaultToolkit(); /** Log level for error output. */ public static final Level ERROR = Level.SEVERE; /** Log level for LST error output. */ public static final Level LST_ERROR = PCGenLogLevel.LST_ERROR; /** Logging level for code warnings. */ public static final Level WARNING = Level.WARNING; /** Logging level for LST warnings such as deprecated syntax use. */ public static final Level LST_WARNING = PCGenLogLevel.LST_WARNING; /** Logging level for code info. */ public static final Level INFO = Level.INFO; /** Logging level for LST information such as references to missing items in PRE or CHOOSE tags. */ public static final Level LST_INFO = PCGenLogLevel.LST_INFO; /** Log level for application debug output. */ public static final Level DEBUG = Level.FINER; private static Logger pcgenLogger; private static Logger pluginLogger; /** * Do any required initialization of the Logger. */ static { // Set a default configuration file if none was specified. Properties p = System.getProperties(); File propsFile = new File(SystemUtils.USER_DIR + File.separator + "logging.properties"); if (!propsFile.exists()) { propsFile = new File("logging.properties"); } if (propsFile.exists() && null == p.get("java.util.logging.config.file")) { p.put("java.util.logging.config.file", propsFile.getAbsolutePath()); } //System.out.println("Using log settings from " + propsFile.getAbsolutePath()); // Get Java Logging to read in the config. try { LogManager.getLogManager().readConfiguration(); } catch (SecurityException | IOException e) { System.err .println("Failed to read logging configuration. Error was:"); e.printStackTrace(); } } /** * Set debugging state: {@code true} is on. * * @param argDebugMode boolean debugging state */ public static void setDebugMode(final boolean argDebugMode) { retainRootLoggers(); debugMode = argDebugMode; if (debugMode) { Logger.getLogger("pcgen").setLevel(DEBUG); Logger.getLogger("plugin").setLevel(DEBUG); } else { Logger.getLogger("pcgen").setLevel(LST_WARNING); Logger.getLogger("plugin").setLevel(LST_WARNING); } } /** * Ensure that our root loggers (pcgen and plugin) do not get garbage * collected, otherwise we lose the logging level! */ private static void retainRootLoggers() { pcgenLogger = Logger.getLogger("pcgen"); pluginLogger = Logger.getLogger("plugin"); } /** * Is someone debugging PCGen? * * @return boolean debugging state */ public static boolean isDebugMode() { return debugMode; } /** * Check if the level of logs would be output for the caller. This can * be used to prevent building logging output if it will not be used. * @param level The logging level to be checked. * @return true if the level would be output, false if not. */ public static boolean isLoggable(Level level) { Logger l = getLogger(); return l != null && l.isLoggable(level); } /** * Print information message if PCGen is debugging. * * @param s String information message */ public static void debugPrint(final String s) { Logger l = getLogger(); if (l != null && l.isLoggable(DEBUG)) { l.log(DEBUG, s); } } /** * Print information message if PCGen is debugging. * * @param param1 String information message (usually variable) * @param param2 Object information message (usually value) */ public static void debugPrint(final String param1, Object param2) { Logger l = getLogger(); if (l.isLoggable(DEBUG)) { l.log(DEBUG, param1 + param2); } } /** * Print localised information message if PCGen is debugging. * * @param message String information message (usually variable) * @param params Object information message (usually value) */ public static void debugPrintLocalised(final String message, Object... params) { Logger l = getLogger(); if (l.isLoggable(DEBUG)) { String msg = LanguageBundle.getFormattedString(message, params); l.log(DEBUG, msg); } } /** * Print the message. Currently quietly discards the Throwable. * * @param s String error message * @param thr Throwable stack frame */ public static void debugPrint(final String s, final Throwable thr) { debugPrint(s); Logger l = getLogger(); if (l != null && l.isLoggable(DEBUG)) { thr.printStackTrace(System.err); } } /** * Print a localized error message from the passed in key. If the * application is in Debug mode will also issue a beep. * * @param aKey A key for the localized string in the language bundle */ public static void errorPrintLocalised(final String aKey) { if (debugMode) { s_TOOLKIT.beep(); } final String msg = LanguageBundle.getString(aKey); System.err.println(msg); } /** * Print a localized error message including parameter substitution. The * method will issue a beep if the application is running in Debug mode. * <p> * This method accepts a variable number of parameters and will replace * {@code {argno}} in the string with each passed paracter in turn. * * @param aKey * A key for the localized string in the language bundle * @param varargs * Variable number of parameters to substitute into the string */ public static void errorPrintLocalised(final String aKey, Object... varargs) { if (debugMode) { s_TOOLKIT.beep(); } final String msg = LanguageBundle.getFormattedString(aKey, varargs); Logger l = getLogger(); if (l.isLoggable(ERROR)) { l.log(ERROR, msg); } } /** * Beep and print error message if PCGen is debugging. * * @param s String error message */ public static void deprecationPrint(final String s) { deprecationPrint(s, null); } /** * Beep and print error message if PCGen is debugging. * * @param s String error message * @param context the LoadContext containing the deprecated resource */ public static void deprecationPrint(final String s, final LoadContext context) { if (debugMode) { s_TOOLKIT.beep(); } Logger l = getLogger(); if (l.isLoggable(LST_WARNING) && SettingsHandler.outputDeprecationMessages()) { if (context != null && context.getSourceURI() != null) { l.log(LST_WARNING, s + " (Source: " + context.getSourceURI() + " )"); } else { l.log(LST_WARNING, s); } } } /** * Report where an issue was encountered. * * @param context the LoadContext containing the resource */ public static void reportSource(final Level lvl, final LoadContext context) { Logger l = getLogger(); if (l.isLoggable(lvl)) { if (context != null && context.getSourceURI() != null) { l.log(lvl, " (Source: " + context.getSourceURI() + " )"); } else { l.log(lvl, " (Source unknown)"); } } } /** * Report where an issue was encountered. * * @param sourceUri the source containing the resource */ public static void reportSource(final Level lvl, final URI sourceUri) { Logger l = getLogger(); if (l.isLoggable(lvl)) { if (sourceUri != null) { l.log(lvl, " (Source: " + sourceUri + ")"); } else { l.log(lvl, " (Source unknown)"); } } } /** * Beep and print error message if PCGen is debugging. * * @param s String error message */ public static void errorPrint(final String s) { if (debugMode) { s_TOOLKIT.beep(); } Logger l = getLogger(); if (l.isLoggable(ERROR)) { l.log(ERROR, s); } } /** * Beep and print error message if PCGen is debugging. * * @param s String error message * @param params Varargs list of parameters for substitution into the * error message. */ public static void errorPrint(final String s, final Object... params) { if (debugMode) { s_TOOLKIT.beep(); } Logger l = getLogger(); if (l.isLoggable(ERROR)) { l.log(ERROR, s, params); } } /** * Beep and print error message if PCGen is debugging. * * @param s String error message * @param context the LoadContext containing the deprecated resource */ public static void errorPrint(final String s, final LoadContext context) { if (debugMode) { s_TOOLKIT.beep(); } Logger l = getLogger(); if (l.isLoggable(ERROR)) { if (context != null && context.getSourceURI() != null) { l.log(ERROR, s + " (Source: " + context.getSourceURI() + " )"); } else { l.log(ERROR, s); } } } /** * Beep and print error message if PCGen is debugging. * * @param s String error message * @param sourceURI the source containing the resource in error */ public static void errorPrint(final String s, final URI sourceURI) { if (debugMode) { s_TOOLKIT.beep(); } Logger l = getLogger(); if (l.isLoggable(ERROR)) { if (sourceURI != null) { l.log(ERROR, s + " (Source: " + sourceURI + " )"); } else { l.log(ERROR, s); } } } /** * Print error message with a stack trace if PCGen is * debugging. * * @param s String error message * @param thr Throwable stack frame */ public static void errorPrint(final String s, final Throwable thr) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); thr.printStackTrace(ps); errorPrint(s + "\n" + baos.toString()); } /** * Log a message, if logging is enabled at the * supplied level of detail. * * @param lvl The detail level of the message * @param msg String message */ public static void log(final Level lvl, final String msg) { Logger l = getLogger(); if (l.isLoggable(lvl)) { l.log(lvl, msg); } } /** * Log a message with a stack trace, if logging is enabled at the * supplied level of detail. * * @param lvl The detail level of the message * @param msg String message * @param thr Throwable stack frame */ public static void log(final Level lvl, final String msg, final Throwable thr) { Logger l = getLogger(); if (l.isLoggable(lvl)) { l.log(lvl, msg, thr); } } /** * Log a message with a stack trace, if logging is enabled at the * supplied level of detail. * This is mainly for use with the pcgen.rules.persistence.token.ParseResult class. * * @param lvl The detail level of the message * @param msg String message * @param stackTrace The stack trace */ public static void log(Level lvl, String msg, StackTraceElement[] stackTrace) { Logger l = getLogger(); if (l.isLoggable(lvl)) { l.log(lvl, msg, stackTrace); } } /** * Print error message with a stack trace if PCGen is * debugging. * * @param s String error message * @param thr Throwable stack frame */ public static void errorPrintLocalised(final String s, final Throwable thr) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); thr.printStackTrace(ps); errorPrint(LanguageBundle.getString(s) + "\n" + baos.toString()); } /** * Report to the console on the current memory sitution. */ public static void memoryReport() { System.out.println(memoryReportStr()); } /** * Generate the memory report string * @return the memory report string */ public static String memoryReportStr() { Runtime rt = Runtime.getRuntime(); NumberFormat numFmt = NumberFormat.getNumberInstance(); StringBuilder sb = new StringBuilder("Memory: "); sb.append(numFmt.format(rt.totalMemory() / 1024.0)); sb.append("Kb total, "); sb.append(numFmt.format(rt.freeMemory() / 1024.0)); sb.append("Kb free, "); sb.append(numFmt.format(rt.maxMemory() / 1024.0)); sb.append("Kb max."); return sb.toString(); } /** * Retrieve a Logger object with the specified name. Generally * this name should be either the fully qualified class name, * or the package name. * * @param name The name of the logger * @return An instance of Logger that deals with the specified name. */ private static java.util.logging.Logger getLogger() { StackTraceElement[] stack = new Throwable().getStackTrace(); StackTraceElement caller = null; for (int i = 1; i < stack.length; i++) //1 to skip this method { if (!"pcgen.util.Logging".equals(stack[i].getClassName())) { caller = stack[i]; break; } } String name = (caller == null/*just in case*/) ? "" : caller.getClassName(); Logger l = null; final int maxRetries = 15; int retries = 0; while (l == null && retries < maxRetries) { l = java.util.logging.Logger.getLogger(name); retries++; } if (l == null) { System.err.println("Unable to get logger for " + name + " after " + retries + " atempts."); } return l; } /** * List the current stack of all threads to STDOUT. */ public static void reportAllThreads() { Map<Thread, StackTraceElement[]> allThreads = Thread.getAllStackTraces(); StringBuilder b = new StringBuilder(); for (Thread t : allThreads.keySet()) { b.append("Thread: "); b.append(t.getName()); b.append(", stacktrace:\n"); StackTraceElement[] traces = allThreads.get(t); for (StackTraceElement element : traces) { b.append(" "); b.append(element.toString()); b.append("\n"); } } System.out.println("==== Thread listing ===="); System.out.println(b); System.out.println("===== end listing ====="); } /** * Register a new log handler. * @param handler The handler to be registered. */ public static void registerHandler(Handler handler) { Logger.getLogger("").addHandler(handler); } /** * Removes a log handler. * @param handler The handler to be removed. */ public static void removeHandler(Handler handler) { Logger.getLogger("").removeHandler(handler); } /** * Return a list of the supported logging levels in * descending order of rank. * @return List of logging levels. */ public static List<Level> getLoggingLevels() { List<Level> levels = new ArrayList<>(); levels.add(ERROR); levels.add(LST_ERROR); levels.add(WARNING); levels.add(LST_WARNING); levels.add(INFO); levels.add(LST_INFO); levels.add(DEBUG); return levels; } /** * @return The current logging level for the main program. */ public static Level getCurrentLoggingLevel() { return Logger.getLogger("pcgen").getLevel(); } /** * Set the current logging level for the main program. * @param level The new level */ public static void setCurrentLoggingLevel(Level level) { retainRootLoggers(); debugMode = (level == Logging.DEBUG); Logger.getLogger("pcgen").setLevel(level); Logger.getLogger("plugin").setLevel(level); } private static LinkedList<QueuedMessage> queuedMessages = new LinkedList<>(); public static void addParseMessage(Level lvl, String msg) { queuedMessages.add(new QueuedMessage(lvl, msg)); } /* * Temporary method for use with ParseResult conversion. * See pcgen.rules.persistence.token.ParseResult for use. */ public static void addParseMessage(Level lvl, String msg, StackTraceElement[] stack) { queuedMessages.add(new QueuedMessage(lvl, msg, stack)); } private static int queuedMessageMark = -1; public static void markParseMessages() { queuedMessageMark = queuedMessages.size(); } public static void rewindParseMessages() { while (queuedMessageMark > -1 && queuedMessages.size() > queuedMessageMark) { queuedMessages.removeLast(); } } public static void replayParsedMessages() { Logger l = getLogger(); for (QueuedMessage msg : queuedMessages) { if (l.isLoggable(msg.level)) { l.log(msg.level, msg.message, msg.stackTrace); } } queuedMessageMark = -1; } public static void clearParseMessages() { queuedMessageMark = -1; queuedMessages.clear(); } private static class QueuedMessage { public final Level level; public final String message; public final StackTraceElement[] stackTrace; public QueuedMessage(Level lvl, String msg) { level = lvl; message = msg; stackTrace = Thread.currentThread().getStackTrace(); } /* * Temporary constructor for use with ParseResult conversion. * See addParseMessage above. */ public QueuedMessage(Level lvl, String msg, StackTraceElement[] stack) { level = lvl; message = msg; stackTrace = stack; } } }