/*
 * Copyright (c) 2015, 2020, 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 jdk.internal.misc.InnocuousThread;
import jdk.internal.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
/** * 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 = InnocuousThread.newThread(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; if (ref != null && !ref.refersTo(null)) return true; synchronized (BootstrapExecutors.class) { ref = executorRef; return ref != null && !ref.refersTo(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 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.
Returns:The logger name.
/** * 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.
Returns:true if the VM is still bootstrapping.
/** * 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 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 { // SimpleConsoleLogger is used return LoggingBackend.NONE; } } }); } } // We will use a temporary SurrogateLogger if // the logging backend is JUL, there is no custom config, // and the LogManager has not been initialized yet. private static boolean useSurrogateLoggers() { // 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 || useSurrogateLoggers(); } // Called by LazyLoggerAccessor. This method will determine whether // to create a BootstrapLogger (if the VM is not yet booted), // 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); } else { if (useSurrogateLoggers()) { // JUL is the default backend, there is no custom configuration, // LogManager has not been used. synchronized(BootstrapLogger.class) { if (useSurrogateLoggers()) { return createSurrogateLogger(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 SurrogateLoggers, // which avoids the cost of actually loading up the LogManager... // The RedirectedLoggers class has the logic to create such surrogate // loggers, and to possibly replace them with real JUL loggers if // someone calls LogManager.getLogManager(). static final class RedirectedLoggers implements Function<LazyLoggerAccessor, SurrogateLogger> { // all accesses must be synchronized on the outer BootstrapLogger.class final Map<LazyLoggerAccessor, SurrogateLogger> redirectedLoggers = new HashMap<>(); // all accesses must be synchronized on the outer BootstrapLogger.class // The redirectLoggers map will be cleared when LogManager is initialized. boolean cleared; @Override // all accesses must be synchronized on the outer BootstrapLogger.class public SurrogateLogger apply(LazyLoggerAccessor t) { if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); return SurrogateLogger.makeSurrogateLogger(t.getLoggerName()); } // all accesses must be synchronized on the outer BootstrapLogger.class SurrogateLogger get(LazyLoggerAccessor a) { if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); return redirectedLoggers.computeIfAbsent(a, this); } // all accesses must be synchronized on the outer BootstrapLogger.class Map<LazyLoggerAccessor, SurrogateLogger> drainLoggersMap() { if (redirectedLoggers.isEmpty()) return null; if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); final Map<LazyLoggerAccessor, SurrogateLogger> accessors = new HashMap<>(redirectedLoggers); redirectedLoggers.clear(); cleared = true; return accessors; } static void replaceSurrogateLoggers(Map<LazyLoggerAccessor, SurrogateLogger> 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, SurrogateLogger> a : accessors.entrySet()) { a.getKey().release(a.getValue(), !lazy); } } // all accesses must be synchronized on the outer BootstrapLogger.class static final RedirectedLoggers INSTANCE = new RedirectedLoggers(); } static synchronized Logger createSurrogateLogger(LazyLoggerAccessor a) { // accesses to RedirectedLoggers is synchronized on BootstrapLogger.class return RedirectedLoggers.INSTANCE.get(a); } private static volatile boolean logManagerConfigured; private static synchronized Map<LazyLoggerAccessor, SurrogateLogger> releaseSurrogateLoggers() { // first check whether there's a chance that we have used // surrogate loggers; Will be false if logManagerConfigured is already // true. final boolean releaseSurrogateLoggers = useSurrogateLoggers(); // then sets the flag that tells that the log manager is configured logManagerConfigured = true; // finally retrieves all surrogate loggers that should be replaced // by real JUL loggers, and return them in the form of a redirected // loggers map. if (releaseSurrogateLoggers) { // accesses to RedirectedLoggers is synchronized on BootstrapLogger.class return RedirectedLoggers.INSTANCE.drainLoggersMap(); } else { return null; } } public static void redirectTemporaryLoggers() { // This call is synchronized on BootstrapLogger.class. final Map<LazyLoggerAccessor, SurrogateLogger> accessors = releaseSurrogateLoggers(); // We will now reset the logger accessors, triggering the // (possibly lazy) replacement of any temporary surrogate logger by the // real logger returned from the loaded LoggerFinder. if (accessors != null) { RedirectedLoggers.replaceSurrogateLoggers(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(); } }