/*
 * Copyright (c) 2015, 2016, 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.internal.logger;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.StackWalker.StackFrame;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.function.Function;
import java.lang.System.Logger;
import java.util.function.Predicate;
import java.util.function.Supplier;
import sun.security.action.GetPropertyAction;
import sun.util.logging.PlatformLogger;
import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration;

A simple console logger to emulate the behavior of JUL loggers when in the default configuration. SimpleConsoleLoggers are also used when JUL is not present and no DefaultLoggerFinder is installed.
/** * A simple console logger to emulate the behavior of JUL loggers when * in the default configuration. SimpleConsoleLoggers are also used when * JUL is not present and no DefaultLoggerFinder is installed. */
public class SimpleConsoleLogger extends LoggerConfiguration implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge { static final Level DEFAULT_LEVEL = getDefaultLevel(); static final PlatformLogger.Level DEFAULT_PLATFORM_LEVEL = PlatformLogger.toPlatformLevel(DEFAULT_LEVEL); static Level getDefaultLevel() { String levelName = GetPropertyAction .privilegedGetProperty("jdk.system.logger.level", "INFO"); try { return Level.valueOf(levelName); } catch (IllegalArgumentException iae) { return Level.INFO; } } final String name; volatile PlatformLogger.Level level; final boolean usePlatformLevel; SimpleConsoleLogger(String name, boolean usePlatformLevel) { this.name = name; this.usePlatformLevel = usePlatformLevel; } String getSimpleFormatString() { return Formatting.SIMPLE_CONSOLE_LOGGER_FORMAT; } PlatformLogger.Level defaultPlatformLevel() { return DEFAULT_PLATFORM_LEVEL; } @Override public final String getName() { return name; } private Enum<?> logLevel(PlatformLogger.Level level) { return usePlatformLevel ? level : level.systemLevel(); } private Enum<?> logLevel(Level level) { return usePlatformLevel ? PlatformLogger.toPlatformLevel(level) : level; } // --------------------------------------------------- // From Logger // --------------------------------------------------- @Override public final boolean isLoggable(Level level) { return isLoggable(PlatformLogger.toPlatformLevel(level)); } @Override public final void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { if (isLoggable(level)) { if (bundle != null) { key = getString(bundle, key); } publish(getCallerInfo(), logLevel(level), key, thrown); } } @Override public final void log(Level level, ResourceBundle bundle, String format, Object... params) { if (isLoggable(level)) { if (bundle != null) { format = getString(bundle, format); } publish(getCallerInfo(), logLevel(level), format, params); } } // --------------------------------------------------- // From PlatformLogger.Bridge // --------------------------------------------------- @Override public final boolean isLoggable(PlatformLogger.Level level) { final PlatformLogger.Level effectiveLevel = effectiveLevel(); return level != PlatformLogger.Level.OFF && level.ordinal() >= effectiveLevel.ordinal(); } @Override public final boolean isEnabled() { return level != PlatformLogger.Level.OFF; } @Override public final void log(PlatformLogger.Level level, String msg) { if (isLoggable(level)) { publish(getCallerInfo(), logLevel(level), msg); } } @Override public final void log(PlatformLogger.Level level, String msg, Throwable thrown) { if (isLoggable(level)) { publish(getCallerInfo(), logLevel(level), msg, thrown); } } @Override public final void log(PlatformLogger.Level level, String msg, Object... params) { if (isLoggable(level)) { publish(getCallerInfo(), logLevel(level), msg, params); } } private PlatformLogger.Level effectiveLevel() { if (level == null) return defaultPlatformLevel(); return level; } @Override public final PlatformLogger.Level getPlatformLevel() { return level; } @Override public final void setPlatformLevel(PlatformLogger.Level newLevel) { level = newLevel; } @Override public final LoggerConfiguration getLoggerConfiguration() { return this; }
Default platform logging support - output messages to System.err - equivalent to ConsoleHandler with SimpleFormatter.
/** * Default platform logging support - output messages to System.err - * equivalent to ConsoleHandler with SimpleFormatter. */
static PrintStream outputStream() { return System.err; } // Returns the caller's class and method's name; best effort // if cannot infer, return the logger's name. private String getCallerInfo() { Optional<StackWalker.StackFrame> frame = new CallerFinder().get(); if (frame.isPresent()) { return frame.get().getClassName() + " " + frame.get().getMethodName(); } else { return name; } } /* * CallerFinder is a stateful predicate. */ static final class CallerFinder implements Predicate<StackWalker.StackFrame> { private static final StackWalker WALKER; static { final PrivilegedAction<StackWalker> action = new PrivilegedAction<>() { @Override public StackWalker run() { return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); } }; WALKER = AccessController.doPrivileged(action); }
Returns StackFrame of the caller's frame.
Returns:StackFrame of the caller's frame.
/** * Returns StackFrame of the caller's frame. * @return StackFrame of the caller's frame. */
Optional<StackWalker.StackFrame> get() { return WALKER.walk((s) -> s.filter(this).findFirst()); } private boolean lookingForLogger = true;
Returns true if we have found the caller's frame, false if the frame must be skipped.
Params:
  • t – The frame info.
Returns:true if we have found the caller's frame, false if the frame must be skipped.
/** * Returns true if we have found the caller's frame, false if the frame * must be skipped. * * @param t The frame info. * @return true if we have found the caller's frame, false if the frame * must be skipped. */
@Override public boolean test(StackWalker.StackFrame t) { final String cname = t.getClassName(); // We should skip all frames until we have found the logger, // because these frames could be frames introduced by e.g. custom // sub classes of Handler. if (lookingForLogger) { // Skip all frames until we have found the first logger frame. lookingForLogger = !isLoggerImplFrame(cname); return false; } // Continue walking until we've found the relevant calling frame. // Skips logging/logger infrastructure. return !Formatting.isFilteredFrame(t); } private boolean isLoggerImplFrame(String cname) { return (cname.equals("sun.util.logging.PlatformLogger") || cname.equals("jdk.internal.logger.SimpleConsoleLogger")); } } private String getCallerInfo(String sourceClassName, String sourceMethodName) { if (sourceClassName == null) return name; if (sourceMethodName == null) return sourceClassName; return sourceClassName + " " + sourceMethodName; } private String toString(Throwable thrown) { String throwable = ""; if (thrown != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.println(); thrown.printStackTrace(pw); pw.close(); throwable = sw.toString(); } return throwable; } private synchronized String format(Enum<?> level, String msg, Throwable thrown, String callerInfo) { ZonedDateTime zdt = ZonedDateTime.now(); String throwable = toString(thrown); return String.format(getSimpleFormatString(), zdt, callerInfo, name, level.name(), msg, throwable); } // publish accepts both PlatformLogger Levels and LoggerFinder Levels. private void publish(String callerInfo, Enum<?> level, String msg) { outputStream().print(format(level, msg, null, callerInfo)); } // publish accepts both PlatformLogger Levels and LoggerFinder Levels. private void publish(String callerInfo, Enum<?> level, String msg, Throwable thrown) { outputStream().print(format(level, msg, thrown, callerInfo)); } // publish accepts both PlatformLogger Levels and LoggerFinder Levels. private void publish(String callerInfo, Enum<?> level, String msg, Object... params) { msg = params == null || params.length == 0 ? msg : Formatting.formatMessage(msg, params); outputStream().print(format(level, msg, null, callerInfo)); } public static SimpleConsoleLogger makeSimpleLogger(String name) { return new SimpleConsoleLogger(name, false); } @Override public final void log(PlatformLogger.Level level, Supplier<String> msgSupplier) { if (isLoggable(level)) { publish(getCallerInfo(), logLevel(level), msgSupplier.get()); } } @Override public final void log(PlatformLogger.Level level, Throwable thrown, Supplier<String> msgSupplier) { if (isLoggable(level)) { publish(getCallerInfo(), logLevel(level), msgSupplier.get(), thrown); } } @Override public final void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod, String msg) { if (isLoggable(level)) { publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg); } } @Override public final void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod, Supplier<String> msgSupplier) { if (isLoggable(level)) { publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get()); } } @Override public final void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod, String msg, Object... params) { if (isLoggable(level)) { publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params); } } @Override public final void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod, String msg, Throwable thrown) { if (isLoggable(level)) { publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown); } } @Override public final void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod, Throwable thrown, Supplier<String> msgSupplier) { if (isLoggable(level)) { publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get(), thrown); } } @Override public final void logrb(PlatformLogger.Level level, String sourceClass, String sourceMethod, ResourceBundle bundle, String key, Object... params) { if (isLoggable(level)) { String msg = bundle == null ? key : getString(bundle, key); publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params); } } @Override public final void logrb(PlatformLogger.Level level, String sourceClass, String sourceMethod, ResourceBundle bundle, String key, Throwable thrown) { if (isLoggable(level)) { String msg = bundle == null ? key : getString(bundle, key); publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown); } } @Override public final void logrb(PlatformLogger.Level level, ResourceBundle bundle, String key, Object... params) { if (isLoggable(level)) { String msg = bundle == null ? key : getString(bundle,key); publish(getCallerInfo(), logLevel(level), msg, params); } } @Override public final void logrb(PlatformLogger.Level level, ResourceBundle bundle, String key, Throwable thrown) { if (isLoggable(level)) { String msg = bundle == null ? key : getString(bundle,key); publish(getCallerInfo(), logLevel(level), msg, thrown); } } static String getString(ResourceBundle bundle, String key) { if (bundle == null || key == null) return key; try { return bundle.getString(key); } catch (MissingResourceException x) { // Emulate what java.util.logging Formatters do // We don't want unchecked exception to propagate up to // the caller's code. return key; } } static final class Formatting { // The default simple log format string. // Used both by SimpleConsoleLogger when java.logging is not present, // and by SurrogateLogger and java.util.logging.SimpleFormatter when // java.logging is present. static final String DEFAULT_FORMAT = "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n"; // The system property key that allows to change the default log format // when java.logging is not present. This is used to control the formatting // of the SimpleConsoleLogger. static final String DEFAULT_FORMAT_PROP_KEY = "jdk.system.logger.format"; // The system property key that allows to change the default log format // when java.logging is present. This is used to control the formatting // of the SurrogateLogger (used before java.util.logging.LogManager is // initialized) and the java.util.logging.SimpleFormatter (used after // java.util.logging.LogManager is initialized). static final String JUL_FORMAT_PROP_KEY = "java.util.logging.SimpleFormatter.format"; // The simple console logger format string static final String SIMPLE_CONSOLE_LOGGER_FORMAT = getSimpleFormat(DEFAULT_FORMAT_PROP_KEY, null); // Make it easier to wrap Logger... static private final String[] skips; static { String additionalPkgs = GetPropertyAction.privilegedGetProperty("jdk.logger.packages"); skips = additionalPkgs == null ? new String[0] : additionalPkgs.split(","); } static boolean isFilteredFrame(StackFrame st) { // skip logging/logger infrastructure if (System.Logger.class.isAssignableFrom(st.getDeclaringClass())) { return true; } // fast escape path: all the prefixes below start with 's' or 'j' and // have more than 12 characters. final String cname = st.getClassName(); char c = cname.length() < 12 ? 0 : cname.charAt(0); if (c == 's') { // skip internal machinery classes if (cname.startsWith("sun.util.logging.")) return true; if (cname.startsWith("sun.rmi.runtime.Log")) return true; } else if (c == 'j') { // Message delayed at Bootstrap: no need to go further up. if (cname.startsWith("jdk.internal.logger.BootstrapLogger$LogEvent")) return false; // skip public machinery classes if (cname.startsWith("jdk.internal.logger.")) return true; if (cname.startsWith("java.util.logging.")) return true; if (cname.startsWith("java.lang.invoke.MethodHandle")) return true; if (cname.startsWith("java.security.AccessController")) return true; } // check additional prefixes if any are specified. if (skips.length > 0) { for (int i=0; i<skips.length; i++) { if (!skips[i].isEmpty() && cname.startsWith(skips[i])) { return true; } } } return false; } static String getSimpleFormat(String key, Function<String, String> defaultPropertyGetter) { // Double check that 'key' is one of the expected property names: // - DEFAULT_FORMAT_PROP_KEY is used to control the // SimpleConsoleLogger format when java.logging is // not present. // - JUL_FORMAT_PROP_KEY is used when this method is called // from the SurrogateLogger subclass. It is used to control the // SurrogateLogger format and java.util.logging.SimpleFormatter // format when java.logging is present. // This method should not be called with any other key. if (!DEFAULT_FORMAT_PROP_KEY.equals(key) && !JUL_FORMAT_PROP_KEY.equals(key)) { throw new IllegalArgumentException("Invalid property name: " + key); } // Do not use any lambda in this method. Using a lambda here causes // jdk/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java // to fail - because that test has a testcase which somehow references // PlatformLogger and counts the number of generated lambda classes. String format = GetPropertyAction.privilegedGetProperty(key); if (format == null && defaultPropertyGetter != null) { format = defaultPropertyGetter.apply(key); } if (format != null) { try { // validate the user-defined format string String.format(format, ZonedDateTime.now(), "", "", "", "", ""); } catch (IllegalArgumentException e) { // illegal syntax; fall back to the default format format = DEFAULT_FORMAT; } } else { format = DEFAULT_FORMAT; } return format; } // Copied from java.util.logging.Formatter.formatMessage static String formatMessage(String format, Object... parameters) { // Do the formatting. try { if (parameters == null || parameters.length == 0) { // No parameters. Just return format string. return format; } // Is it a java.text style format? // Ideally we could match with // Pattern.compile("\\{\\d").matcher(format).find()) // However the cost is 14% higher, so we cheaply check for // boolean isJavaTestFormat = false; final int len = format.length(); for (int i=0; i<len-2; i++) { final char c = format.charAt(i); if (c == '{') { final int d = format.charAt(i+1); if (d >= '0' && d <= '9') { isJavaTestFormat = true; break; } } } if (isJavaTestFormat) { return java.text.MessageFormat.format(format, parameters); } return format; } catch (Exception ex) { // Formatting failed: use format string. return format; } } } }