/* * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.internal.runtime.logging; import java.io.PrintWriter; import java.security.AccessControlContext; import java.security.AccessController; import java.security.Permissions; import java.security.PrivilegedAction; import java.security.ProtectionDomain; import java.util.logging.ConsoleHandler; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.LoggingPermission; import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.events.RuntimeEvent; /** * Wrapper class for Logging system. This is how you are supposed to register a logger and use it */ public final class DebugLogger { /** Disabled logger used for all loggers that need an instance, but shouldn't output anything */ public static final DebugLogger DISABLED_LOGGER = new DebugLogger("disabled", Level.OFF, false); private final Logger logger; private final boolean isEnabled; private int indent; private static final int INDENT_SPACE = 4; /** A quiet logger only logs {@link RuntimeEvent}s and does't output any text, regardless of level */ private final boolean isQuiet; /** * Constructor * * A logger can be paired with a property, e.g. {@code --log:codegen:info} is equivalent to {@code -Dnashorn.codegen.log} * * @param loggerName name of logger - this is the unique key with which it can be identified * @param loggerLevel level of the logger * @param isQuiet is this a quiet logger, i.e. enabled for things like e.g. RuntimeEvent:s, but quiet otherwise */ public DebugLogger(final String loggerName, final Level loggerLevel, final boolean isQuiet) { this.logger = instantiateLogger(loggerName, loggerLevel); this.isQuiet = isQuiet; assert logger != null; this.isEnabled = getLevel() != Level.OFF; } private static Logger instantiateLogger(final String name, final Level level) { final Logger logger = java.util.logging.Logger.getLogger(name); AccessController.doPrivileged(new PrivilegedAction<Void>() { @Override public Void run() { for (final Handler h : logger.getHandlers()) { logger.removeHandler(h); } logger.setLevel(level); logger.setUseParentHandlers(false); final Handler c = new ConsoleHandler(); c.setFormatter(new Formatter() { @Override public String format(final LogRecord record) { final StringBuilder sb = new StringBuilder(); sb.append('[') .append(record.getLoggerName()) .append("] ") .append(record.getMessage()) .append('\n'); return sb.toString(); } }); logger.addHandler(c); c.setLevel(level); return null; } }, createLoggerControlAccCtxt()); return logger; } /** * Do not currently support chaining this with parent logger. Logger level null * means disabled * @return level */ public Level getLevel() { return logger.getLevel() == null ? Level.OFF : logger.getLevel(); } /** * Get the output writer for the logger. Loggers always default to * stderr for output as they are used mainly to output debug info * * Can be inherited so this should not be static. * * @return print writer for log output. */ @SuppressWarnings("static-method") public PrintWriter getOutputStream() { return Context.getCurrentErr(); } /** * Add quotes around a string * @param str string * @return quoted string */ public static String quote(final String str) { if (str.isEmpty()) { return "''"; } char startQuote = '\0'; char endQuote = '\0'; char quote = '\0'; if (str.startsWith("\\") || str.startsWith("\"")) { startQuote = str.charAt(0); } if (str.endsWith("\\") || str.endsWith("\"")) { endQuote = str.charAt(str.length() - 1); } if (startQuote == '\0' || endQuote == '\0') { quote = startQuote == '\0' ? endQuote : startQuote; } if (quote == '\0') { quote = '\''; } return (startQuote == '\0' ? quote : startQuote) + str + (endQuote == '\0' ? quote : endQuote); } /** * Check if the logger is enabled * @return true if enabled */ public boolean isEnabled() { return isEnabled; } /** * Check if the logger is enabled * @param logger logger to check, null will return false * @return true if enabled */ public static boolean isEnabled(final DebugLogger logger) { return logger != null && logger.isEnabled(); } /** * If you want to change the indent level of your logger, call indent with a new position. * Positions start at 0 and are increased by one for a new "tab" * * @param pos indent position */ public void indent(final int pos) { if (isEnabled) { indent += pos * INDENT_SPACE; } } /** * Add an indent position */ public void indent() { indent += INDENT_SPACE; } /** * Unindent a position */ public void unindent() { indent -= INDENT_SPACE; if (indent < 0) { indent = 0; } } private static void logEvent(final RuntimeEvent<?> event) { if (event != null) { final Global global = Context.getGlobal(); if (global.has("Debug")) { final ScriptObject debug = (ScriptObject)global.get("Debug"); final ScriptFunction addRuntimeEvent = (ScriptFunction)debug.get("addRuntimeEvent"); ScriptRuntime.apply(addRuntimeEvent, debug, event); } } } /** * Check if the event of given level will be logged. * @see java.util.logging.Level * * @param level logging level * @return true if event of given level will be logged. */ public boolean isLoggable(final Level level) { return logger.isLoggable(level); } /** * Shorthand for outputting a log string as log level {@link java.util.logging.Level#FINEST} on this logger * @param str the string to log */ public void finest(final String str) { log(Level.FINEST, str); } /** * Shorthand for outputting a log string as log level {@link java.util.logging.Level#FINEST} on this logger * @param event optional runtime event to log * @param str the string to log */ public void finest(final RuntimeEvent<?> event, final String str) { finest(str); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINEST} on this logger * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void finest(final Object... objs) { log(Level.FINEST, objs); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINEST} on this logger * @param event optional runtime event to log * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void finest(final RuntimeEvent<?> event, final Object... objs) { finest(objs); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINER} on this logger * @param str the string to log */ public void finer(final String str) { log(Level.FINER, str); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINER} on this logger * @param event optional runtime event to log * @param str the string to log */ public void finer(final RuntimeEvent<?> event, final String str) { finer(str); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINER} on this logger * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void finer(final Object... objs) { log(Level.FINER, objs); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINER} on this logger * @param event optional runtime event to log * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void finer(final RuntimeEvent<?> event, final Object... objs) { finer(objs); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINE} on this logger * @param str the string to log */ public void fine(final String str) { log(Level.FINE, str); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINE} on this logger * @param event optional runtime event to log * @param str the string to log */ public void fine(final RuntimeEvent<?> event, final String str) { fine(str); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINE} on this logger * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void fine(final Object... objs) { log(Level.FINE, objs); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINE} on this logger * @param event optional runtime event to log * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void fine(final RuntimeEvent<?> event, final Object... objs) { fine(objs); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#CONFIG} on this logger * @param str the string to log */ public void config(final String str) { log(Level.CONFIG, str); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#CONFIG} on this logger * @param event optional runtime event to log * @param str the string to log */ public void config(final RuntimeEvent<?> event, final String str) { config(str); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#CONFIG} on this logger * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void config(final Object... objs) { log(Level.CONFIG, objs); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#CONFIG} on this logger * @param event optional runtime event to log * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void config(final RuntimeEvent<?> event, final Object... objs) { config(objs); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#INFO} on this logger * @param str the string to log */ public void info(final String str) { log(Level.INFO, str); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#INFO} on this logger * @param event optional runtime event to log * @param str the string to log */ public void info(final RuntimeEvent<?> event, final String str) { info(str); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINE} on this logger * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void info(final Object... objs) { log(Level.INFO, objs); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINE} on this logger * @param event optional runtime event to log * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void info(final RuntimeEvent<?> event, final Object... objs) { info(objs); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#WARNING} on this logger * @param str the string to log */ public void warning(final String str) { log(Level.WARNING, str); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#WARNING} on this logger * @param event optional runtime event to log * @param str the string to log */ public void warning(final RuntimeEvent<?> event, final String str) { warning(str); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINE} on this logger * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void warning(final Object... objs) { log(Level.WARNING, objs); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINE} on this logger * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead * @param event optional runtime event to log */ public void warning(final RuntimeEvent<?> event, final Object... objs) { warning(objs); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#SEVERE} on this logger * @param str the string to log */ public void severe(final String str) { log(Level.SEVERE, str); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#SEVERE} on this logger * @param str the string to log * @param event optional runtime event to log */ public void severe(final RuntimeEvent<?> event, final String str) { severe(str); logEvent(event); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#SEVERE} on this logger * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void severe(final Object... objs) { log(Level.SEVERE, objs); } /** * Shorthand for outputting a log string as log level * {@link java.util.logging.Level#FINE} on this logger * @param event optional runtime event to log * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead */ public void severe(final RuntimeEvent<?> event, final Object... objs) { severe(objs); logEvent(event); } /** * Output log line on this logger at a given level of verbosity * @see java.util.logging.Level * * @param level minimum log level required for logging to take place * @param str string to log */ public void log(final Level level, final String str) { if (isEnabled && !isQuiet && logger.isLoggable(level)) { final StringBuilder sb = new StringBuilder(); for (int i = 0 ; i < indent ; i++) { sb.append(' '); } sb.append(str); logger.log(level, sb.toString()); } } /** * Output log line on this logger at a given level of verbosity * @see java.util.logging.Level * * @param level minimum log level required for logging to take place * @param objs objects for which to invoke toString and concatenate to log */ public void log(final Level level, final Object... objs) { if (isEnabled && !isQuiet && logger.isLoggable(level)) { final StringBuilder sb = new StringBuilder(); for (final Object obj : objs) { sb.append(obj); } log(level, sb.toString()); } } /** * Access control context for logger level and instantiation permissions * @return access control context */ private static AccessControlContext createLoggerControlAccCtxt() { final Permissions perms = new Permissions(); perms.add(new LoggingPermission("control", null)); return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) }); } }