blob: 813c4787e86cf3fafc0f050f3c46e3d98026e466 [file] [log] [blame]
/*
* Copyright (c) 2015, 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.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.function.BooleanSupplier;
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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import sun.misc.InnocuousThread;
import sun.misc.VM;
import sun.util.logging.PlatformLogger;
import jdk.internal.logger.LazyLoggers.LazyLoggerAccessor;
/**
* The BootstrapLogger class handles all the logic needed by Lazy Loggers
* to delay the creation of System.Logger instances until the VM is booted.
* By extension - it also contains the logic that will delay the creation
* of JUL Loggers until the LogManager is initialized by the application, in
* the common case where JUL is the default and there is no custom JUL
* configuration.
*
* A BootstrapLogger instance is both a Logger and a
* PlatformLogger.Bridge instance, which will put all Log messages in a queue
* until the VM is booted.
* Once the VM is booted, it obtain the real System.Logger instance from the
* LoggerFinder and flushes the message to the queue.
*
* There are a few caveat:
* - the queue may not be flush until the next message is logged after
* the VM is booted
* - while the BootstrapLogger is active, the default implementation
* for all convenience methods is used
* - PlatformLogger.setLevel calls are ignored
*
*
*/
public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
PlatformLogger.ConfigurableBridge {
// We use the BootstrapExecutors class to submit delayed messages
// to an independent InnocuousThread which will ensure that
// delayed log events will be clearly identified as messages that have
// been delayed during the boot sequence.
private static class BootstrapExecutors implements ThreadFactory {
// Maybe that should be made configurable with system properties.
static final long KEEP_EXECUTOR_ALIVE_SECONDS = 30;
// The BootstrapMessageLoggerTask is a Runnable which keeps
// a hard ref to the ExecutorService that owns it.
// This ensure that the ExecutorService is not gc'ed until the thread
// has stopped running.
private static class BootstrapMessageLoggerTask implements Runnable {
ExecutorService owner;
Runnable run;
public BootstrapMessageLoggerTask(ExecutorService owner, Runnable r) {
this.owner = owner;
this.run = r;
}
@Override
public void run() {
try {
run.run();
} finally {
owner = null; // allow the ExecutorService to be gced.
}
}
}
private static volatile WeakReference<ExecutorService> executorRef;
private static ExecutorService getExecutor() {
WeakReference<ExecutorService> ref = executorRef;
ExecutorService executor = ref == null ? null : ref.get();
if (executor != null) return executor;
synchronized (BootstrapExecutors.class) {
ref = executorRef;
executor = ref == null ? null : ref.get();
if (executor == null) {
executor = new ThreadPoolExecutor(0, 1,
KEEP_EXECUTOR_ALIVE_SECONDS, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new BootstrapExecutors());
}
// The executor service will be elligible for gc
// KEEP_EXECUTOR_ALIVE_SECONDS seconds (30s)
// after the execution of its last pending task.
executorRef = new WeakReference<>(executor);
return executorRef.get();
}
}
@Override
public Thread newThread(Runnable r) {
ExecutorService owner = getExecutor();
Thread thread = AccessController.doPrivileged(new PrivilegedAction<Thread>() {
@Override
public Thread run() {
Thread t = new InnocuousThread(new BootstrapMessageLoggerTask(owner, r));
t.setName("BootstrapMessageLoggerTask-"+t.getName());
return t;
}
}, null, new RuntimePermission("enableContextClassLoaderOverride"));
thread.setDaemon(true);
return thread;
}
static void submit(Runnable r) {
getExecutor().execute(r);
}
// This is used by tests.
static void join(Runnable r) {
try {
getExecutor().submit(r).get();
} catch (InterruptedException | ExecutionException ex) {
// should not happen
throw new RuntimeException(ex);
}
}
// This is used by tests.
static void awaitPendingTasks() {
WeakReference<ExecutorService> ref = executorRef;
ExecutorService executor = ref == null ? null : ref.get();
if (ref == null) {
synchronized(BootstrapExecutors.class) {
ref = executorRef;
executor = ref == null ? null : ref.get();
}
}
if (executor != null) {
// since our executor uses a FIFO and has a single thread
// then awaiting the execution of its pending tasks can be done
// simply by registering a new task and waiting until it
// completes. This of course would not work if we were using
// several threads, but we don't.
join(()->{});
}
}
// This is used by tests.
static boolean isAlive() {
WeakReference<ExecutorService> ref = executorRef;
ExecutorService executor = ref == null ? null : ref.get();
if (executor != null) return true;
synchronized (BootstrapExecutors.class) {
ref = executorRef;
executor = ref == null ? null : ref.get();
return executor != null;
}
}
// The pending log event queue. The first event is the head, and
// new events are added at the tail
static LogEvent head, tail;
static void enqueue(LogEvent event) {
if (event.next != null) return;
synchronized (BootstrapExecutors.class) {
if (event.next != null) return;
event.next = event;
if (tail == null) {
head = tail = event;
} else {
tail.next = event;
tail = event;
}
}
}
static void flush() {
LogEvent event;
// drain the whole queue
synchronized(BootstrapExecutors.class) {
event = head;
head = tail = null;
}
while(event != null) {
LogEvent.log(event);
synchronized(BootstrapExecutors.class) {
LogEvent prev = event;
event = (event.next == event ? null : event.next);
prev.next = null;
}
}
}
}
// The accessor in which this logger is temporarily set.
final LazyLoggerAccessor holder;
BootstrapLogger(LazyLoggerAccessor holder) {
this.holder = holder;
}
// Temporary data object storing log events
// It would be nice to use a Consumer<Logger> instead of a LogEvent.
// This way we could simply do things like:
// push((logger) -> logger.log(level, msg));
// Unfortunately, if we come to here it means we are in the bootsraping
// phase where using lambdas is not safe yet - so we have to use a
// a data object instead...
//
static final class LogEvent {
// only one of these two levels should be non null
final Level level;
final PlatformLogger.Level platformLevel;
final BootstrapLogger bootstrap;
final ResourceBundle bundle;
final String msg;
final Throwable thrown;
final Object[] params;
final Supplier<String> msgSupplier;
final String sourceClass;
final String sourceMethod;
final long timeMillis;
final long nanoAdjustment;
// because logging a message may entail calling toString() on
// the parameters etc... we need to store the context of the
// caller who logged the message - so that we can reuse it when
// we finally log the message.
final AccessControlContext acc;
// The next event in the queue
LogEvent next;
private LogEvent(BootstrapLogger bootstrap, Level level,
ResourceBundle bundle, String msg,
Throwable thrown, Object[] params) {
this.acc = AccessController.getContext();
this.timeMillis = System.currentTimeMillis();
this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
this.level = level;
this.platformLevel = null;
this.bundle = bundle;
this.msg = msg;
this.msgSupplier = null;
this.thrown = thrown;
this.params = params;
this.sourceClass = null;
this.sourceMethod = null;
this.bootstrap = bootstrap;
}
private LogEvent(BootstrapLogger bootstrap, Level level,
Supplier<String> msgSupplier,
Throwable thrown, Object[] params) {
this.acc = AccessController.getContext();
this.timeMillis = System.currentTimeMillis();
this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
this.level = level;
this.platformLevel = null;
this.bundle = null;
this.msg = null;
this.msgSupplier = msgSupplier;
this.thrown = thrown;
this.params = params;
this.sourceClass = null;
this.sourceMethod = null;
this.bootstrap = bootstrap;
}
private LogEvent(BootstrapLogger bootstrap,
PlatformLogger.Level platformLevel,
String sourceClass, String sourceMethod,
ResourceBundle bundle, String msg,
Throwable thrown, Object[] params) {
this.acc = AccessController.getContext();
this.timeMillis = System.currentTimeMillis();
this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
this.level = null;
this.platformLevel = platformLevel;
this.bundle = bundle;
this.msg = msg;
this.msgSupplier = null;
this.thrown = thrown;
this.params = params;
this.sourceClass = sourceClass;
this.sourceMethod = sourceMethod;
this.bootstrap = bootstrap;
}
private LogEvent(BootstrapLogger bootstrap,
PlatformLogger.Level platformLevel,
String sourceClass, String sourceMethod,
Supplier<String> msgSupplier,
Throwable thrown, Object[] params) {
this.acc = AccessController.getContext();
this.timeMillis = System.currentTimeMillis();
this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
this.level = null;
this.platformLevel = platformLevel;
this.bundle = null;
this.msg = null;
this.msgSupplier = msgSupplier;
this.thrown = thrown;
this.params = params;
this.sourceClass = sourceClass;
this.sourceMethod = sourceMethod;
this.bootstrap = bootstrap;
}
// Log this message in the given logger. Do not call directly.
// Use LogEvent.log(LogEvent, logger) instead.
private void log(Logger logger) {
assert platformLevel == null && level != null;
//new Exception("logging delayed message").printStackTrace();
if (msgSupplier != null) {
if (thrown != null) {
logger.log(level, msgSupplier, thrown);
} else {
logger.log(level, msgSupplier);
}
} else {
// BootstrapLoggers are never localized so we can safely
// use the method that takes a ResourceBundle parameter
// even when that resource bundle is null.
if (thrown != null) {
logger.log(level, bundle, msg, thrown);
} else {
logger.log(level, bundle, msg, params);
}
}
}
// Log this message in the given logger. Do not call directly.
// Use LogEvent.doLog(LogEvent, logger) instead.
private void log(PlatformLogger.Bridge logger) {
assert platformLevel != null && level == null;
if (sourceClass == null) {
if (msgSupplier != null) {
if (thrown != null) {
logger.log(platformLevel, thrown, msgSupplier);
} else {
logger.log(platformLevel, msgSupplier);
}
} else {
// BootstrapLoggers are never localized so we can safely
// use the method that takes a ResourceBundle parameter
// even when that resource bundle is null.
if (thrown != null) {
logger.logrb(platformLevel, bundle, msg, thrown);
} else {
logger.logrb(platformLevel, bundle, msg, params);
}
}
} else {
if (msgSupplier != null) {
if (thrown != null) {
logger.logp(platformLevel, sourceClass, sourceMethod, thrown, msgSupplier);
} else {
logger.logp(platformLevel, sourceClass, sourceMethod, msgSupplier);
}
} else {
// BootstrapLoggers are never localized so we can safely
// use the method that takes a ResourceBundle parameter
// even when that resource bundle is null.
if (thrown != null) {
logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, thrown);
} else {
logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, params);
}
}
}
}
// non default methods from Logger interface
static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
ResourceBundle bundle, String key, Throwable thrown) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level), bundle, key,
thrown, null);
}
static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
ResourceBundle bundle, String format, Object[] params) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level), bundle, format,
null, params);
}
static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
Supplier<String> msgSupplier, Throwable thrown) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level),
Objects.requireNonNull(msgSupplier), thrown, null);
}
static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
Supplier<String> msgSupplier) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level),
Objects.requireNonNull(msgSupplier), null, null);
}
static void log(LogEvent log, Logger logger) {
final SecurityManager sm = System.getSecurityManager();
// 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.
if (sm == null || log.acc == null) {
BootstrapExecutors.submit(() -> log.log(logger));
} else {
BootstrapExecutors.submit(() ->
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
log.log(logger); return null;
}, log.acc));
}
}
// non default methods from PlatformLogger.Bridge interface
static LogEvent valueOf(BootstrapLogger bootstrap,
PlatformLogger.Level level, String msg) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level), null, null, null,
msg, null, null);
}
static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
String msg, Throwable thrown) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level), null, null, null, msg, thrown, null);
}
static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
String msg, Object[] params) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level), null, null, null, msg, null, params);
}
static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
Supplier<String> msgSupplier) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level), null, null, msgSupplier, null, null);
}
static LogEvent vaueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
Supplier<String> msgSupplier,
Throwable thrown) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level), null, null,
msgSupplier, thrown, null);
}
static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
String sourceClass, String sourceMethod,
ResourceBundle bundle, String msg, Object[] params) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level), sourceClass,
sourceMethod, bundle, msg, null, params);
}
static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
String sourceClass, String sourceMethod,
ResourceBundle bundle, String msg, Throwable thrown) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level), sourceClass,
sourceMethod, bundle, msg, thrown, null);
}
static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
String sourceClass, String sourceMethod,
Supplier<String> msgSupplier, Throwable thrown) {
return new LogEvent(Objects.requireNonNull(bootstrap),
Objects.requireNonNull(level), sourceClass,
sourceMethod, msgSupplier, thrown, null);
}
static void log(LogEvent log, PlatformLogger.Bridge logger) {
final SecurityManager sm = System.getSecurityManager();
if (sm == null || log.acc == null) {
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);
}
}
static void log(LogEvent event) {
event.bootstrap.flush(event);
}
}
// Push a log event at the end of the pending LogEvent queue.
void push(LogEvent log) {
BootstrapExecutors.enqueue(log);
// if the queue has been flushed just before we entered
// the synchronized block we need to flush it again.
checkBootstrapping();
}
// Flushes the queue of pending LogEvents to the logger.
void flush(LogEvent event) {
assert event.bootstrap == this;
if (event.platformLevel != null) {
PlatformLogger.Bridge concrete = holder.getConcretePlatformLogger(this);
LogEvent.log(event, concrete);
} else {
Logger concrete = holder.getConcreteLogger(this);
LogEvent.log(event, concrete);
}
}
/**
* The name of this logger. This is the name of the actual logger for which
* this logger acts as a temporary proxy.
* @return The logger name.
*/
@Override
public String getName() {
return holder.name;
}
/**
* Check whether the VM is still bootstrapping, and if not, arranges
* for this logger's holder to create the real logger and flush the
* pending event queue.
* @return true if the VM is still bootstrapping.
*/
boolean checkBootstrapping() {
if (isBooted()) {
BootstrapExecutors.flush();
return false;
}
return true;
}
// ----------------------------------
// Methods from Logger
// ----------------------------------
@Override
public boolean isLoggable(Level level) {
if (checkBootstrapping()) {
return level.getSeverity() >= Level.INFO.getSeverity();
} else {
final Logger spi = holder.wrapped();
return spi.isLoggable(level);
}
}
@Override
public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, bundle, key, thrown));
} else {
final Logger spi = holder.wrapped();
spi.log(level, bundle, key, thrown);
}
}
@Override
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, bundle, format, params));
} else {
final Logger spi = holder.wrapped();
spi.log(level, bundle, format, params);
}
}
@Override
public void log(Level level, String msg, Throwable thrown) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, null, msg, thrown));
} else {
final Logger spi = holder.wrapped();
spi.log(level, msg, thrown);
}
}
@Override
public void log(Level level, String format, Object... params) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, null, format, params));
} else {
final Logger spi = holder.wrapped();
spi.log(level, format, params);
}
}
@Override
public void log(Level level, Supplier<String> msgSupplier) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, msgSupplier));
} else {
final Logger spi = holder.wrapped();
spi.log(level, msgSupplier);
}
}
@Override
public void log(Level level, Object obj) {
if (checkBootstrapping()) {
Logger.super.log(level, obj);
} else {
final Logger spi = holder.wrapped();
spi.log(level, obj);
}
}
@Override
public void log(Level level, String msg) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, null, msg, (Object[])null));
} else {
final Logger spi = holder.wrapped();
spi.log(level, msg);
}
}
@Override
public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, msgSupplier, thrown));
} else {
final Logger spi = holder.wrapped();
spi.log(level, msgSupplier, thrown);
}
}
// ----------------------------------
// Methods from PlatformLogger.Bridge
// ----------------------------------
@Override
public boolean isLoggable(PlatformLogger.Level level) {
if (checkBootstrapping()) {
return level.intValue() >= PlatformLogger.Level.INFO.intValue();
} else {
final PlatformLogger.Bridge spi = holder.platform();
return spi.isLoggable(level);
}
}
@Override
public boolean isEnabled() {
if (checkBootstrapping()) {
return true;
} else {
final PlatformLogger.Bridge spi = holder.platform();
return spi.isEnabled();
}
}
@Override
public void log(PlatformLogger.Level level, String msg) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, msg));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.log(level, msg);
}
}
@Override
public void log(PlatformLogger.Level level, String msg, Throwable thrown) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, msg, thrown));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.log(level, msg, thrown);
}
}
@Override
public void log(PlatformLogger.Level level, String msg, Object... params) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, msg, params));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.log(level, msg, params);
}
}
@Override
public void log(PlatformLogger.Level level, Supplier<String> msgSupplier) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, msgSupplier));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.log(level, msgSupplier);
}
}
@Override
public void log(PlatformLogger.Level level, Throwable thrown,
Supplier<String> msgSupplier) {
if (checkBootstrapping()) {
push(LogEvent.vaueOf(this, level, msgSupplier, thrown));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.log(level, thrown, msgSupplier);
}
}
@Override
public void logp(PlatformLogger.Level level, String sourceClass,
String sourceMethod, String msg) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null,
msg, (Object[])null));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.logp(level, sourceClass, sourceMethod, msg);
}
}
@Override
public void logp(PlatformLogger.Level level, String sourceClass,
String sourceMethod, Supplier<String> msgSupplier) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, null));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.logp(level, sourceClass, sourceMethod, msgSupplier);
}
}
@Override
public void logp(PlatformLogger.Level level, String sourceClass,
String sourceMethod, String msg, Object... params) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, params));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.logp(level, sourceClass, sourceMethod, msg, params);
}
}
@Override
public void logp(PlatformLogger.Level level, String sourceClass,
String sourceMethod, String msg, Throwable thrown) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, thrown));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.logp(level, sourceClass, sourceMethod, msg, thrown);
}
}
@Override
public void logp(PlatformLogger.Level level, String sourceClass,
String sourceMethod, Throwable thrown, Supplier<String> msgSupplier) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, thrown));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.logp(level, sourceClass, sourceMethod, thrown, msgSupplier);
}
}
@Override
public void logrb(PlatformLogger.Level level, String sourceClass,
String sourceMethod, ResourceBundle bundle, String msg, Object... params) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, params));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.logrb(level, sourceClass, sourceMethod, bundle, msg, params);
}
}
@Override
public void logrb(PlatformLogger.Level level, String sourceClass,
String sourceMethod, ResourceBundle bundle, String msg, Throwable thrown) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, thrown));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.logrb(level, sourceClass, sourceMethod, bundle, msg, thrown);
}
}
@Override
public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
String msg, Object... params) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, null, null, bundle, msg, params));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.logrb(level, bundle, msg, params);
}
}
@Override
public void logrb(PlatformLogger.Level level, ResourceBundle bundle, String msg, Throwable thrown) {
if (checkBootstrapping()) {
push(LogEvent.valueOf(this, level, null, null, bundle, msg, thrown));
} else {
final PlatformLogger.Bridge spi = holder.platform();
spi.logrb(level, bundle, msg, thrown);
}
}
@Override
public LoggerConfiguration getLoggerConfiguration() {
if (checkBootstrapping()) {
// This practically means that PlatformLogger.setLevel()
// calls will be ignored if the VM is still bootstrapping. We could
// attempt to fix that but is it worth it?
return PlatformLogger.ConfigurableBridge.super.getLoggerConfiguration();
} else {
final PlatformLogger.Bridge spi = holder.platform();
return PlatformLogger.ConfigurableBridge.getLoggerConfiguration(spi);
}
}
// This BooleanSupplier is a hook for tests - so that we can simulate
// what would happen before the VM is booted.
private static volatile BooleanSupplier isBooted;
public static boolean isBooted() {
if (isBooted != null) return isBooted.getAsBoolean();
else return VM.isBooted();
}
// A bit of black magic. We try to find out the nature of the logging
// backend without actually loading it.
private static enum LoggingBackend {
// There is no LoggerFinder and JUL is not present
NONE(true),
// There is no LoggerFinder, but we have found a
// JdkLoggerFinder installed (which means JUL is present),
// and we haven't found any custom configuration for JUL.
// Until LogManager is initialized we can use a simple console
// logger.
JUL_DEFAULT(false),
// Same as above, except that we have found a custom configuration
// for JUL. We cannot use the simple console logger in this case.
JUL_WITH_CONFIG(true),
// We have found a custom LoggerFinder.
CUSTOM(true);
final boolean useLoggerFinder;
private LoggingBackend(boolean useLoggerFinder) {
this.useLoggerFinder = useLoggerFinder;
}
};
// The purpose of this class is to delay the initialization of
// the detectedBackend field until it is actually read.
// We do not want this field to get initialized if VM.isBooted() is false.
private static final class DetectBackend {
static final LoggingBackend detectedBackend;
static {
detectedBackend = AccessController.doPrivileged(new PrivilegedAction<LoggingBackend>() {
@Override
public LoggingBackend run() {
final Iterator<LoggerFinder> iterator =
ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader())
.iterator();
if (iterator.hasNext()) {
return LoggingBackend.CUSTOM; // Custom Logger Provider is registered
}
// No custom logger provider: we will be using the default
// backend.
final Iterator<DefaultLoggerFinder> iterator2 =
ServiceLoader.loadInstalled(DefaultLoggerFinder.class)
.iterator();
if (iterator2.hasNext()) {
// LoggingProviderImpl is registered. The default
// implementation is java.util.logging
String cname = System.getProperty("java.util.logging.config.class");
String fname = System.getProperty("java.util.logging.config.file");
return (cname != null || fname != null)
? LoggingBackend.JUL_WITH_CONFIG
: LoggingBackend.JUL_DEFAULT;
} else {
// SimpleLogger is used
return LoggingBackend.NONE;
}
}
});
}
}
// We will use temporary SimpleConsoleLoggers if
// the logging backend is JUL, there is no custom config,
// and the LogManager has not been initialized yet.
private static boolean useTemporaryLoggers() {
// being paranoid: this should already have been checked
if (!isBooted()) return true;
return DetectBackend.detectedBackend == LoggingBackend.JUL_DEFAULT
&& !logManagerConfigured;
}
// We will use lazy loggers if:
// - the VM is not yet booted
// - 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
|| useTemporaryLoggers();
}
// Called by LazyLoggerAccessor. This method will determine whether
// to create a BootstrapLogger (if the VM is not yet booted),
// a SimpleConsoleLogger (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);
} else {
boolean temporary = useTemporaryLoggers();
if (temporary) {
// JUL is the default backend, there is no custom configuration,
// LogManager has not been used.
synchronized(BootstrapLogger.class) {
if (useTemporaryLoggers()) {
return makeTemporaryLogger(accessor);
}
}
}
// Already booted. Return the real logger.
return accessor.createLogger();
}
}
// If the backend is JUL, and there is no custom configuration, and
// nobody has attempted to call LogManager.getLogManager() yet, then
// we can temporarily substitute JUL Logger with SimpleConsoleLoggers,
// which avoids the cost of actually loading up the LogManager...
// The TemporaryLoggers class has the logic to create such temporary
// loggers, and to possibly replace them with real JUL loggers if
// someone calls LogManager.getLogManager().
static final class TemporaryLoggers implements
Function<LazyLoggerAccessor, SimpleConsoleLogger> {
// all accesses must be synchronized on the outer BootstrapLogger.class
final Map<LazyLoggerAccessor, SimpleConsoleLogger> temporaryLoggers =
new HashMap<>();
// all accesses must be synchronized on the outer BootstrapLogger.class
// The temporaryLoggers map will be cleared when LogManager is initialized.
boolean cleared;
@Override
// all accesses must be synchronized on the outer BootstrapLogger.class
public SimpleConsoleLogger apply(LazyLoggerAccessor t) {
if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
return SimpleConsoleLogger.makeSimpleLogger(t.getLoggerName(), true);
}
// all accesses must be synchronized on the outer BootstrapLogger.class
SimpleConsoleLogger get(LazyLoggerAccessor a) {
if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
return temporaryLoggers.computeIfAbsent(a, this);
}
// all accesses must be synchronized on the outer BootstrapLogger.class
Map<LazyLoggerAccessor, SimpleConsoleLogger> drainTemporaryLoggers() {
if (temporaryLoggers.isEmpty()) return null;
if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
final Map<LazyLoggerAccessor, SimpleConsoleLogger> accessors = new HashMap<>(temporaryLoggers);
temporaryLoggers.clear();
cleared = true;
return accessors;
}
static void resetTemporaryLoggers(Map<LazyLoggerAccessor, SimpleConsoleLogger> accessors) {
// When the backend is JUL we want to force the creation of
// JUL loggers here: some tests are expecting that the
// PlatformLogger will create JUL loggers as soon as the
// LogManager is initialized.
//
// If the backend is not JUL then we can delay the re-creation
// of the wrapped logger until they are next accessed.
//
final LoggingBackend detectedBackend = DetectBackend.detectedBackend;
final boolean lazy = detectedBackend != LoggingBackend.JUL_DEFAULT
&& detectedBackend != LoggingBackend.JUL_WITH_CONFIG;
for (Map.Entry<LazyLoggerAccessor, SimpleConsoleLogger> a : accessors.entrySet()) {
a.getKey().release(a.getValue(), !lazy);
}
}
// all accesses must be synchronized on the outer BootstrapLogger.class
static final TemporaryLoggers INSTANCE = new TemporaryLoggers();
}
static synchronized Logger makeTemporaryLogger(LazyLoggerAccessor a) {
// accesses to TemporaryLoggers is synchronized on BootstrapLogger.class
return TemporaryLoggers.INSTANCE.get(a);
}
private static volatile boolean logManagerConfigured;
private static synchronized Map<LazyLoggerAccessor, SimpleConsoleLogger>
releaseTemporaryLoggers() {
// first check whether there's a chance that we have used
// temporary loggers; Will be false if logManagerConfigured is already
// true.
final boolean clearTemporaryLoggers = useTemporaryLoggers();
// then sets the flag that tells that the log manager is configured
logManagerConfigured = true;
// finally replace all temporary loggers by real JUL loggers
if (clearTemporaryLoggers) {
// accesses to TemporaryLoggers is synchronized on BootstrapLogger.class
return TemporaryLoggers.INSTANCE.drainTemporaryLoggers();
} else {
return null;
}
}
public static void redirectTemporaryLoggers() {
// This call is synchronized on BootstrapLogger.class.
final Map<LazyLoggerAccessor, SimpleConsoleLogger> accessors =
releaseTemporaryLoggers();
// We will now reset the logger accessors, triggering the
// (possibly lazy) replacement of any temporary logger by the
// real logger returned from the loaded LoggerFinder.
if (accessors != null) {
TemporaryLoggers.resetTemporaryLoggers(accessors);
}
BootstrapExecutors.flush();
}
// Hook for tests which need to wait until pending messages
// are processed.
static void awaitPendingTasks() {
BootstrapExecutors.awaitPendingTasks();
}
static boolean isAlive() {
return BootstrapExecutors.isAlive();
}
}