| /* | |
| * Copyright (c) 2015, 2024, 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.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.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. | |
| */ | |
| 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 = System.getProperty("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. | |
| */ | |
| 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 = | |
| StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); | |
| /** | |
| * 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. | |
| * | |
| * @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... | |
| private static final String[] skips; | |
| static { | |
| String additionalPkgs = System.getProperty("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 = System.getProperty(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; | |
| } | |
| } | |
| } | |
| } |