|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
static PrintStream outputStream() { |
|
return System.err; |
|
} |
|
|
|
// Returns the caller's class and method's name; best effort |
|
|
|
private String getCallerInfo() { |
|
Optional<StackWalker.StackFrame> frame = new CallerFinder().get(); |
|
if (frame.isPresent()) { |
|
return frame.get().getClassName() + " " + frame.get().getMethodName(); |
|
} else { |
|
return name; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("removal") |
|
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); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
Optional<StackWalker.StackFrame> get() { |
|
return WALKER.walk((s) -> s.filter(this).findFirst()); |
|
} |
|
|
|
private boolean lookingForLogger = true; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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 |
|
|
|
if (lookingForLogger) { |
|
|
|
lookingForLogger = !isLoggerImplFrame(cname); |
|
return false; |
|
} |
|
// Continue walking until we've found the relevant calling frame. |
|
|
|
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); |
|
} |
|
|
|
|
|
private void publish(String callerInfo, Enum<?> level, String msg) { |
|
outputStream().print(format(level, msg, null, callerInfo)); |
|
} |
|
|
|
private void publish(String callerInfo, Enum<?> level, String msg, Throwable thrown) { |
|
outputStream().print(format(level, msg, thrown, callerInfo)); |
|
} |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
static final String JUL_FORMAT_PROP_KEY = |
|
"java.util.logging.SimpleFormatter.format"; |
|
|
|
|
|
static final String SIMPLE_CONSOLE_LOGGER_FORMAT = |
|
getSimpleFormat(DEFAULT_FORMAT_PROP_KEY, null); |
|
|
|
|
|
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) { |
|
|
|
if (System.Logger.class.isAssignableFrom(st.getDeclaringClass())) { |
|
return true; |
|
} |
|
|
|
// fast escape path: all the prefixes below start with 's' or 'j' and |
|
|
|
final String cname = st.getClassName(); |
|
char c = cname.length() < 12 ? 0 : cname.charAt(0); |
|
if (c == 's') { |
|
|
|
if (cname.startsWith("sun.util.logging.")) return true; |
|
if (cname.startsWith("sun.rmi.runtime.Log")) return true; |
|
} else if (c == 'j') { |
|
|
|
if (cname.startsWith("jdk.internal.logger.BootstrapLogger$LogEvent")) return false; |
|
|
|
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; |
|
} |
|
|
|
|
|
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. |
|
|
|
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 |
|
|
|
String format = GetPropertyAction.privilegedGetProperty(key); |
|
|
|
if (format == null && defaultPropertyGetter != null) { |
|
format = defaultPropertyGetter.apply(key); |
|
} |
|
if (format != null) { |
|
try { |
|
|
|
String.format(format, ZonedDateTime.now(), "", "", "", "", ""); |
|
} catch (IllegalArgumentException e) { |
|
|
|
format = DEFAULT_FORMAT; |
|
} |
|
} else { |
|
format = DEFAULT_FORMAT; |
|
} |
|
return format; |
|
} |
|
|
|
|
|
|
|
static String formatMessage(String format, Object... parameters) { |
|
|
|
try { |
|
if (parameters == null || parameters.length == 0) { |
|
|
|
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) { |
|
|
|
return format; |
|
} |
|
} |
|
} |
|
} |