changeset 13961:eeea9b77edec

8150840: Add an internal system property to control the default level of System.Logger when java.logging is not present. Reviewed-by: mchung, rriggs
author dfuchs
date Fri, 25 Mar 2016 17:12:18 +0100
parents aac56691c2c4
children 00d704eff42f
files src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java src/java.base/share/classes/jdk/internal/logger/SurrogateLogger.java src/java.logging/share/classes/java/util/logging/LogRecord.java src/java.logging/share/classes/java/util/logging/SimpleFormatter.java test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/LoggerFinderLoaderTest.java test/java/lang/System/LoggerFinder/internal/SimpleConsoleLoggerTest/SimpleConsoleLoggerTest.java
diffstat 7 files changed, 774 insertions(+), 107 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java	Fri Mar 25 19:46:48 2016 +0800
+++ b/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java	Fri Mar 25 17:12:18 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016, 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
@@ -904,7 +904,7 @@
                                 ? LoggingBackend.JUL_WITH_CONFIG
                                 : LoggingBackend.JUL_DEFAULT;
                         } else {
-                            // SimpleLogger is used
+                            // SimpleConsoleLogger is used
                             return LoggingBackend.NONE;
                         }
                     }
@@ -913,10 +913,10 @@
         }
     }
 
-    // We will use temporary SimpleConsoleLoggers if
+    // 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 useTemporaryLoggers() {
+    private static  boolean useSurrogateLoggers() {
         // being paranoid: this should already have been checked
         if (!isBooted()) return true;
         return DetectBackend.detectedBackend == LoggingBackend.JUL_DEFAULT
@@ -931,25 +931,24 @@
     public static synchronized boolean useLazyLoggers() {
         return !BootstrapLogger.isBooted()
                 || DetectBackend.detectedBackend == LoggingBackend.CUSTOM
-                || useTemporaryLoggers();
+                || useSurrogateLoggers();
     }
 
     // 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
+    // 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 {
-            boolean temporary = useTemporaryLoggers();
-            if (temporary) {
+            if (useSurrogateLoggers()) {
                 // JUL is the default backend, there is no custom configuration,
                 // LogManager has not been used.
                 synchronized(BootstrapLogger.class) {
-                    if (useTemporaryLoggers()) {
-                        return makeTemporaryLogger(accessor);
+                    if (useSurrogateLoggers()) {
+                        return createSurrogateLogger(accessor);
                     }
                 }
             }
@@ -961,46 +960,46 @@
 
     // 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,
+    // we can temporarily substitute JUL Logger with SurrogateLoggers,
     // which avoids the cost of actually loading up the LogManager...
-    // The TemporaryLoggers class has the logic to create such temporary
+    // 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 TemporaryLoggers implements
-            Function<LazyLoggerAccessor, SimpleConsoleLogger> {
+    static final class RedirectedLoggers implements
+            Function<LazyLoggerAccessor, SurrogateLogger> {
 
         // all accesses must be synchronized on the outer BootstrapLogger.class
-        final Map<LazyLoggerAccessor, SimpleConsoleLogger> temporaryLoggers =
+        final Map<LazyLoggerAccessor, SurrogateLogger> redirectedLoggers =
                 new HashMap<>();
 
         // all accesses must be synchronized on the outer BootstrapLogger.class
-        // The temporaryLoggers map will be cleared when LogManager is initialized.
+        // The redirectLoggers 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) {
+        public SurrogateLogger apply(LazyLoggerAccessor t) {
             if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
-            return SimpleConsoleLogger.makeSimpleLogger(t.getLoggerName(), true);
+            return SurrogateLogger.makeSurrogateLogger(t.getLoggerName());
         }
 
         // all accesses must be synchronized on the outer BootstrapLogger.class
-        SimpleConsoleLogger get(LazyLoggerAccessor a) {
+        SurrogateLogger get(LazyLoggerAccessor a) {
             if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
-            return temporaryLoggers.computeIfAbsent(a, this);
+            return redirectedLoggers.computeIfAbsent(a, this);
         }
 
         // all accesses must be synchronized on the outer BootstrapLogger.class
-        Map<LazyLoggerAccessor, SimpleConsoleLogger> drainTemporaryLoggers() {
-            if (temporaryLoggers.isEmpty()) return null;
+        Map<LazyLoggerAccessor, SurrogateLogger> drainLoggersMap() {
+            if (redirectedLoggers.isEmpty()) return null;
             if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
-            final Map<LazyLoggerAccessor, SimpleConsoleLogger> accessors = new HashMap<>(temporaryLoggers);
-            temporaryLoggers.clear();
+            final Map<LazyLoggerAccessor, SurrogateLogger> accessors = new HashMap<>(redirectedLoggers);
+            redirectedLoggers.clear();
             cleared = true;
             return accessors;
         }
 
-        static void resetTemporaryLoggers(Map<LazyLoggerAccessor, SimpleConsoleLogger> 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
@@ -1012,36 +1011,38 @@
             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()) {
+            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 TemporaryLoggers INSTANCE = new TemporaryLoggers();
+        static final RedirectedLoggers INSTANCE = new RedirectedLoggers();
     }
 
-    static synchronized Logger makeTemporaryLogger(LazyLoggerAccessor a) {
-        // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class
-        return TemporaryLoggers.INSTANCE.get(a);
+    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, SimpleConsoleLogger>
-         releaseTemporaryLoggers() {
+    private static synchronized Map<LazyLoggerAccessor, SurrogateLogger>
+         releaseSurrogateLoggers() {
         // first check whether there's a chance that we have used
-        // temporary loggers; Will be false if logManagerConfigured is already
+        // surrogate loggers; Will be false if logManagerConfigured is already
         // true.
-        final boolean clearTemporaryLoggers = useTemporaryLoggers();
+        final boolean releaseSurrogateLoggers = useSurrogateLoggers();
 
         // 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();
+        // 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;
         }
@@ -1049,14 +1050,14 @@
 
     public static void redirectTemporaryLoggers() {
         // This call is synchronized on BootstrapLogger.class.
-        final Map<LazyLoggerAccessor, SimpleConsoleLogger> accessors =
-                releaseTemporaryLoggers();
+        final Map<LazyLoggerAccessor, SurrogateLogger> accessors =
+                releaseSurrogateLoggers();
 
         // We will now reset the logger accessors, triggering the
-        // (possibly lazy) replacement of any temporary logger by the
+        // (possibly lazy) replacement of any temporary surrogate logger by the
         // real logger returned from the loaded LoggerFinder.
         if (accessors != null) {
-            TemporaryLoggers.resetTemporaryLoggers(accessors);
+            RedirectedLoggers.replaceSurrogateLoggers(accessors);
         }
 
         BootstrapExecutors.flush();
--- a/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java	Fri Mar 25 19:46:48 2016 +0800
+++ b/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java	Fri Mar 25 17:12:18 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016, 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,6 +38,7 @@
 import java.lang.System.Logger;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
+import sun.security.action.GetPropertyAction;
 import sun.util.logging.PlatformLogger;
 import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration;
 
@@ -49,7 +50,19 @@
 public class SimpleConsoleLogger extends LoggerConfiguration
     implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge {
 
-    static final PlatformLogger.Level DEFAULT_LEVEL = PlatformLogger.Level.INFO;
+    static final Level DEFAULT_LEVEL = getDefaultLevel();
+    static final PlatformLogger.Level DEFAULT_PLATFORM_LEVEL =
+            PlatformLogger.toPlatformLevel(DEFAULT_LEVEL);
+
+    static Level getDefaultLevel() {
+        String levelName = AccessController.doPrivileged(
+                new GetPropertyAction("jdk.system.logger.level", "INFO"));
+        try {
+            return Level.valueOf(levelName);
+        } catch (IllegalArgumentException iae) {
+            return Level.INFO;
+        }
+    }
 
     final String name;
     volatile PlatformLogger.Level  level;
@@ -59,8 +72,16 @@
         this.usePlatformLevel = usePlatformLevel;
     }
 
+    String getSimpleFormatString() {
+        return Formatting.SIMPLE_CONSOLE_LOGGER_FORMAT;
+    }
+
+    PlatformLogger.Level defaultPlatformLevel() {
+        return DEFAULT_PLATFORM_LEVEL;
+    }
+
     @Override
-    public String getName() {
+    public final String getName() {
         return name;
     }
 
@@ -77,12 +98,12 @@
     // ---------------------------------------------------
 
     @Override
-    public boolean isLoggable(Level level) {
+    public final boolean isLoggable(Level level) {
         return isLoggable(PlatformLogger.toPlatformLevel(level));
     }
 
     @Override
-    public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+    public final void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
         if (isLoggable(level)) {
             if (bundle != null) {
                 key = bundle.getString(key);
@@ -92,7 +113,7 @@
     }
 
     @Override
-    public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+    public final void log(Level level, ResourceBundle bundle, String format, Object... params) {
         if (isLoggable(level)) {
             if (bundle != null) {
                 format = bundle.getString(format);
@@ -106,55 +127,55 @@
     // ---------------------------------------------------
 
     @Override
-    public boolean isLoggable(PlatformLogger.Level level) {
+    public final boolean isLoggable(PlatformLogger.Level level) {
         final PlatformLogger.Level effectiveLevel =  effectiveLevel();
         return level != PlatformLogger.Level.OFF
                 && level.ordinal() >= effectiveLevel.ordinal();
     }
 
     @Override
-    public boolean isEnabled() {
+    public final boolean isEnabled() {
         return level != PlatformLogger.Level.OFF;
     }
 
     @Override
-    public void log(PlatformLogger.Level level, String msg) {
+    public final void log(PlatformLogger.Level level, String msg) {
         if (isLoggable(level)) {
             publish(getCallerInfo(), logLevel(level), msg);
         }
     }
 
     @Override
-    public void log(PlatformLogger.Level level, String msg, Throwable thrown) {
+    public final void log(PlatformLogger.Level level, String msg, Throwable thrown) {
         if (isLoggable(level)) {
             publish(getCallerInfo(), logLevel(level), msg, thrown);
         }
     }
 
     @Override
-    public void log(PlatformLogger.Level level, String msg, Object... params) {
+    public final void log(PlatformLogger.Level level, String msg, Object... params) {
         if (isLoggable(level)) {
             publish(getCallerInfo(), logLevel(level), msg, params);
         }
     }
 
     private PlatformLogger.Level effectiveLevel() {
-        if (level == null) return DEFAULT_LEVEL;
+        if (level == null) return defaultPlatformLevel();
         return level;
     }
 
     @Override
-    public PlatformLogger.Level getPlatformLevel() {
+    public final PlatformLogger.Level getPlatformLevel() {
         return level;
     }
 
     @Override
-    public void setPlatformLevel(PlatformLogger.Level newLevel) {
+    public final void setPlatformLevel(PlatformLogger.Level newLevel) {
         level = newLevel;
     }
 
     @Override
-    public LoggerConfiguration getLoggerConfiguration() {
+    public final LoggerConfiguration getLoggerConfiguration() {
         return this;
     }
 
@@ -222,7 +243,7 @@
             }
             // Continue walking until we've found the relevant calling frame.
             // Skips logging/logger infrastructure.
-            return !isFilteredFrame(t);
+            return !Formatting.isFilteredFrame(t);
         }
 
         private boolean isLoggerImplFrame(String cname) {
@@ -256,7 +277,7 @@
         ZonedDateTime zdt = ZonedDateTime.now();
         String throwable = toString(thrown);
 
-        return String.format(Formatting.formatString,
+        return String.format(getSimpleFormatString(),
                          zdt,
                          callerInfo,
                          name,
@@ -280,31 +301,19 @@
         outputStream().print(format(level, msg, null, callerInfo));
     }
 
-    public static SimpleConsoleLogger makeSimpleLogger(String name, boolean usePlatformLevel) {
-        return new SimpleConsoleLogger(name, usePlatformLevel);
-    }
-
     public static SimpleConsoleLogger makeSimpleLogger(String name) {
         return new SimpleConsoleLogger(name, false);
     }
 
-    public static String getSimpleFormat(Function<String, String> defaultPropertyGetter) {
-        return Formatting.getSimpleFormat(defaultPropertyGetter);
-    }
-
-    public static boolean isFilteredFrame(StackFrame st) {
-        return Formatting.isFilteredFrame(st);
-    }
-
     @Override
-    public void log(PlatformLogger.Level level, Supplier<String> msgSupplier) {
+    public final void log(PlatformLogger.Level level, Supplier<String> msgSupplier) {
         if (isLoggable(level)) {
             publish(getCallerInfo(), logLevel(level), msgSupplier.get());
         }
     }
 
     @Override
-    public void log(PlatformLogger.Level level, Throwable thrown,
+    public final void log(PlatformLogger.Level level, Throwable thrown,
             Supplier<String> msgSupplier) {
         if (isLoggable(level)) {
             publish(getCallerInfo(), logLevel(level), msgSupplier.get(), thrown);
@@ -312,7 +321,7 @@
     }
 
     @Override
-    public void logp(PlatformLogger.Level level, String sourceClass,
+    public final void logp(PlatformLogger.Level level, String sourceClass,
             String sourceMethod, String msg) {
         if (isLoggable(level)) {
             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg);
@@ -320,7 +329,7 @@
     }
 
     @Override
-    public void logp(PlatformLogger.Level level, String sourceClass,
+    public final void logp(PlatformLogger.Level level, String sourceClass,
             String sourceMethod, Supplier<String> msgSupplier) {
         if (isLoggable(level)) {
             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get());
@@ -328,7 +337,7 @@
     }
 
     @Override
-    public void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod,
+    public final void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod,
             String msg, Object... params) {
         if (isLoggable(level)) {
             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params);
@@ -336,7 +345,7 @@
     }
 
     @Override
-    public void logp(PlatformLogger.Level level, String sourceClass,
+    public final void logp(PlatformLogger.Level level, String sourceClass,
             String sourceMethod, String msg, Throwable thrown) {
         if (isLoggable(level)) {
             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown);
@@ -344,7 +353,7 @@
     }
 
     @Override
-    public void logp(PlatformLogger.Level level, String sourceClass,
+    public final void logp(PlatformLogger.Level level, String sourceClass,
             String sourceMethod, Throwable thrown, Supplier<String> msgSupplier) {
         if (isLoggable(level)) {
             publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get(), thrown);
@@ -352,7 +361,7 @@
     }
 
     @Override
-    public void logrb(PlatformLogger.Level level, String sourceClass,
+    public final void logrb(PlatformLogger.Level level, String sourceClass,
             String sourceMethod, ResourceBundle bundle, String key, Object... params) {
         if (isLoggable(level)) {
             String msg = bundle == null ? key : bundle.getString(key);
@@ -361,7 +370,7 @@
     }
 
     @Override
-    public void logrb(PlatformLogger.Level level, String sourceClass,
+    public final void logrb(PlatformLogger.Level level, String sourceClass,
             String sourceMethod, ResourceBundle bundle, String key, Throwable thrown) {
         if (isLoggable(level)) {
             String msg = bundle == null ? key : bundle.getString(key);
@@ -370,7 +379,7 @@
     }
 
     @Override
-    public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+    public final void logrb(PlatformLogger.Level level, ResourceBundle bundle,
             String key, Object... params) {
         if (isLoggable(level)) {
             String msg = bundle == null ? key : bundle.getString(key);
@@ -379,7 +388,7 @@
     }
 
     @Override
-    public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+    public final void logrb(PlatformLogger.Level level, ResourceBundle bundle,
             String key, Throwable thrown) {
         if (isLoggable(level)) {
             String msg = bundle == null ? key : bundle.getString(key);
@@ -387,21 +396,38 @@
         }
     }
 
-    private static final class Formatting {
+    static final class Formatting {
+        // The default simple log format string.
+        // Used both by SimpleConsoleLogger when java.logging is not present,
+        // and by SurrogateLogger and java.util.logging.SimpleFormatter when
+        // java.logging is present.
         static final String DEFAULT_FORMAT =
             "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
-        static final String FORMAT_PROP_KEY =
+
+        // The system property key that allows to change the default log format
+        // when java.logging is not present. This is used to control the formatting
+        // of the SimpleConsoleLogger.
+        static final String DEFAULT_FORMAT_PROP_KEY =
+            "jdk.system.logger.format";
+
+        // The system property key that allows to change the default log format
+        // when java.logging is present. This is used to control the formatting
+        // of the SurrogateLogger (used before java.util.logging.LogManager is
+        // initialized) and the java.util.logging.SimpleFormatter (used after
+        // java.util.logging.LogManager is  initialized).
+        static final String JUL_FORMAT_PROP_KEY =
             "java.util.logging.SimpleFormatter.format";
-        static final String formatString = getSimpleFormat(null);
+
+        // The simple console logger format string
+        static final String SIMPLE_CONSOLE_LOGGER_FORMAT =
+                getSimpleFormat(DEFAULT_FORMAT_PROP_KEY, null);
 
         // Make it easier to wrap Logger...
         static private final String[] skips;
         static {
             String additionalPkgs = AccessController.doPrivileged(
-                (PrivilegedAction<String>)
-                () -> System.getProperty("jdk.logger.packages"));
+                new GetPropertyAction("jdk.logger.packages"));
             skips = additionalPkgs == null ? new String[0] : additionalPkgs.split(",");
-
         }
 
         static boolean isFilteredFrame(StackFrame st) {
@@ -440,21 +466,29 @@
             return false;
         }
 
-        static String getSimpleFormat(Function<String, String> defaultPropertyGetter) {
-            // Using a lambda here causes
+        static String getSimpleFormat(String key, Function<String, String> defaultPropertyGetter) {
+            // Double check that 'key' is one of the expected property names:
+            // - DEFAULT_FORMAT_PROP_KEY is used to control the
+            //   SimpleConsoleLogger format when java.logging is
+            //   not present.
+            // - JUL_FORMAT_PROP_KEY is used when this method is called
+            //   from the SurrogateLogger subclass. It is used to control the
+            //   SurrogateLogger format and java.util.logging.SimpleFormatter
+            //   format when java.logging is present.
+            // This method should not be called with any other key.
+            if (!DEFAULT_FORMAT_PROP_KEY.equals(key)
+                    && !JUL_FORMAT_PROP_KEY.equals(key)) {
+                throw new IllegalArgumentException("Invalid property name: " + key);
+            }
+
+            // Do not use any lambda in this method. Using a lambda here causes
             //    jdk/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java
             // to fail - because that test has a testcase which somehow references
-            // PlatformLogger and counts the number of generated lambda classes
-            // So we explicitely use new PrivilegedAction<String> here.
-            String format =
-                AccessController.doPrivileged(new PrivilegedAction<String>() {
-                @Override
-                public String run() {
-                    return System.getProperty(FORMAT_PROP_KEY);
-                }
-            });
+            // PlatformLogger and counts the number of generated lambda classes.
+            String format = AccessController.doPrivileged(new GetPropertyAction(key));
+
             if (format == null && defaultPropertyGetter != null) {
-                format = defaultPropertyGetter.apply(FORMAT_PROP_KEY);
+                format = defaultPropertyGetter.apply(key);
             }
             if (format != null) {
                 try {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/logger/SurrogateLogger.java	Fri Mar 25 17:12:18 2016 +0100
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2016, 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.util.function.Function;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * A simple console logger used to emulate the behavior of JUL loggers when
+ * java.util.logging has no custom configuration.
+ * Surrogate loggers are usually only used temporarily, until the LogManager
+ * is initialized. At this point, the surrogates are replaced by an actual
+ * logger obtained from LogManager.
+ */
+public final class SurrogateLogger extends SimpleConsoleLogger {
+
+    private static final PlatformLogger.Level JUL_DEFAULT_LEVEL =
+            PlatformLogger.Level.INFO;
+    private static volatile String simpleFormatString;
+
+    SurrogateLogger(String name) {
+        super(name, true);
+    }
+
+    @Override
+    PlatformLogger.Level defaultPlatformLevel() {
+        return JUL_DEFAULT_LEVEL;
+    }
+
+    @Override
+    String getSimpleFormatString() {
+        if (simpleFormatString == null) {
+            simpleFormatString = getSimpleFormat(null);
+        }
+        return simpleFormatString;
+    }
+
+    public static String getSimpleFormat(Function<String, String> defaultPropertyGetter) {
+        return Formatting.getSimpleFormat(Formatting.JUL_FORMAT_PROP_KEY, defaultPropertyGetter);
+    }
+
+    public static SurrogateLogger makeSurrogateLogger(String name) {
+        return new SurrogateLogger(name);
+    }
+
+    public static boolean isFilteredFrame(StackWalker.StackFrame st) {
+        return Formatting.isFilteredFrame(st);
+    }
+}
--- a/src/java.logging/share/classes/java/util/logging/LogRecord.java	Fri Mar 25 19:46:48 2016 +0800
+++ b/src/java.logging/share/classes/java/util/logging/LogRecord.java	Fri Mar 25 17:12:18 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, 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
@@ -33,8 +33,7 @@
 import java.security.PrivilegedAction;
 import java.time.Clock;
 import java.util.function.Predicate;
-
-import static jdk.internal.logger.SimpleConsoleLogger.isFilteredFrame;
+import static jdk.internal.logger.SurrogateLogger.isFilteredFrame;
 
 /**
  * LogRecord objects are used to pass logging requests between
--- a/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java	Fri Mar 25 19:46:48 2016 +0800
+++ b/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java	Fri Mar 25 17:12:18 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, 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
@@ -29,7 +29,7 @@
 import java.io.*;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
-import jdk.internal.logger.SimpleConsoleLogger;
+import jdk.internal.logger.SurrogateLogger;
 
 /**
  * Print a brief summary of the {@code LogRecord} in a human readable
@@ -64,7 +64,7 @@
     }
 
     private final String format =
-        SimpleConsoleLogger.getSimpleFormat(SimpleFormatter::getLoggingProperty);
+        SurrogateLogger.getSimpleFormat(SimpleFormatter::getLoggingProperty);
 
     /**
      * Format the given LogRecord.
--- a/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/LoggerFinderLoaderTest.java	Fri Mar 25 19:46:48 2016 +0800
+++ b/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/LoggerFinderLoaderTest.java	Fri Mar 25 17:12:18 2016 +0100
@@ -181,7 +181,7 @@
         }
 
         System.Logger createSimpleLogger(String name) {
-            PrivilegedAction<System.Logger> pa = () -> SimpleConsoleLogger.makeSimpleLogger(name, false);
+            PrivilegedAction<System.Logger> pa = () -> SimpleConsoleLogger.makeSimpleLogger(name);
             return AccessController.doPrivileged(pa);
         }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/System/LoggerFinder/internal/SimpleConsoleLoggerTest/SimpleConsoleLoggerTest.java	Fri Mar 25 17:12:18 2016 +0100
@@ -0,0 +1,561 @@
+/*
+ * Copyright (c) 2016, 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.
+ */
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.ResourceBundle;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import jdk.internal.logger.SimpleConsoleLogger;
+import jdk.internal.logger.SurrogateLogger;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary JDK implementation specific unit test for SimpleConsoleLogger.
+ *          Tests the behavior of SimpleConsoleLogger.
+ * @modules java.base/sun.util.logging
+ *          java.base/jdk.internal.logger
+ * @build SimpleConsoleLoggerTest
+ * @run  main/othervm SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=OFF SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=ERROR SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=WARNING SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=INFO SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=DEBUG SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=TRACE SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=ALL SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=WOMBAT SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=FINEST SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=DEBUG -Djava.util.logging.SimpleFormatter.format=++++_%2$s%n%4$s:_%5$s%6$s%n SimpleConsoleLoggerTest
+ * @run  main/othervm -Djdk.system.logger.level=DEBUG -Djdk.system.logger.format=++++_%2$s%n%4$s:_%5$s%6$s%n SimpleConsoleLoggerTest
+ *
+ * @author danielfuchs
+ */
+public class SimpleConsoleLoggerTest {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    final static boolean VERBOSE = false;
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k.toUpperCase(Locale.ROOT) + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+
+    static class ErrorStream extends PrintStream {
+
+        static AtomicBoolean forward = new AtomicBoolean();
+        ByteArrayOutputStream out;
+        String saved = "";
+        public ErrorStream(ByteArrayOutputStream out) {
+            super(out);
+            this.out = out;
+        }
+
+        @Override
+        public void write(int b) {
+            super.write(b);
+            if (forward.get()) err.write(b);
+        }
+
+        @Override
+        public void write(byte[] b) throws IOException {
+            super.write(b);
+            if (forward.get()) err.write(b);
+        }
+
+        @Override
+        public void write(byte[] buf, int off, int len) {
+            super.write(buf, off, len);
+            if (forward.get()) err.write(buf, off, len);
+        }
+
+        public String peek() {
+            flush();
+            return out.toString();
+        }
+
+        public String drain() {
+            flush();
+            String res = out.toString();
+            out.reset();
+            return res;
+        }
+
+        public void store() {
+            flush();
+            saved = out.toString();
+            out.reset();
+        }
+
+        public void restore() {
+            out.reset();
+            try {
+                out.write(saved.getBytes());
+            } catch(IOException io) {
+                throw new UncheckedIOException(io);
+            }
+        }
+
+        static final PrintStream err = System.err;
+        static final ErrorStream errorStream = new ErrorStream(new ByteArrayOutputStream());
+    }
+
+    private static StringBuilder appendProperty(StringBuilder b, String name) {
+        String value = System.getProperty(name);
+        if (value == null) return b;
+        return b.append(name).append("=").append(value).append('\n');
+    }
+
+    public static void main(String[] args) {
+        Locale.setDefault(Locale.ENGLISH);
+        System.setErr(ErrorStream.errorStream);
+        try {
+            test(args);
+        } finally {
+            try {
+                System.setErr(ErrorStream.err);
+            } catch (Error | RuntimeException x) {
+                x.printStackTrace(ErrorStream.err);
+            }
+        }
+    }
+
+
+    public static void test(String[] args) {
+
+        ErrorStream.errorStream.restore();
+        String l = System.getProperty("jdk.system.logger.level");
+        String f = System.getProperty("jdk.system.logger.format");
+        String jf = System.getProperty("java.util.logging.SimpleFormatter.format");
+        System.out.println("Running test: "
+            + "\n\tjdk.system.logger.level=\"" + l + "\""
+            + "\n\tjdk.system.logger.format=\"" + f + "\""
+            + "\n\tjava.util.logging.SimpleFormatter.format=\"" + jf + "\"");
+
+        test(l,f,jf);
+        System.out.println("\nPASSED: tested " + SEQUENCER.get() + " test cases");
+    }
+
+    static final AtomicLong SEQUENCER = new AtomicLong();
+    public static void test(String defaultLevel, String defaultFormat, String julFormat) {
+
+        final Map<Logger, String> loggerDescMap = new HashMap<>();
+
+        SimpleConsoleLogger simple = SimpleConsoleLogger.makeSimpleLogger("test.logger");
+        loggerDescMap.put(simple, "SimpleConsoleLogger.makeSimpleLogger(\"test.logger\")");
+        SimpleConsoleLogger temporary = SurrogateLogger.makeSurrogateLogger("test.logger");
+        loggerDescMap.put(temporary, "SurrogateLogger.makeSimpleLogger(\"test.logger\")");
+
+        Level level;
+        try {
+            level = defaultLevel == null ? null : Level.valueOf(defaultLevel);
+        } catch (IllegalArgumentException ex) {
+            level = null;
+        }
+        testLogger(loggerDescMap, simple, level, false, defaultFormat);
+        testLogger(loggerDescMap, temporary, null, true, julFormat);
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    static String getName(Level level, boolean usePlatformLevel) {
+        if (usePlatformLevel) {
+            return PlatformLogger.toPlatformLevel(level).name();
+        } else {
+            return level.getName();
+        }
+    }
+
+    // Calls the 8 methods defined on Logger and verify the
+    // parameters received by the underlying TestProvider.LoggerImpl
+    // logger.
+    private static void testLogger(Map<Logger, String> loggerDescMap,
+            SimpleConsoleLogger simple,
+            Level defaultLevel,
+            boolean usePlatformLevel,
+            String defaultFormat) {
+
+        System.out.println("Testing " + loggerDescMap.get(simple) + " [" + simple +"]");
+
+        String formatStrMarker = defaultFormat == null ? ""
+                : defaultFormat.startsWith("++++") ? "++++" : "";
+        String unexpectedMarker = defaultFormat == null ? "++++"
+                : defaultFormat.startsWith("++++") ? "????" : "++++";
+        String formatStrSpec = defaultFormat == null ? "[date]"
+                : defaultFormat.startsWith("++++") ? "++++" : "????";
+        String sep = defaultFormat == null ? ": " : ":_";
+        String sepw = defaultFormat == null ? " " : "_";
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        for (Level loggerLevel : defaultLevel == null
+                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
+            for (Level messageLevel : Level.values()) {
+                ErrorStream.errorStream.drain();
+                String desc = "logger.log(messageLevel, foo): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                SEQUENCER.incrementAndGet();
+                simple.log(messageLevel, foo);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF
+                    || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String expected = getName(messageLevel, usePlatformLevel) + sep + fooMsg;
+                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
+                        || !logged.contains(formatStrMarker)
+                        || logged.contains(unexpectedMarker)
+                        || !logged.contains(expected)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
+                                + expected
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        String msg = "blah";
+        for (Level loggerLevel : defaultLevel == null
+                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                SEQUENCER.incrementAndGet();
+                simple.log(messageLevel, msg);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF
+                    || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String expected = getName(messageLevel, usePlatformLevel) + sep + msg;
+                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
+                        || !logged.contains(formatStrMarker)
+                        || logged.contains(unexpectedMarker)
+                        || !logged.contains(expected)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
+                                + expected
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        Supplier<String> fooSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+
+        for (Level loggerLevel : defaultLevel == null
+                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                SEQUENCER.incrementAndGet();
+                simple.log(messageLevel, fooSupplier);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF
+                    || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String expected = getName(messageLevel, usePlatformLevel) + sep + fooSupplier.get();
+                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
+                        || !logged.contains(formatStrMarker)
+                        || logged.contains(unexpectedMarker)
+                        || !logged.contains(expected)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
+                                + expected
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = msg;
+        for (Level loggerLevel : defaultLevel == null
+                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                SEQUENCER.incrementAndGet();
+                simple.log(messageLevel, format, foo, msg);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String msgFormat = format;
+                    String text = java.text.MessageFormat.format(msgFormat, foo, msg);
+                    String expected = getName(messageLevel, usePlatformLevel) + sep + text;
+                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
+                        || !logged.contains(formatStrMarker)
+                        || !logged.contains(expected)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
+                                + expected
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        for (Level loggerLevel : defaultLevel == null
+                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                SEQUENCER.incrementAndGet();
+                simple.log(messageLevel, msg, thrown);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    thrown.printStackTrace(new PrintStream(baos));
+                    String text = baos.toString();
+                    String expected = getName(messageLevel, usePlatformLevel) + sep + msg;
+                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
+                        || !logged.contains(formatStrMarker)
+                        || !logged.contains(expected)
+                        || logged.contains(unexpectedMarker)
+                        || !logged.contains(text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
+                                + msg +"\n"
+                                + text
+                                + ">>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+
+        for (Level loggerLevel : defaultLevel == null
+                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                SEQUENCER.incrementAndGet();
+                simple.log(messageLevel, fooSupplier, thrown);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    thrown.printStackTrace(new PrintStream(baos));
+                    String text = baos.toString();
+                    String expected = getName(messageLevel, usePlatformLevel) + sep + fooSupplier.get();
+                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
+                        || !logged.contains(formatStrMarker)
+                        || !logged.contains(expected)
+                        || logged.contains(unexpectedMarker)
+                        || !logged.contains(text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
+                                + expected +"\n"
+                                + text
+                                + ">>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        for (Level loggerLevel : defaultLevel == null
+                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                SEQUENCER.incrementAndGet();
+                simple.log(messageLevel, bundle, format, foo, msg);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String text = java.text.MessageFormat.format(bundle.getString(format), foo, msg);
+                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
+                        || !logged.contains(formatStrMarker)
+                        || logged.contains(unexpectedMarker)
+                        || !logged.contains(getName(messageLevel, usePlatformLevel) + sep + text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
+                                + getName(messageLevel, usePlatformLevel) + " " + text
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        for (Level loggerLevel : defaultLevel == null
+                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                SEQUENCER.incrementAndGet();
+                simple.log(messageLevel, bundle, msg, thrown);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String textMsg = bundle.getString(msg);
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    thrown.printStackTrace(new PrintStream(baos));
+                    String text = baos.toString();
+                    String expected = getName(messageLevel, usePlatformLevel) + sep + textMsg;
+                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
+                        || !logged.contains(formatStrMarker)
+                        || !logged.contains(expected)
+                        || logged.contains(unexpectedMarker)
+                        || !logged.contains(text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
+                                + expected +"\n"
+                                + text
+                                + ">>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+    }
+}