Path: blob/master/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java
41159 views
/*1* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package jdk.internal.logger;2627import java.io.PrintStream;28import java.io.PrintWriter;29import java.io.StringWriter;30import java.lang.StackWalker.StackFrame;31import java.security.AccessController;32import java.security.PrivilegedAction;33import java.time.ZonedDateTime;34import java.util.Optional;35import java.util.MissingResourceException;36import java.util.ResourceBundle;37import java.util.function.Function;38import java.lang.System.Logger;39import java.util.function.Predicate;40import java.util.function.Supplier;41import sun.security.action.GetPropertyAction;42import sun.util.logging.PlatformLogger;43import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration;4445/**46* A simple console logger to emulate the behavior of JUL loggers when47* in the default configuration. SimpleConsoleLoggers are also used when48* JUL is not present and no DefaultLoggerFinder is installed.49*/50public class SimpleConsoleLogger extends LoggerConfiguration51implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge {5253static final Level DEFAULT_LEVEL = getDefaultLevel();54static final PlatformLogger.Level DEFAULT_PLATFORM_LEVEL =55PlatformLogger.toPlatformLevel(DEFAULT_LEVEL);5657static Level getDefaultLevel() {58String levelName = GetPropertyAction59.privilegedGetProperty("jdk.system.logger.level", "INFO");60try {61return Level.valueOf(levelName);62} catch (IllegalArgumentException iae) {63return Level.INFO;64}65}6667final String name;68volatile PlatformLogger.Level level;69final boolean usePlatformLevel;70SimpleConsoleLogger(String name, boolean usePlatformLevel) {71this.name = name;72this.usePlatformLevel = usePlatformLevel;73}7475String getSimpleFormatString() {76return Formatting.SIMPLE_CONSOLE_LOGGER_FORMAT;77}7879PlatformLogger.Level defaultPlatformLevel() {80return DEFAULT_PLATFORM_LEVEL;81}8283@Override84public final String getName() {85return name;86}8788private Enum<?> logLevel(PlatformLogger.Level level) {89return usePlatformLevel ? level : level.systemLevel();90}9192private Enum<?> logLevel(Level level) {93return usePlatformLevel ? PlatformLogger.toPlatformLevel(level) : level;94}9596// ---------------------------------------------------97// From Logger98// ---------------------------------------------------99100@Override101public final boolean isLoggable(Level level) {102return isLoggable(PlatformLogger.toPlatformLevel(level));103}104105@Override106public final void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {107if (isLoggable(level)) {108if (bundle != null) {109key = getString(bundle, key);110}111publish(getCallerInfo(), logLevel(level), key, thrown);112}113}114115@Override116public final void log(Level level, ResourceBundle bundle, String format, Object... params) {117if (isLoggable(level)) {118if (bundle != null) {119format = getString(bundle, format);120}121publish(getCallerInfo(), logLevel(level), format, params);122}123}124125// ---------------------------------------------------126// From PlatformLogger.Bridge127// ---------------------------------------------------128129@Override130public final boolean isLoggable(PlatformLogger.Level level) {131final PlatformLogger.Level effectiveLevel = effectiveLevel();132return level != PlatformLogger.Level.OFF133&& level.ordinal() >= effectiveLevel.ordinal();134}135136@Override137public final boolean isEnabled() {138return level != PlatformLogger.Level.OFF;139}140141@Override142public final void log(PlatformLogger.Level level, String msg) {143if (isLoggable(level)) {144publish(getCallerInfo(), logLevel(level), msg);145}146}147148@Override149public final void log(PlatformLogger.Level level, String msg, Throwable thrown) {150if (isLoggable(level)) {151publish(getCallerInfo(), logLevel(level), msg, thrown);152}153}154155@Override156public final void log(PlatformLogger.Level level, String msg, Object... params) {157if (isLoggable(level)) {158publish(getCallerInfo(), logLevel(level), msg, params);159}160}161162private PlatformLogger.Level effectiveLevel() {163if (level == null) return defaultPlatformLevel();164return level;165}166167@Override168public final PlatformLogger.Level getPlatformLevel() {169return level;170}171172@Override173public final void setPlatformLevel(PlatformLogger.Level newLevel) {174level = newLevel;175}176177@Override178public final LoggerConfiguration getLoggerConfiguration() {179return this;180}181182/**183* Default platform logging support - output messages to System.err -184* equivalent to ConsoleHandler with SimpleFormatter.185*/186static PrintStream outputStream() {187return System.err;188}189190// Returns the caller's class and method's name; best effort191// if cannot infer, return the logger's name.192private String getCallerInfo() {193Optional<StackWalker.StackFrame> frame = new CallerFinder().get();194if (frame.isPresent()) {195return frame.get().getClassName() + " " + frame.get().getMethodName();196} else {197return name;198}199}200201/*202* CallerFinder is a stateful predicate.203*/204@SuppressWarnings("removal")205static final class CallerFinder implements Predicate<StackWalker.StackFrame> {206private static final StackWalker WALKER;207static {208final PrivilegedAction<StackWalker> action = new PrivilegedAction<>() {209@Override210public StackWalker run() {211return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);212}213};214WALKER = AccessController.doPrivileged(action);215}216217/**218* Returns StackFrame of the caller's frame.219* @return StackFrame of the caller's frame.220*/221Optional<StackWalker.StackFrame> get() {222return WALKER.walk((s) -> s.filter(this).findFirst());223}224225private boolean lookingForLogger = true;226/**227* Returns true if we have found the caller's frame, false if the frame228* must be skipped.229*230* @param t The frame info.231* @return true if we have found the caller's frame, false if the frame232* must be skipped.233*/234@Override235public boolean test(StackWalker.StackFrame t) {236final String cname = t.getClassName();237// We should skip all frames until we have found the logger,238// because these frames could be frames introduced by e.g. custom239// sub classes of Handler.240if (lookingForLogger) {241// Skip all frames until we have found the first logger frame.242lookingForLogger = !isLoggerImplFrame(cname);243return false;244}245// Continue walking until we've found the relevant calling frame.246// Skips logging/logger infrastructure.247return !Formatting.isFilteredFrame(t);248}249250private boolean isLoggerImplFrame(String cname) {251return (cname.equals("sun.util.logging.PlatformLogger") ||252cname.equals("jdk.internal.logger.SimpleConsoleLogger"));253}254}255256private String getCallerInfo(String sourceClassName, String sourceMethodName) {257if (sourceClassName == null) return name;258if (sourceMethodName == null) return sourceClassName;259return sourceClassName + " " + sourceMethodName;260}261262private String toString(Throwable thrown) {263String throwable = "";264if (thrown != null) {265StringWriter sw = new StringWriter();266PrintWriter pw = new PrintWriter(sw);267pw.println();268thrown.printStackTrace(pw);269pw.close();270throwable = sw.toString();271}272return throwable;273}274275private synchronized String format(Enum<?> level,276String msg, Throwable thrown, String callerInfo) {277278ZonedDateTime zdt = ZonedDateTime.now();279String throwable = toString(thrown);280281return String.format(getSimpleFormatString(),282zdt,283callerInfo,284name,285level.name(),286msg,287throwable);288}289290// publish accepts both PlatformLogger Levels and LoggerFinder Levels.291private void publish(String callerInfo, Enum<?> level, String msg) {292outputStream().print(format(level, msg, null, callerInfo));293}294// publish accepts both PlatformLogger Levels and LoggerFinder Levels.295private void publish(String callerInfo, Enum<?> level, String msg, Throwable thrown) {296outputStream().print(format(level, msg, thrown, callerInfo));297}298// publish accepts both PlatformLogger Levels and LoggerFinder Levels.299private void publish(String callerInfo, Enum<?> level, String msg, Object... params) {300msg = params == null || params.length == 0 ? msg301: Formatting.formatMessage(msg, params);302outputStream().print(format(level, msg, null, callerInfo));303}304305public static SimpleConsoleLogger makeSimpleLogger(String name) {306return new SimpleConsoleLogger(name, false);307}308309@Override310public final void log(PlatformLogger.Level level, Supplier<String> msgSupplier) {311if (isLoggable(level)) {312publish(getCallerInfo(), logLevel(level), msgSupplier.get());313}314}315316@Override317public final void log(PlatformLogger.Level level, Throwable thrown,318Supplier<String> msgSupplier) {319if (isLoggable(level)) {320publish(getCallerInfo(), logLevel(level), msgSupplier.get(), thrown);321}322}323324@Override325public final void logp(PlatformLogger.Level level, String sourceClass,326String sourceMethod, String msg) {327if (isLoggable(level)) {328publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg);329}330}331332@Override333public final void logp(PlatformLogger.Level level, String sourceClass,334String sourceMethod, Supplier<String> msgSupplier) {335if (isLoggable(level)) {336publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get());337}338}339340@Override341public final void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod,342String msg, Object... params) {343if (isLoggable(level)) {344publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params);345}346}347348@Override349public final void logp(PlatformLogger.Level level, String sourceClass,350String sourceMethod, String msg, Throwable thrown) {351if (isLoggable(level)) {352publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown);353}354}355356@Override357public final void logp(PlatformLogger.Level level, String sourceClass,358String sourceMethod, Throwable thrown, Supplier<String> msgSupplier) {359if (isLoggable(level)) {360publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get(), thrown);361}362}363364@Override365public final void logrb(PlatformLogger.Level level, String sourceClass,366String sourceMethod, ResourceBundle bundle, String key, Object... params) {367if (isLoggable(level)) {368String msg = bundle == null ? key : getString(bundle, key);369publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params);370}371}372373@Override374public final void logrb(PlatformLogger.Level level, String sourceClass,375String sourceMethod, ResourceBundle bundle, String key, Throwable thrown) {376if (isLoggable(level)) {377String msg = bundle == null ? key : getString(bundle, key);378publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown);379}380}381382@Override383public final void logrb(PlatformLogger.Level level, ResourceBundle bundle,384String key, Object... params) {385if (isLoggable(level)) {386String msg = bundle == null ? key : getString(bundle,key);387publish(getCallerInfo(), logLevel(level), msg, params);388}389}390391@Override392public final void logrb(PlatformLogger.Level level, ResourceBundle bundle,393String key, Throwable thrown) {394if (isLoggable(level)) {395String msg = bundle == null ? key : getString(bundle,key);396publish(getCallerInfo(), logLevel(level), msg, thrown);397}398}399400static String getString(ResourceBundle bundle, String key) {401if (bundle == null || key == null) return key;402try {403return bundle.getString(key);404} catch (MissingResourceException x) {405// Emulate what java.util.logging Formatters do406// We don't want unchecked exception to propagate up to407// the caller's code.408return key;409}410}411412static final class Formatting {413// The default simple log format string.414// Used both by SimpleConsoleLogger when java.logging is not present,415// and by SurrogateLogger and java.util.logging.SimpleFormatter when416// java.logging is present.417static final String DEFAULT_FORMAT =418"%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";419420// The system property key that allows to change the default log format421// when java.logging is not present. This is used to control the formatting422// of the SimpleConsoleLogger.423static final String DEFAULT_FORMAT_PROP_KEY =424"jdk.system.logger.format";425426// The system property key that allows to change the default log format427// when java.logging is present. This is used to control the formatting428// of the SurrogateLogger (used before java.util.logging.LogManager is429// initialized) and the java.util.logging.SimpleFormatter (used after430// java.util.logging.LogManager is initialized).431static final String JUL_FORMAT_PROP_KEY =432"java.util.logging.SimpleFormatter.format";433434// The simple console logger format string435static final String SIMPLE_CONSOLE_LOGGER_FORMAT =436getSimpleFormat(DEFAULT_FORMAT_PROP_KEY, null);437438// Make it easier to wrap Logger...439static private final String[] skips;440static {441String additionalPkgs =442GetPropertyAction.privilegedGetProperty("jdk.logger.packages");443skips = additionalPkgs == null ? new String[0] : additionalPkgs.split(",");444}445446static boolean isFilteredFrame(StackFrame st) {447// skip logging/logger infrastructure448if (System.Logger.class.isAssignableFrom(st.getDeclaringClass())) {449return true;450}451452// fast escape path: all the prefixes below start with 's' or 'j' and453// have more than 12 characters.454final String cname = st.getClassName();455char c = cname.length() < 12 ? 0 : cname.charAt(0);456if (c == 's') {457// skip internal machinery classes458if (cname.startsWith("sun.util.logging.")) return true;459if (cname.startsWith("sun.rmi.runtime.Log")) return true;460} else if (c == 'j') {461// Message delayed at Bootstrap: no need to go further up.462if (cname.startsWith("jdk.internal.logger.BootstrapLogger$LogEvent")) return false;463// skip public machinery classes464if (cname.startsWith("jdk.internal.logger.")) return true;465if (cname.startsWith("java.util.logging.")) return true;466if (cname.startsWith("java.lang.invoke.MethodHandle")) return true;467if (cname.startsWith("java.security.AccessController")) return true;468}469470// check additional prefixes if any are specified.471if (skips.length > 0) {472for (int i=0; i<skips.length; i++) {473if (!skips[i].isEmpty() && cname.startsWith(skips[i])) {474return true;475}476}477}478479return false;480}481482static String getSimpleFormat(String key, Function<String, String> defaultPropertyGetter) {483// Double check that 'key' is one of the expected property names:484// - DEFAULT_FORMAT_PROP_KEY is used to control the485// SimpleConsoleLogger format when java.logging is486// not present.487// - JUL_FORMAT_PROP_KEY is used when this method is called488// from the SurrogateLogger subclass. It is used to control the489// SurrogateLogger format and java.util.logging.SimpleFormatter490// format when java.logging is present.491// This method should not be called with any other key.492if (!DEFAULT_FORMAT_PROP_KEY.equals(key)493&& !JUL_FORMAT_PROP_KEY.equals(key)) {494throw new IllegalArgumentException("Invalid property name: " + key);495}496497// Do not use any lambda in this method. Using a lambda here causes498// jdk/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java499// to fail - because that test has a testcase which somehow references500// PlatformLogger and counts the number of generated lambda classes.501String format = GetPropertyAction.privilegedGetProperty(key);502503if (format == null && defaultPropertyGetter != null) {504format = defaultPropertyGetter.apply(key);505}506if (format != null) {507try {508// validate the user-defined format string509String.format(format, ZonedDateTime.now(), "", "", "", "", "");510} catch (IllegalArgumentException e) {511// illegal syntax; fall back to the default format512format = DEFAULT_FORMAT;513}514} else {515format = DEFAULT_FORMAT;516}517return format;518}519520521// Copied from java.util.logging.Formatter.formatMessage522static String formatMessage(String format, Object... parameters) {523// Do the formatting.524try {525if (parameters == null || parameters.length == 0) {526// No parameters. Just return format string.527return format;528}529// Is it a java.text style format?530// Ideally we could match with531// Pattern.compile("\\{\\d").matcher(format).find())532// However the cost is 14% higher, so we cheaply check for533//534boolean isJavaTestFormat = false;535final int len = format.length();536for (int i=0; i<len-2; i++) {537final char c = format.charAt(i);538if (c == '{') {539final int d = format.charAt(i+1);540if (d >= '0' && d <= '9') {541isJavaTestFormat = true;542break;543}544}545}546if (isJavaTestFormat) {547return java.text.MessageFormat.format(format, parameters);548}549return format;550} catch (Exception ex) {551// Formatting failed: use format string.552return format;553}554}555}556}557558559