8314263: Signed jars triggering Logger finder recursion and StackOverflowError
Co-authored-by: Daniel Fuchs <dfuchs@openjdk.org> Reviewed-by: dfuchs
This commit is contained in:
parent
6701eba736
commit
7daae1fb42
@ -68,6 +68,7 @@ import java.util.function.Supplier;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.logger.LoggerFinderLoader.TemporaryLoggerFinder;
|
||||
import jdk.internal.misc.CarrierThreadLocal;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.util.StaticProperty;
|
||||
@ -1766,13 +1767,16 @@ public final class System {
|
||||
// We do not need to synchronize: LoggerFinderLoader will
|
||||
// always return the same instance, so if we don't have it,
|
||||
// just fetch it again.
|
||||
if (service == null) {
|
||||
LoggerFinder finder = service;
|
||||
if (finder == null) {
|
||||
PrivilegedAction<LoggerFinder> pa =
|
||||
() -> LoggerFinderLoader.getLoggerFinder();
|
||||
service = AccessController.doPrivileged(pa, null,
|
||||
finder = AccessController.doPrivileged(pa, null,
|
||||
LOGGERFINDER_PERMISSION);
|
||||
if (finder instanceof TemporaryLoggerFinder) return finder;
|
||||
service = finder;
|
||||
}
|
||||
return service;
|
||||
return finder;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2023, 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
|
||||
@ -38,7 +38,6 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.lang.System.LoggerFinder;
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -227,9 +226,19 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
||||
|
||||
// The accessor in which this logger is temporarily set.
|
||||
final LazyLoggerAccessor holder;
|
||||
// tests whether the logger is invoked by the loading thread before
|
||||
// the LoggerFinder is loaded; can be null;
|
||||
final BooleanSupplier isLoadingThread;
|
||||
|
||||
BootstrapLogger(LazyLoggerAccessor holder) {
|
||||
// returns true if the logger is invoked by the loading thread before the
|
||||
// LoggerFinder service is loaded
|
||||
boolean isLoadingThread() {
|
||||
return isLoadingThread != null && isLoadingThread.getAsBoolean();
|
||||
}
|
||||
|
||||
BootstrapLogger(LazyLoggerAccessor holder, BooleanSupplier isLoadingThread) {
|
||||
this.holder = holder;
|
||||
this.isLoadingThread = isLoadingThread;
|
||||
}
|
||||
|
||||
// Temporary data object storing log events
|
||||
@ -505,14 +514,15 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
||||
static void log(LogEvent log, PlatformLogger.Bridge logger) {
|
||||
final SecurityManager sm = System.getSecurityManager();
|
||||
if (sm == null || log.acc == null) {
|
||||
log.log(logger);
|
||||
BootstrapExecutors.submit(() -> log.log(logger));
|
||||
} else {
|
||||
// not sure we can actually use lambda here. We may need to create
|
||||
// an anonymous class. Although if we reach here, then it means
|
||||
// the VM is booted.
|
||||
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
|
||||
log.log(logger); return null;
|
||||
}, log.acc);
|
||||
BootstrapExecutors.submit(() ->
|
||||
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
|
||||
log.log(logger); return null;
|
||||
}, log.acc));
|
||||
}
|
||||
}
|
||||
|
||||
@ -559,8 +569,9 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
||||
* @return true if the VM is still bootstrapping.
|
||||
*/
|
||||
boolean checkBootstrapping() {
|
||||
if (isBooted()) {
|
||||
if (isBooted() && !isLoadingThread()) {
|
||||
BootstrapExecutors.flush();
|
||||
holder.getConcreteLogger(this);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -935,10 +946,16 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
||||
// - the logging backend is a custom backend
|
||||
// - the logging backend is JUL, there is no custom config,
|
||||
// and the LogManager has not been initialized yet.
|
||||
public static synchronized boolean useLazyLoggers() {
|
||||
return !BootstrapLogger.isBooted()
|
||||
|| DetectBackend.detectedBackend == LoggingBackend.CUSTOM
|
||||
|| useSurrogateLoggers();
|
||||
public static boolean useLazyLoggers() {
|
||||
// Note: avoid triggering the initialization of the DetectBackend class
|
||||
// while holding the BootstrapLogger class monitor
|
||||
if (!BootstrapLogger.isBooted() ||
|
||||
DetectBackend.detectedBackend == LoggingBackend.CUSTOM) {
|
||||
return true;
|
||||
}
|
||||
synchronized (BootstrapLogger.class) {
|
||||
return useSurrogateLoggers();
|
||||
}
|
||||
}
|
||||
|
||||
// Called by LazyLoggerAccessor. This method will determine whether
|
||||
@ -946,9 +963,9 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
||||
// a SurrogateLogger (if JUL is the default backend and there
|
||||
// is no custom JUL configuration and LogManager is not yet initialized),
|
||||
// or a logger returned by the loaded LoggerFinder (all other cases).
|
||||
static Logger getLogger(LazyLoggerAccessor accessor) {
|
||||
if (!BootstrapLogger.isBooted()) {
|
||||
return new BootstrapLogger(accessor);
|
||||
static Logger getLogger(LazyLoggerAccessor accessor, BooleanSupplier isLoading) {
|
||||
if (!BootstrapLogger.isBooted() || isLoading != null && isLoading.getAsBoolean()) {
|
||||
return new BootstrapLogger(accessor, isLoading);
|
||||
} else {
|
||||
if (useSurrogateLoggers()) {
|
||||
// JUL is the default backend, there is no custom configuration,
|
||||
@ -964,6 +981,12 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
||||
}
|
||||
}
|
||||
|
||||
// trigger class initialization outside of holding lock
|
||||
static void ensureBackendDetected() {
|
||||
assert VM.isBooted() : "VM is not booted";
|
||||
// triggers detection of the backend
|
||||
var backend = DetectBackend.detectedBackend;
|
||||
}
|
||||
|
||||
// If the backend is JUL, and there is no custom configuration, and
|
||||
// nobody has attempted to call LogManager.getLogManager() yet, then
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2023, 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
|
||||
@ -32,6 +32,9 @@ import java.lang.System.LoggerFinder;
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import jdk.internal.logger.LoggerFinderLoader.TemporaryLoggerFinder;
|
||||
import jdk.internal.misc.VM;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
@ -110,6 +113,9 @@ public final class LazyLoggers {
|
||||
// We need to pass the actual caller module when creating the logger.
|
||||
private final WeakReference<Module> moduleRef;
|
||||
|
||||
// whether this is the loading thread, can be null
|
||||
private final BooleanSupplier isLoadingThread;
|
||||
|
||||
// The name of the logger that will be created lazyly
|
||||
final String name;
|
||||
// The plain logger SPI object - null until it is accessed for the
|
||||
@ -122,16 +128,24 @@ public final class LazyLoggers {
|
||||
private LazyLoggerAccessor(String name,
|
||||
LazyLoggerFactories<? extends Logger> factories,
|
||||
Module module) {
|
||||
this(Objects.requireNonNull(name), Objects.requireNonNull(factories),
|
||||
Objects.requireNonNull(module), null);
|
||||
this(name, factories, module, null);
|
||||
}
|
||||
|
||||
private LazyLoggerAccessor(String name,
|
||||
LazyLoggerFactories<? extends Logger> factories,
|
||||
Module module, Void unused) {
|
||||
Module module, BooleanSupplier isLoading) {
|
||||
|
||||
this(Objects.requireNonNull(name), Objects.requireNonNull(factories),
|
||||
Objects.requireNonNull(module), isLoading, null);
|
||||
}
|
||||
|
||||
private LazyLoggerAccessor(String name,
|
||||
LazyLoggerFactories<? extends Logger> factories,
|
||||
Module module, BooleanSupplier isLoading, Void unused) {
|
||||
this.name = name;
|
||||
this.factories = factories;
|
||||
this.moduleRef = new WeakReference<>(module);
|
||||
this.isLoadingThread = isLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,7 +176,7 @@ public final class LazyLoggers {
|
||||
// BootstrapLogger has the logic to decide whether to invoke the
|
||||
// SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger)
|
||||
// logger.
|
||||
wrapped = BootstrapLogger.getLogger(this);
|
||||
wrapped = BootstrapLogger.getLogger(this, isLoadingThread);
|
||||
synchronized(this) {
|
||||
// if w has already been in between, simply drop 'wrapped'.
|
||||
setWrappedIfNotSet(wrapped);
|
||||
@ -194,7 +208,7 @@ public final class LazyLoggers {
|
||||
// BootstrapLogger has the logic to decide whether to invoke the
|
||||
// SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger)
|
||||
// logger.
|
||||
final Logger wrapped = BootstrapLogger.getLogger(this);
|
||||
final Logger wrapped = BootstrapLogger.getLogger(this, isLoadingThread);
|
||||
synchronized(this) {
|
||||
// if w has already been set, simply drop 'wrapped'.
|
||||
setWrappedIfNotSet(wrapped);
|
||||
@ -282,10 +296,10 @@ public final class LazyLoggers {
|
||||
* Creates a new lazy logger accessor for the named logger. The given
|
||||
* factories will be use when it becomes necessary to actually create
|
||||
* the logger.
|
||||
* @param <T> An interface that extends {@link Logger}.
|
||||
* @param name The logger name.
|
||||
* @param factories The factories that should be used to create the
|
||||
* wrapped logger.
|
||||
* @param module The module for which the logger is being created
|
||||
* @return A new LazyLoggerAccessor.
|
||||
*/
|
||||
public static LazyLoggerAccessor makeAccessor(String name,
|
||||
@ -340,6 +354,7 @@ public final class LazyLoggers {
|
||||
prov = sm == null ? LoggerFinder.getLoggerFinder() :
|
||||
AccessController.doPrivileged(
|
||||
(PrivilegedAction<LoggerFinder>)LoggerFinder::getLoggerFinder);
|
||||
if (prov instanceof TemporaryLoggerFinder) return prov;
|
||||
provider = prov;
|
||||
}
|
||||
return prov;
|
||||
@ -359,7 +374,6 @@ public final class LazyLoggers {
|
||||
new LazyLoggerFactories<>(loggerSupplier);
|
||||
|
||||
|
||||
|
||||
// A concrete implementation of Logger that delegates to a System.Logger,
|
||||
// but only creates the System.Logger instance lazily when it's used for
|
||||
// the first time.
|
||||
@ -377,6 +391,11 @@ public final class LazyLoggers {
|
||||
}
|
||||
}
|
||||
|
||||
static Logger makeLazyLogger(String name, Module module, BooleanSupplier isLoading) {
|
||||
final LazyLoggerAccessor holder = new LazyLoggerAccessor(name, factories, module, isLoading);
|
||||
return new JdkLazyLogger(holder, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a logger from the LoggerFinder. Creates the actual concrete
|
||||
* logger.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2023, 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
|
||||
@ -25,6 +25,8 @@
|
||||
package jdk.internal.logger;
|
||||
|
||||
import java.io.FilePermission;
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.LoggerFinder;
|
||||
import java.security.AccessController;
|
||||
import java.security.Permission;
|
||||
import java.security.PrivilegedAction;
|
||||
@ -32,6 +34,9 @@ import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
import sun.security.util.SecurityConstants;
|
||||
import sun.security.action.GetBooleanAction;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
@ -65,13 +70,28 @@ public final class LoggerFinderLoader {
|
||||
throw new InternalError("LoggerFinderLoader cannot be instantiated");
|
||||
}
|
||||
|
||||
|
||||
// record the loadingThread while loading the backend
|
||||
static volatile Thread loadingThread;
|
||||
// Return the loaded LoggerFinder, or load it if not already loaded.
|
||||
private static System.LoggerFinder service() {
|
||||
if (service != null) return service;
|
||||
// ensure backend is detected before attempting to load the finder
|
||||
BootstrapLogger.ensureBackendDetected();
|
||||
synchronized(lock) {
|
||||
if (service != null) return service;
|
||||
service = loadLoggerFinder();
|
||||
Thread currentThread = Thread.currentThread();
|
||||
if (loadingThread == currentThread) {
|
||||
// recursive attempt to load the backend while loading the backend
|
||||
// use a temporary logger finder that returns special BootstrapLogger
|
||||
// which will wait until loading is finished
|
||||
return TemporaryLoggerFinder.INSTANCE;
|
||||
}
|
||||
loadingThread = currentThread;
|
||||
try {
|
||||
service = loadLoggerFinder();
|
||||
} finally {
|
||||
loadingThread = null;
|
||||
}
|
||||
}
|
||||
// Since the LoggerFinder is already loaded - we can stop using
|
||||
// temporary loggers.
|
||||
@ -79,6 +99,12 @@ public final class LoggerFinderLoader {
|
||||
return service;
|
||||
}
|
||||
|
||||
// returns true if called by the thread that loads the LoggerFinder, while
|
||||
// loading the LoggerFinder.
|
||||
static boolean isLoadingThread() {
|
||||
return loadingThread != null && loadingThread == Thread.currentThread();
|
||||
}
|
||||
|
||||
// Get configuration error policy
|
||||
private static ErrorPolicy configurationErrorPolicy() {
|
||||
String errorPolicy =
|
||||
@ -117,6 +143,34 @@ public final class LoggerFinderLoader {
|
||||
return iterator;
|
||||
}
|
||||
|
||||
public static final class TemporaryLoggerFinder extends LoggerFinder {
|
||||
private TemporaryLoggerFinder() {}
|
||||
@Stable
|
||||
private LoggerFinder loadedService;
|
||||
|
||||
private static final BooleanSupplier isLoadingThread = new BooleanSupplier() {
|
||||
@Override
|
||||
public boolean getAsBoolean() {
|
||||
return LoggerFinderLoader.isLoadingThread();
|
||||
}
|
||||
};
|
||||
private static final TemporaryLoggerFinder INSTANCE = new TemporaryLoggerFinder();
|
||||
|
||||
@Override
|
||||
public Logger getLogger(String name, Module module) {
|
||||
if (loadedService == null) {
|
||||
loadedService = service;
|
||||
if (loadedService == null) {
|
||||
return LazyLoggers.makeLazyLogger(name, module, isLoadingThread);
|
||||
}
|
||||
}
|
||||
assert loadedService != null;
|
||||
assert !LoggerFinderLoader.isLoadingThread();
|
||||
assert loadedService != this;
|
||||
return LazyLoggers.getLogger(name, module);
|
||||
}
|
||||
}
|
||||
|
||||
// Loads the LoggerFinder using ServiceLoader. If no LoggerFinder
|
||||
// is found returns the default (possibly JUL based) implementation
|
||||
private static System.LoggerFinder loadLoggerFinder() {
|
||||
|
@ -0,0 +1 @@
|
||||
loggerfinder.SimpleLoggerFinder
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8314263
|
||||
* @summary Creating a logger while loading the Logger finder
|
||||
* triggers recursion and StackOverflowError
|
||||
* @modules java.base/sun.util.logging
|
||||
* @library /test/lib
|
||||
* @compile RecursiveLoadingTest.java SimpleLoggerFinder.java
|
||||
* @run main/othervm PlatformRecursiveLoadingTest
|
||||
*/
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
public class PlatformRecursiveLoadingTest {
|
||||
|
||||
/**
|
||||
* This test triggers recursion by calling `System.getLogger` in the class init and constructor
|
||||
* of a custom LoggerFinder. Without the fix, this is expected to throw
|
||||
* java.lang.NoClassDefFoundError: Could not initialize class jdk.internal.logger.LoggerFinderLoader$ErrorPolicy
|
||||
* caused by: java.lang.StackOverflowError
|
||||
*/
|
||||
public static void main(String[] args) throws Throwable {
|
||||
PlatformLogger.getLogger("main").info("in main");
|
||||
List<Object> logs = loggerfinder.SimpleLoggerFinder.LOGS;
|
||||
logs.stream().map(SimpleLogRecord::of).forEach(System.out::println);
|
||||
logs.stream().map(SimpleLogRecord::of).forEach(SimpleLogRecord::check);
|
||||
assertEquals(String.valueOf(logs.size()), String.valueOf(3));
|
||||
}
|
||||
|
||||
static List<Object> asList(Object[] params) {
|
||||
return params == null ? null : Arrays.asList(params);
|
||||
}
|
||||
|
||||
record SimpleLogRecord(String message, Instant instant, String loggerName,
|
||||
java.util.logging.Level level, List<Object> params,
|
||||
String resourceBundleName, long seqNumber,
|
||||
String sourceClassName, String methodName, Throwable thrown) {
|
||||
SimpleLogRecord(LogRecord record) {
|
||||
this(record.getMessage(), record.getInstant(), record.getLoggerName(), record.getLevel(),
|
||||
asList(record.getParameters()), record.getResourceBundleName(), record.getSequenceNumber(),
|
||||
record.getSourceClassName(), record.getSourceMethodName(), record.getThrown());
|
||||
}
|
||||
static SimpleLogRecord of(Object o) {
|
||||
return (o instanceof LogRecord record) ? new SimpleLogRecord(record) : null;
|
||||
}
|
||||
static SimpleLogRecord check(SimpleLogRecord record) {
|
||||
if (record.loggerName.equals("dummy")) {
|
||||
assertEquals(record.sourceClassName, "jdk.internal.logger.BootstrapLogger$LogEvent");
|
||||
assertEquals(record.methodName(), "log");
|
||||
}
|
||||
if (record.loggerName.equals("main")) {
|
||||
assertEquals(record.sourceClassName, PlatformRecursiveLoadingTest.class.getName());
|
||||
assertEquals(record.methodName, "main");
|
||||
}
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertEquals(String received, String expected) {
|
||||
if (!expected.equals(received)) {
|
||||
throw new RuntimeException("Received: " + received);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8314263
|
||||
* @summary Creating a logger while loading the Logger finder
|
||||
* triggers recursion and StackOverflowError
|
||||
* @compile RecursiveLoadingTest.java SimpleLoggerFinder.java
|
||||
* @run main/othervm RecursiveLoadingTest
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
public class RecursiveLoadingTest {
|
||||
|
||||
/**
|
||||
* This test triggers recursion by calling `System.getLogger` in the class init and constructor
|
||||
* of a custom LoggerFinder. Without the fix, this is expected to throw
|
||||
* java.lang.NoClassDefFoundError: Could not initialize class jdk.internal.logger.LoggerFinderLoader$ErrorPolicy
|
||||
* caused by: java.lang.StackOverflowError
|
||||
*/
|
||||
public static void main(String[] args) throws Throwable {
|
||||
System.getLogger("main").log(System.Logger.Level.INFO, "in main");
|
||||
List<Object> logs = loggerfinder.SimpleLoggerFinder.LOGS;
|
||||
logs.stream().map(SimpleLogRecord::of).forEach(System.out::println);
|
||||
logs.stream().map(SimpleLogRecord::of).forEach(SimpleLogRecord::check);
|
||||
assertEquals(String.valueOf(logs.size()), String.valueOf(3));
|
||||
}
|
||||
|
||||
static List<Object> asList(Object[] params) {
|
||||
return params == null ? null : Arrays.asList(params);
|
||||
}
|
||||
|
||||
record SimpleLogRecord(String message, Instant instant, String loggerName,
|
||||
java.util.logging.Level level, List<Object> params,
|
||||
String resourceBundleName, long seqNumber,
|
||||
String sourceClassName, String methodName, Throwable thrown) {
|
||||
SimpleLogRecord(LogRecord record) {
|
||||
this(record.getMessage(), record.getInstant(), record.getLoggerName(), record.getLevel(),
|
||||
asList(record.getParameters()), record.getResourceBundleName(), record.getSequenceNumber(),
|
||||
record.getSourceClassName(), record.getSourceMethodName(), record.getThrown());
|
||||
}
|
||||
static SimpleLogRecord of(Object o) {
|
||||
return (o instanceof LogRecord record) ? new SimpleLogRecord(record) : null;
|
||||
}
|
||||
static SimpleLogRecord check(SimpleLogRecord record) {
|
||||
if (record.loggerName.equals("dummy")) {
|
||||
assertEquals(record.sourceClassName, "jdk.internal.logger.BootstrapLogger$LogEvent");
|
||||
assertEquals(record.methodName(), "log");
|
||||
}
|
||||
if (record.loggerName.equals("main")) {
|
||||
assertEquals(record.sourceClassName, RecursiveLoadingTest.class.getName());
|
||||
assertEquals(record.methodName, "main");
|
||||
}
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertEquals(String received, String expected) {
|
||||
if (!expected.equals(received)) {
|
||||
throw new RuntimeException("Received: " + received);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*
|
||||
* 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 loggerfinder;
|
||||
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class SimpleLoggerFinder extends System.LoggerFinder {
|
||||
|
||||
public static final CopyOnWriteArrayList<Object> LOGS = new CopyOnWriteArrayList<>();
|
||||
static {
|
||||
try {
|
||||
long sleep = new Random().nextLong(1000L) + 1L;
|
||||
// simulate a slow load service
|
||||
Thread.sleep(sleep);
|
||||
System.getLogger("dummy")
|
||||
.log(System.Logger.Level.INFO,
|
||||
"Logger finder service load sleep value: " + sleep);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<String, SimpleLogger> loggers = new ConcurrentHashMap<>();
|
||||
public SimpleLoggerFinder() {
|
||||
System.getLogger("dummy")
|
||||
.log(System.Logger.Level.INFO,
|
||||
"Logger finder service created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public System.Logger getLogger(String name, Module module) {
|
||||
return loggers.computeIfAbsent(name, SimpleLogger::new);
|
||||
}
|
||||
|
||||
private static class SimpleLogger implements System.Logger {
|
||||
private final java.util.logging.Logger logger;
|
||||
|
||||
private static final class SimpleHandler extends Handler {
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
LOGS.add(record);
|
||||
}
|
||||
@Override public void flush() { }
|
||||
@Override public void close() { }
|
||||
}
|
||||
|
||||
public SimpleLogger(String name) {
|
||||
logger = Logger.getLogger(name);
|
||||
logger.addHandler(new SimpleHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return logger.getName();
|
||||
}
|
||||
|
||||
java.util.logging.Level level(Level level) {
|
||||
return switch (level) {
|
||||
case ALL -> java.util.logging.Level.ALL;
|
||||
case DEBUG -> java.util.logging.Level.FINE;
|
||||
case TRACE -> java.util.logging.Level.FINER;
|
||||
case INFO -> java.util.logging.Level.INFO;
|
||||
case WARNING -> java.util.logging.Level.WARNING;
|
||||
case ERROR -> java.util.logging.Level.SEVERE;
|
||||
case OFF -> java.util.logging.Level.OFF;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoggable(Level level) {
|
||||
return logger.isLoggable(level(level));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
|
||||
var julLevel = level(level);
|
||||
if (!logger.isLoggable(julLevel)) return;
|
||||
if (bundle != null) {
|
||||
logger.logrb(julLevel, bundle, msg, thrown);
|
||||
} else {
|
||||
logger.log(julLevel, msg, thrown);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
|
||||
var julLevel = level(level);
|
||||
if (!logger.isLoggable(julLevel)) return;
|
||||
if (params == null) {
|
||||
if (bundle == null) {
|
||||
logger.log(julLevel, format);
|
||||
} else {
|
||||
logger.logrb(julLevel, bundle, format);
|
||||
}
|
||||
} else {
|
||||
if (bundle == null) {
|
||||
logger.log(julLevel, format, params);
|
||||
} else {
|
||||
logger.logrb(julLevel, bundle, format, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
loggerfinder.SimpleLoggerFinder
|
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8314263
|
||||
* @summary Signed jars triggering Logger finder recursion and StackOverflowError
|
||||
* @library /test/lib
|
||||
* @build jdk.test.lib.compiler.CompilerUtils
|
||||
* jdk.test.lib.process.*
|
||||
* jdk.test.lib.util.JarUtils
|
||||
* jdk.test.lib.JDKToolLauncher
|
||||
* @compile SignedLoggerFinderTest.java SimpleLoggerFinder.java
|
||||
* @run main SignedLoggerFinderTest init
|
||||
* @run main SignedLoggerFinderTest init sign
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.*;
|
||||
import java.security.*;
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.jar.*;
|
||||
|
||||
import jdk.test.lib.JDKToolFinder;
|
||||
import jdk.test.lib.JDKToolLauncher;
|
||||
import jdk.test.lib.Utils;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
public class SignedLoggerFinderTest {
|
||||
|
||||
/**
|
||||
* This test triggers recursion in the broken JDK. The error can
|
||||
* manifest in a few different ways.
|
||||
* One error seen is "java.lang.NoClassDefFoundError:
|
||||
* Could not initialize class jdk.internal.logger.LoggerFinderLoader$ErrorPolicy"
|
||||
*
|
||||
* The original reported error was a StackOverflow (also seen in different iterations
|
||||
* of this run). Running test in signed and unsigned jar mode for sanity coverage.
|
||||
* The current bug only manifests when jars are signed.
|
||||
*/
|
||||
|
||||
private static boolean init = false;
|
||||
private static boolean signJars = false;
|
||||
private static boolean mutliThreadLoad = false;
|
||||
private static volatile boolean testComplete = false;
|
||||
|
||||
private static final String KEYSTORE = "8314263.keystore";
|
||||
private static final String ALIAS = "JavaTest";
|
||||
private static final String STOREPASS = "changeit";
|
||||
private static final String KEYPASS = "changeit";
|
||||
private static final String DNAME = "CN=sample";
|
||||
private static final String CUSTOM_LOGGER_FINDER_NAME =
|
||||
"loggerfinder.SimpleLoggerFinder";
|
||||
private static final String CUSTOM_LOGGER_NAME =
|
||||
"loggerfinder.SimpleLoggerFinder$SimpleLogger";
|
||||
private static final String INTERNAL_LOGGER_FINDER_NAME =
|
||||
"sun.util.logging.internal.LoggingProviderImpl";
|
||||
private static final String INTERNAL_LOGGER_NAME =
|
||||
"sun.util.logging.internal.LoggingProviderImpl$JULWrapper";
|
||||
private static final Path jarPath1 =
|
||||
Path.of(System.getProperty("test.classes", "."), "SimpleLoggerFinder.jar");
|
||||
private static final Path jarPath2 =
|
||||
Path.of(System.getProperty("test.classes", "."), "SimpleLoggerFinder2.jar");
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
init = args.length >=1 && args[0].equals("init");
|
||||
signJars = args.length >=2 && args[1].equals("sign");
|
||||
|
||||
// init only passed in by jtreg test run, initialize the environment
|
||||
// for the subsequent test run
|
||||
if (init) {
|
||||
initialize();
|
||||
launchTest(false, false);
|
||||
launchTest(false, true);
|
||||
launchTest(true, false);
|
||||
launchTest(true, true);
|
||||
|
||||
} else {
|
||||
// set up complete. Run the code to trigger the recursion
|
||||
// We're in the JVM launched by ProcessTools.executeCommand
|
||||
boolean multiThreadLoad = Boolean.getBoolean("multiThreadLoad");
|
||||
boolean withCustomLoggerFinder = Boolean.getBoolean("withCustomLoggerFinder");
|
||||
|
||||
if (multiThreadLoad) {
|
||||
long sleep = new Random().nextLong(100L) + 1L;
|
||||
System.out.println("multi thread load sleep value: " + sleep);
|
||||
new Thread(runnableWithSleep(
|
||||
() -> System.getLogger("logger" + System.currentTimeMillis()),
|
||||
sleep, "System.getLogger type: ")).start();
|
||||
new Thread(runnableWithSleep(
|
||||
() -> System.LoggerFinder.getLoggerFinder(),
|
||||
sleep, "System.getLoggerFinder type: ")).start();
|
||||
}
|
||||
|
||||
if (withCustomLoggerFinder) {
|
||||
JarFile jf = new JarFile(jarPath1.toString(), true);
|
||||
jf.getInputStream(jf.getJarEntry("loggerfinder/SimpleLoggerFinder.class"));
|
||||
JarFile jf2 = new JarFile(jarPath2.toString(), true);
|
||||
jf2.getInputStream(jf.getJarEntry("loggerfinder/SimpleLoggerFinder.class"));
|
||||
} else {
|
||||
// some other call to prod LoggerFinder loading
|
||||
System.getLogger("random" + System.currentTimeMillis());
|
||||
System.LoggerFinder.getLoggerFinder();
|
||||
}
|
||||
Security.setProperty("test_1", "test");
|
||||
|
||||
// some extra sanity checks
|
||||
if (withCustomLoggerFinder) {
|
||||
assertEquals(System.LoggerFinder.getLoggerFinder().getClass().getName(),
|
||||
CUSTOM_LOGGER_FINDER_NAME);
|
||||
System.Logger testLogger = System.getLogger("jdk.event.security");
|
||||
assertEquals(testLogger.getClass().getName(), CUSTOM_LOGGER_NAME);
|
||||
} else {
|
||||
assertEquals(System.LoggerFinder.getLoggerFinder().getClass().getName(),
|
||||
INTERNAL_LOGGER_FINDER_NAME);
|
||||
System.Logger testLogger = System.getLogger("jdk.event.security");
|
||||
assertEquals(testLogger.getClass().getName(), INTERNAL_LOGGER_NAME);
|
||||
}
|
||||
testComplete = true;
|
||||
|
||||
// LoggerFinder should be initialized, trigger a simple log call
|
||||
Security.setProperty("test_2", "test");
|
||||
}
|
||||
}
|
||||
|
||||
// helper to create the inner test. Run config variations with the LoggerFinder jars
|
||||
// on the classpath and with other threads running System.Logger calls during load
|
||||
private static void launchTest(boolean multiThreadLoad, boolean withCustomLoggerFinder) {
|
||||
List<String> cmds = new ArrayList<>();
|
||||
cmds.add(JDKToolFinder.getJDKTool("java"));
|
||||
cmds.addAll(asList(Utils.getTestJavaOpts()));
|
||||
if (withCustomLoggerFinder) {
|
||||
cmds.addAll(List.of("-classpath",
|
||||
System.getProperty("test.classes") + File.pathSeparator +
|
||||
jarPath1.toString() + File.pathSeparator + jarPath2.toString(),
|
||||
"-Dtest.classes=" + System.getProperty("test.classes")));
|
||||
} else {
|
||||
cmds.addAll(List.of("-classpath",
|
||||
System.getProperty("test.classes")));
|
||||
}
|
||||
cmds.addAll(List.of(
|
||||
// following debug property seems useful to tickle the issue
|
||||
"-Dsun.misc.URLClassPath.debug=true",
|
||||
// console logger level to capture event output
|
||||
"-Djdk.system.logger.level=DEBUG",
|
||||
// useful for debug purposes
|
||||
"-Djdk.logger.finder.error=DEBUG",
|
||||
// enable logging to verify correct output
|
||||
"-Djava.util.logging.config.file=" +
|
||||
Path.of(System.getProperty("test.src", "."), "logging.properties")));
|
||||
if (multiThreadLoad) {
|
||||
cmds.add("-DmultiThreadLoad=true");
|
||||
}
|
||||
if (withCustomLoggerFinder) {
|
||||
cmds.add("-DwithCustomLoggerFinder=true");
|
||||
}
|
||||
cmds.addAll(List.of(
|
||||
"SignedLoggerFinderTest",
|
||||
"no-init"));
|
||||
|
||||
try {
|
||||
OutputAnalyzer outputAnalyzer = ProcessTools.executeCommand(cmds.stream()
|
||||
.filter(t -> !t.isEmpty())
|
||||
.toArray(String[]::new))
|
||||
.shouldHaveExitValue(0);
|
||||
if (withCustomLoggerFinder) {
|
||||
outputAnalyzer
|
||||
.shouldContain("TEST LOGGER: [test_1, test]")
|
||||
.shouldContain("TEST LOGGER: [test_2, test]");
|
||||
} else {
|
||||
outputAnalyzer
|
||||
.shouldContain("SecurityPropertyModification: key:test_1")
|
||||
.shouldContain("SecurityPropertyModification: key:test_2");
|
||||
}
|
||||
if (withCustomLoggerFinder && signJars) {
|
||||
// X509 cert generated during verification of signed jar file
|
||||
outputAnalyzer
|
||||
.shouldContain(DNAME);
|
||||
}
|
||||
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException("Unexpected fail.", t);
|
||||
}
|
||||
}
|
||||
|
||||
private static Runnable runnableWithSleep(Supplier s, long sleep, String desc) {
|
||||
return () -> {
|
||||
while(!testComplete) {
|
||||
System.out.println(desc + s.get().getClass().getName());
|
||||
try {
|
||||
Thread.sleep(sleep);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void initialize() throws Throwable {
|
||||
if (signJars) {
|
||||
genKey();
|
||||
}
|
||||
|
||||
Path classes = Paths.get(System.getProperty("test.classes", ""));
|
||||
JarUtils.createJarFile(jarPath1,
|
||||
classes,
|
||||
classes.resolve("loggerfinder/SimpleLoggerFinder.class"),
|
||||
classes.resolve("loggerfinder/SimpleLoggerFinder$SimpleLogger.class"));
|
||||
|
||||
JarUtils.updateJarFile(jarPath1, Path.of(System.getProperty("test.src")),
|
||||
Path.of("META-INF", "services", "java.lang.System$LoggerFinder"));
|
||||
if (signJars) {
|
||||
signJar(jarPath1.toString());
|
||||
}
|
||||
// multiple signed jars with services to have ServiceLoader iteration
|
||||
Files.copy(jarPath1, jarPath2, REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
private static void genKey() throws Throwable {
|
||||
String keytool = JDKToolFinder.getJDKTool("keytool");
|
||||
Files.deleteIfExists(Paths.get(KEYSTORE));
|
||||
ProcessTools.executeCommand(keytool,
|
||||
"-J-Duser.language=en",
|
||||
"-J-Duser.country=US",
|
||||
"-genkey",
|
||||
"-keyalg", "rsa",
|
||||
"-alias", ALIAS,
|
||||
"-keystore", KEYSTORE,
|
||||
"-keypass", KEYPASS,
|
||||
"-dname", DNAME,
|
||||
"-storepass", STOREPASS
|
||||
).shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
|
||||
private static OutputAnalyzer signJar(String jarName) throws Throwable {
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add("-verbose");
|
||||
args.add(jarName);
|
||||
args.add(ALIAS);
|
||||
|
||||
return jarsigner(args);
|
||||
}
|
||||
|
||||
private static OutputAnalyzer jarsigner(List<String> extra)
|
||||
throws Throwable {
|
||||
JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jarsigner")
|
||||
.addVMArg("-Duser.language=en")
|
||||
.addVMArg("-Duser.country=US")
|
||||
.addToolArg("-keystore")
|
||||
.addToolArg(KEYSTORE)
|
||||
.addToolArg("-storepass")
|
||||
.addToolArg(STOREPASS)
|
||||
.addToolArg("-keypass")
|
||||
.addToolArg(KEYPASS);
|
||||
for (String s : extra) {
|
||||
if (s.startsWith("-J")) {
|
||||
launcher.addVMArg(s.substring(2));
|
||||
} else {
|
||||
launcher.addToolArg(s);
|
||||
}
|
||||
}
|
||||
return ProcessTools.executeCommand(launcher.getCommand());
|
||||
}
|
||||
|
||||
private static void assertEquals(String received, String expected) {
|
||||
if (!expected.equals(received)) {
|
||||
throw new RuntimeException("Received: " + received);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*
|
||||
* 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 loggerfinder;
|
||||
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
|
||||
public class SimpleLoggerFinder extends System.LoggerFinder {
|
||||
|
||||
static {
|
||||
try {
|
||||
long sleep = new Random().nextLong(1000L) + 1L;
|
||||
System.out.println("Logger finder service load sleep value: " + sleep);
|
||||
// simulate a slow load service
|
||||
Thread.sleep(sleep);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public System.Logger getLogger(String name, Module module) {
|
||||
return new SimpleLogger(name);
|
||||
}
|
||||
|
||||
private static class SimpleLogger implements System.Logger {
|
||||
private final String name;
|
||||
|
||||
public SimpleLogger(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoggable(Level level) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
|
||||
System.out.println("TEST LOGGER: " + msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
|
||||
System.out.println("TEST LOGGER: " + Arrays.asList(params));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
############################################################
|
||||
# Configuration file for log testing
|
||||
#
|
||||
############################################################
|
||||
|
||||
handlers= java.util.logging.ConsoleHandler
|
||||
|
||||
.level= FINE
|
||||
|
||||
java.util.logging.ConsoleHandler.level = FINE
|
||||
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
|
||||
|
||||
jdk.event.security.level = FINE
|
||||
|
Loading…
Reference in New Issue
Block a user