8005615: Java Logger fails to load tomcat logger implementation (JULI)
authormchung
Thu, 10 Jan 2013 19:43:36 -0800
changeset 16105 fe7392acb767
parent 16104 234ab73a1830
child 16106 8d062d0a26d7
8005615: Java Logger fails to load tomcat logger implementation (JULI) Reviewed-by: alanb, ahgross
jdk/src/share/classes/java/util/logging/LogManager.java
jdk/src/share/classes/java/util/logging/Logger.java
jdk/test/java/util/logging/CustomLogManager.java
jdk/test/java/util/logging/CustomLogManagerTest.java
jdk/test/java/util/logging/SimpleLogManager.java
--- a/jdk/src/share/classes/java/util/logging/LogManager.java	Tue Dec 18 13:48:48 2012 -0500
+++ b/jdk/src/share/classes/java/util/logging/LogManager.java	Thu Jan 10 19:43:36 2013 -0800
@@ -158,7 +158,7 @@
 
     // LoggerContext for system loggers and user loggers
     private final LoggerContext systemContext = new SystemLoggerContext();
-    private final LoggerContext userContext = new UserLoggerContext();
+    private final LoggerContext userContext = new LoggerContext();
     private Logger rootLogger;
 
     // Have we done the primordial reading of the configuration file?
@@ -196,13 +196,13 @@
 
                     // Create and retain Logger for the root of the namespace.
                     manager.rootLogger = manager.new RootLogger();
-                    manager.systemContext.addLogger(manager.rootLogger);
-                    manager.userContext.addLogger(manager.rootLogger);
+                    manager.addLogger(manager.rootLogger);
+                    manager.systemContext.addLocalLogger(manager.rootLogger);
 
                     // Adding the global Logger. Doing so in the Logger.<clinit>
                     // would deadlock with the LogManager.<clinit>.
-                    Logger.getGlobal().setLogManager(manager);
-                    manager.systemContext.addLogger(Logger.getGlobal());
+                    Logger.global.setLogManager(manager);
+                    manager.addLogger(Logger.global);
 
                     // We don't call readConfiguration() here, as we may be running
                     // very early in the JVM startup sequence.  Instead readConfiguration
@@ -373,7 +373,7 @@
 
     // Returns the LoggerContext for the user code (i.e. application or AppContext).
     // Loggers are isolated from each AppContext.
-    LoggerContext getUserContext() {
+    private LoggerContext getUserContext() {
         LoggerContext context = null;
 
         SecurityManager sm = System.getSecurityManager();
@@ -394,8 +394,8 @@
                     if (javaAwtAccess.isMainAppContext()) {
                         context = userContext;
                     } else {
-                        context = new UserLoggerContext();
-                        context.addLogger(manager.rootLogger);
+                        context = new LoggerContext();
+                        context.addLocalLogger(manager.rootLogger);
                     }
                     javaAwtAccess.put(ecx, LoggerContext.class, context);
                 }
@@ -406,10 +406,6 @@
         return context;
     }
 
-    LoggerContext getSystemContext() {
-        return systemContext;
-    }
-
     private List<LoggerContext> contexts() {
         List<LoggerContext> cxs = new ArrayList<>();
         cxs.add(systemContext);
@@ -417,6 +413,58 @@
         return cxs;
     }
 
+    // Find or create a specified logger instance. If a logger has
+    // already been created with the given name it is returned.
+    // Otherwise a new logger instance is created and registered
+    // in the LogManager global namespace.
+    // This method will always return a non-null Logger object.
+    // Synchronization is not required here. All synchronization for
+    // adding a new Logger object is handled by addLogger().
+    //
+    // This method must delegate to the LogManager implementation to
+    // add a new Logger or return the one that has been added previously
+    // as a LogManager subclass may override the addLogger, getLogger,
+    // readConfiguration, and other methods.
+    Logger demandLogger(String name, String resourceBundleName) {
+        Logger result = getLogger(name);
+        if (result == null) {
+            // only allocate the new logger once
+            Logger newLogger = new Logger(name, resourceBundleName);
+            do {
+                if (addLogger(newLogger)) {
+                    // We successfully added the new Logger that we
+                    // created above so return it without refetching.
+                    return newLogger;
+                }
+
+                // We didn't add the new Logger that we created above
+                // because another thread added a Logger with the same
+                // name after our null check above and before our call
+                // to addLogger(). We have to refetch the Logger because
+                // addLogger() returns a boolean instead of the Logger
+                // reference itself. However, if the thread that created
+                // the other Logger is not holding a strong reference to
+                // the other Logger, then it is possible for the other
+                // Logger to be GC'ed after we saw it in addLogger() and
+                // before we can refetch it. If it has been GC'ed then
+                // we'll just loop around and try again.
+                result = getLogger(name);
+            } while (result == null);
+        }
+        return result;
+    }
+
+    Logger demandSystemLogger(String name, String resourceBundleName) {
+        return systemContext.demandLogger(name, resourceBundleName);
+    }
+
+    // LoggerContext maintains the logger namespace per context.
+    // The default LogManager implementation has one system context and user
+    // context.  The system context is used to maintain the namespace for
+    // all system loggers and is queried by the system code.  If a system logger
+    // doesn't exist in the user context, it'll also be added to the user context.
+    // The user context is queried by the user code and all other loggers are
+    // added in the user context.
     static class LoggerContext {
         // Table of named Loggers that maps names to Loggers.
         private final Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();
@@ -427,6 +475,12 @@
             this.root = new LogNode(null, this);
         }
 
+        Logger demandLogger(String name, String resourceBundleName) {
+            // a LogManager subclass may have its own implementation to add and
+            // get a Logger.  So delegate to the LogManager to do the work.
+            return manager.demandLogger(name, resourceBundleName);
+        }
+
         synchronized Logger findLogger(String name) {
             LoggerWeakRef ref = namedLoggers.get(name);
             if (ref == null) {
@@ -441,7 +495,9 @@
             return logger;
         }
 
-        synchronized boolean addLogger(Logger logger) {
+        // Add a logger to this context.  This method will only set its level
+        // and process parent loggers.  It doesn't set its handlers.
+        synchronized boolean addLocalLogger(Logger logger) {
             final String name = logger.getName();
             if (name == null) {
                 throw new NullPointerException();
@@ -474,9 +530,9 @@
                 doSetLevel(logger, level);
             }
 
-            // Do we have a per logger handler too?
-            // Note: this will add a 200ms penalty
-            manager.loadLoggerHandlers(logger, name, name + ".handlers");
+            // instantiation of the handler is done in the LogManager.addLogger
+            // implementation as a handler class may be only visible to LogManager
+            // subclass for the custom log manager case
             processParentHandlers(logger, name);
 
             // Find the new node and its parent.
@@ -513,50 +569,21 @@
             return namedLoggers.keys();
         }
 
-        Logger demandLogger(String name) {
-            return demandLogger(name, null);
-        }
-
-        // Find or create a specified logger instance. If a logger has
-        // already been created with the given name it is returned.
-        // Otherwise a new logger instance is created and registered
-        // in the LogManager global namespace.
-
-        // This method will always return a non-null Logger object.
-        // Synchronization is not required here. All synchronization for
-        // adding a new Logger object is handled by addLogger().
-        Logger demandLogger(String name, String resourceBundleName) {
-            Logger result = findLogger(name);
-            if (result == null) {
-                // only allocate the new logger once
-                Logger newLogger = new Logger(name, resourceBundleName);
-                do {
-                    if (addLogger(newLogger)) {
-                        // We successfully added the new Logger that we
-                        // created above so return it without refetching.
-                        return newLogger;
-                    }
-
-                    // We didn't add the new Logger that we created above
-                    // because another thread added a Logger with the same
-                    // name after our null check above and before our call
-                    // to addLogger(). We have to refetch the Logger because
-                    // addLogger() returns a boolean instead of the Logger
-                    // reference itself. However, if the thread that created
-                    // the other Logger is not holding a strong reference to
-                    // the other Logger, then it is possible for the other
-                    // Logger to be GC'ed after we saw it in addLogger() and
-                    // before we can refetch it. If it has been GC'ed then
-                    // we'll just loop around and try again.
-                    result = findLogger(name);
-                } while (result == null);
-            }
-            return result;
-        }
-
         // If logger.getUseParentHandlers() returns 'true' and any of the logger's
         // parents have levels or handlers defined, make sure they are instantiated.
-        private void processParentHandlers(Logger logger, String name) {
+        private void processParentHandlers(final Logger logger, final String name) {
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                public Void run() {
+                    if (logger != manager.rootLogger) {
+                        boolean useParent = manager.getBooleanProperty(name + ".useParentHandlers", true);
+                        if (!useParent) {
+                            logger.setUseParentHandlers(false);
+                        }
+                    }
+                    return null;
+                }
+            });
+
             int ix = 1;
             for (;;) {
                 int ix2 = name.indexOf(".", ix);
@@ -564,12 +591,11 @@
                     break;
                 }
                 String pname = name.substring(0, ix2);
-
                 if (manager.getProperty(pname + ".level") != null ||
                     manager.getProperty(pname + ".handlers") != null) {
                     // This pname has a level/handlers definition.
                     // Make sure it exists.
-                    demandLogger(pname);
+                    demandLogger(pname, null);
                 }
                 ix = ix2+1;
             }
@@ -607,53 +633,51 @@
     }
 
     static class SystemLoggerContext extends LoggerContext {
-        // Default resource bundle for all system loggers
-        Logger demandLogger(String name) {
-            // default to use the system logger's resource bundle
-            return super.demandLogger(name, Logger.SYSTEM_LOGGER_RB_NAME);
-        }
-    }
-
-    static class UserLoggerContext extends LoggerContext {
-        /**
-         * Returns a Logger of the given name if there is one registered
-         * in this context.  Otherwise, it will return the one registered
-         * in the system context if there is one.  The returned Logger
-         * instance may be initialized with a different resourceBundleName.
-         * If no such logger exists, a new Logger instance will be created
-         * and registered in this context.
-         */
+        // Add a system logger in the system context's namespace as well as
+        // in the LogManager's namespace if not exist so that there is only
+        // one single logger of the given name.  System loggers are visible
+        // to applications unless a logger of the same name has been added.
         Logger demandLogger(String name, String resourceBundleName) {
             Logger result = findLogger(name);
             if (result == null) {
-                // use the system logger if exists; or allocate a new logger.
-                // The system logger is added to the app logger context so that
-                // any child logger created in the app logger context can have
-                // a system logger as its parent if already exist.
-                Logger logger = manager.systemContext.findLogger(name);
-                Logger newLogger =
-                    logger != null ? logger : new Logger(name, resourceBundleName);
+                // only allocate the new system logger once
+                Logger newLogger = new Logger(name, resourceBundleName);
                 do {
-                    if (addLogger(newLogger)) {
+                    if (addLocalLogger(newLogger)) {
                         // We successfully added the new Logger that we
                         // created above so return it without refetching.
-                        return newLogger;
+                        result = newLogger;
+                    } else {
+                        // We didn't add the new Logger that we created above
+                        // because another thread added a Logger with the same
+                        // name after our null check above and before our call
+                        // to addLogger(). We have to refetch the Logger because
+                        // addLogger() returns a boolean instead of the Logger
+                        // reference itself. However, if the thread that created
+                        // the other Logger is not holding a strong reference to
+                        // the other Logger, then it is possible for the other
+                        // Logger to be GC'ed after we saw it in addLogger() and
+                        // before we can refetch it. If it has been GC'ed then
+                        // we'll just loop around and try again.
+                        result = findLogger(name);
                     }
-
-                    // We didn't add the new Logger that we created above
-                    // because another thread added a Logger with the same
-                    // name after our null check above and before our call
-                    // to addLogger(). We have to refetch the Logger because
-                    // addLogger() returns a boolean instead of the Logger
-                    // reference itself. However, if the thread that created
-                    // the other Logger is not holding a strong reference to
-                    // the other Logger, then it is possible for the other
-                    // Logger to be GC'ed after we saw it in addLogger() and
-                    // before we can refetch it. If it has been GC'ed then
-                    // we'll just loop around and try again.
-                    result = findLogger(name);
                 } while (result == null);
             }
+            // Add the system logger to the LogManager's namespace if not exists
+            // The LogManager will set its handlers via the LogManager.addLogger method.
+            if (!manager.addLogger(result) && result.getHandlers().length == 0) {
+                // if logger already exists but handlers not set
+                final Logger l = manager.getLogger(name);
+                final Logger logger = result;
+                AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                    public Void run() {
+                        for (Handler hdl : l.getHandlers()) {
+                            logger.addHandler(hdl);
+                        }
+                        return null;
+                    }
+                });
+            }
             return result;
         }
     }
@@ -663,22 +687,16 @@
     // be made based on the logging configuration, which can
     // only be modified by trusted code.
     private void loadLoggerHandlers(final Logger logger, final String name,
-                                    final String handlersPropertyName) {
+                                    final String handlersPropertyName)
+    {
         AccessController.doPrivileged(new PrivilegedAction<Object>() {
             public Object run() {
-                if (logger != rootLogger) {
-                    boolean useParent = getBooleanProperty(name + ".useParentHandlers", true);
-                    if (!useParent) {
-                        logger.setUseParentHandlers(false);
-                    }
-                }
-
                 String names[] = parseClassNames(handlersPropertyName);
                 for (int i = 0; i < names.length; i++) {
                     String word = names[i];
                     try {
                         Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(word);
-                        Handler  hdl = (Handler) clz.newInstance();
+                        Handler hdl = (Handler) clz.newInstance();
                         // Check if there is a property defining the
                         // this handler's level.
                         String levs = getProperty(word + ".level");
@@ -700,7 +718,8 @@
                     }
                 }
                 return null;
-            }});
+            }
+        });
     }
 
 
@@ -839,13 +858,17 @@
         if (name == null) {
             throw new NullPointerException();
         }
-        if (systemContext.findLogger(name) != null) {
+        LoggerContext cx = getUserContext();
+        if (cx.addLocalLogger(logger)) {
+            // Do we have a per logger handler too?
+            // Note: this will add a 200ms penalty
+            loadLoggerHandlers(logger, name, name + ".handlers");
+            return true;
+        } else {
             return false;
         }
-        return getUserContext().addLogger(logger);
     }
 
-
     // Private method to set a level on a logger.
     // If necessary, we raise privilege before doing the call.
     private static void doSetLevel(final Logger logger, final Level level) {
@@ -864,8 +887,6 @@
             }});
     }
 
-
-
     // Private method to set a parent on a logger.
     // If necessary, we raise privilege before doing the setParent call.
     private static void doSetParent(final Logger logger, final Logger parent) {
@@ -900,15 +921,7 @@
      * @return  matching logger or null if none is found
      */
     public Logger getLogger(String name) {
-        // return the first logger added
-        //
-        // once a system logger is added in the system context, no one can
-        // adds a logger with the same name in the global context
-        // (see LogManager.addLogger).  So if there is a logger in the global
-        // context with the same name as one in the system context, it must be
-        // added before the system logger was created.
-        Logger logger = getUserContext().findLogger(name);
-        return logger != null ? logger : systemContext.findLogger(name);
+        return getUserContext().findLogger(name);
     }
 
     /**
@@ -928,10 +941,7 @@
      * @return  enumeration of logger name strings
      */
     public Enumeration<String> getLoggerNames() {
-        // only return unique names
-        Set<String> names = new HashSet<>(Collections.list(systemContext.getLoggerNames()));
-        names.addAll(Collections.list(getUserContext().getLoggerNames()));
-        return Collections.enumeration(names);
+        return getUserContext().getLoggerNames();
     }
 
     /**
@@ -1329,7 +1339,6 @@
     // that we only instantiate the global handlers when they
     // are first needed.
     private class RootLogger extends Logger {
-
         private RootLogger() {
             super("", null);
             setLevel(defaultLevel);
--- a/jdk/src/share/classes/java/util/logging/Logger.java	Tue Dec 18 13:48:48 2012 -0500
+++ b/jdk/src/share/classes/java/util/logging/Logger.java	Thu Jan 10 19:43:36 2013 -0800
@@ -31,7 +31,6 @@
 import java.security.*;
 import java.lang.ref.WeakReference;
 import java.util.function.Supplier;
-import java.util.logging.LogManager.LoggerContext;
 
 /**
  * A Logger object is used to log messages for a specific
@@ -321,18 +320,32 @@
     //
     // As an interim solution, if the immediate caller whose caller loader is
     // null, we assume it's a system logger and add it to the system context.
-    private static LoggerContext getLoggerContext() {
+    // These system loggers only set the resource bundle to the given
+    // resource bundle name (rather than the default system resource bundle).
+    private static class SystemLoggerHelper {
+        static boolean disableCallerCheck = getBooleanProperty("sun.util.logging.disableCallerCheck");
+        private static boolean getBooleanProperty(final String key) {
+            String s = AccessController.doPrivileged(new PrivilegedAction<String>() {
+                public String run() {
+                    return System.getProperty(key);
+                }
+            });
+            return Boolean.valueOf(s);
+        }
+    }
+
+    private static Logger demandLogger(String name, String resourceBundleName) {
         LogManager manager = LogManager.getLogManager();
         SecurityManager sm = System.getSecurityManager();
-        if (sm != null) {
-            // 0: Reflection 1: Logger.getLoggerContext 2: Logger.getLogger 3: caller
+        if (sm != null && !SystemLoggerHelper.disableCallerCheck) {
+            // 0: Reflection 1: Logger.demandLogger 2: Logger.getLogger 3: caller
             final int SKIP_FRAMES = 3;
             Class<?> caller = sun.reflect.Reflection.getCallerClass(SKIP_FRAMES);
             if (caller.getClassLoader() == null) {
-                return manager.getSystemContext();
+                return manager.demandSystemLogger(name, resourceBundleName);
             }
         }
-        return manager.getUserContext();
+        return manager.demandLogger(name, resourceBundleName);
     }
 
     /**
@@ -376,8 +389,7 @@
         // would throw an IllegalArgumentException in the second call
         // because the wrapper would result in an attempt to replace
         // the existing "resourceBundleForFoo" with null.
-        LoggerContext context = getLoggerContext();
-        return context.demandLogger(name);
+        return demandLogger(name, null);
     }
 
     /**
@@ -424,8 +436,7 @@
     // Synchronization is not required here. All synchronization for
     // adding a new Logger object is handled by LogManager.addLogger().
     public static Logger getLogger(String name, String resourceBundleName) {
-        LoggerContext context = getLoggerContext();
-        Logger result = context.demandLogger(name, resourceBundleName);
+        Logger result = demandLogger(name, resourceBundleName);
 
         // MissingResourceException or IllegalArgumentException can be
         // thrown by setupResourceInfo().
@@ -438,11 +449,10 @@
     // i.e. caller of sun.util.logging.PlatformLogger.getLogger
     static Logger getPlatformLogger(String name) {
         LogManager manager = LogManager.getLogManager();
-        LoggerContext context = manager.getSystemContext();
 
         // all loggers in the system context will default to
         // the system logger's resource bundle
-        Logger result = context.demandLogger(name);
+        Logger result = manager.demandSystemLogger(name, SYSTEM_LOGGER_RB_NAME);
         return result;
     }
 
@@ -1588,7 +1598,8 @@
             public ResourceBundle run() {
                 try {
                     return ResourceBundle.getBundle(SYSTEM_LOGGER_RB_NAME,
-                                                    locale);
+                                                    locale,
+                                                    ClassLoader.getSystemClassLoader());
                 } catch (MissingResourceException e) {
                     throw new InternalError(e.toString());
                 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/logging/CustomLogManager.java	Thu Jan 10 19:43:36 2013 -0800
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2013, 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.*;
+import java.util.*;
+import java.util.logging.*;
+
+/*
+ * Custom LogManager implementation to verify that the implementation delegates
+ * to the LogManager subclass to register both system logger and user logger.
+ *
+ * The LogManager implementation is the one configuring the logger's property
+ * such as level, handler, etc.
+ */
+public class CustomLogManager extends LogManager {
+    static LogManager INSTANCE;
+    Map<String,Logger> namedLoggers = new HashMap<>();
+    Properties props = initConfig();
+    public CustomLogManager() {
+        if (INSTANCE != null) {
+            throw new RuntimeException("CustomLogManager already created");
+        }
+        INSTANCE = this;
+    }
+
+    public synchronized boolean addLogger(Logger logger) {
+        String name = logger.getName();
+        if (namedLoggers.containsKey(name)) {
+            return false;
+        }
+        namedLoggers.put(name, logger);
+        // set level
+        if (props.get(name + ".level") != null) {
+            logger.setLevel(Level.parse(props.getProperty(name + ".level")));
+        }
+        // add handlers
+        if (props.get(name + ".handlers") != null && logger.getHandlers().length == 0) {
+            logger.addHandler(new CustomHandler());
+        }
+        // add parent loggers
+        int ix = 1;
+        for (;;) {
+            int ix2 = name.indexOf(".", ix);
+            if (ix2 < 0) {
+                break;
+            }
+            String pname = name.substring(0, ix2);
+            if (props.get(pname + ".level") != null ||
+                props.get(pname + ".handlers") != null) {
+                // This pname has a level/handlers definition.
+                // Make sure it exists.
+                //
+                // The test doesn't set the parent for simplicity.
+                if (!namedLoggers.containsKey(pname)) {
+                    Logger.getLogger(pname);
+                }
+            }
+            ix = ix2 + 1;
+        }
+        return true;
+    }
+
+    public synchronized Logger getLogger(String name) {
+        return namedLoggers.get(name);
+    }
+
+    public synchronized Enumeration<String> getLoggerNames() {
+        return Collections.enumeration(namedLoggers.keySet());
+    }
+
+    public String getProperty(String name) {
+        return props.getProperty(name);
+    }
+
+    public void readConfiguration() {
+        // do nothing
+    }
+
+    public void readConfiguration(InputStream ins) {
+        // do nothing
+    }
+
+    private Properties initConfig() {
+        Properties props = new Properties();
+        props.put(".level", "CONFIG");
+        props.put("CustomLogManagerTest.level", "WARNING");
+        props.put("CustomLogManagerTest.handlers", "CustomLogManager$CustomHandler");
+        props.put("SimpleLogManager.level", "INFO");
+        props.put("SimpleLogManager.handlers", "CustomLogManager$CustomHandler");
+        props.put("CustomLogManager$CustomHandler.level", "WARNING");
+        props.put(".handlers", "CustomLogManager$CustomHandler");
+        props.put("org.foo.bar.level", "SEVERE");
+        props.put("org.foo.handlers", "CustomLogManager$CustomHandler");
+        props.put("org.openjdk.level", "SEVERE");
+        props.put("org.openjdk.handlers", "CustomLogManager$CustomHandler");
+        props.put("org.openjdk.core.level", "INFO");
+
+        return props;
+    }
+
+    public static void checkLogger(String name) {
+        checkLogger(name, null);
+    }
+
+    public static void checkLogger(String name, String resourceBundleName) {
+        Logger logger = INSTANCE.getLogger(name);
+        if (logger == null) {
+            throw new RuntimeException("Logger \"" + name + "\" not exist");
+        }
+        System.out.format("Logger \"%s\" level=%s handlers=%s resourcebundle=%s%n",
+            name, logger.getLevel(),
+            Arrays.toString(logger.getHandlers()),
+            logger.getResourceBundleName());
+        String rb = logger.getResourceBundleName();
+        if (rb != resourceBundleName && (rb == null || rb.equals(resourceBundleName))) {
+            throw new RuntimeException("Logger \"" + name +
+                "\" unexpected resource bundle: " + rb);
+        }
+
+        String value = INSTANCE.getProperty(name + ".level");
+        String level = logger.getLevel() != null ? logger.getLevel().getName() : null;
+        if (level != value && (level == null || level.equals(value))) {
+            throw new RuntimeException("Logger \"" + name + "\" unexpected level: " + level);
+        }
+
+        Handler[] handlers = logger.getHandlers();
+        String hdl = INSTANCE.getProperty(name + ".handlers");
+        if ((hdl == null && handlers.length != 0) ||
+            (hdl != null && handlers.length != 1)) {
+            throw new RuntimeException("Logger \"" + name + "\" unexpected handler: " +
+                Arrays.toString(handlers));
+        }
+        checkParents(name);
+    }
+
+    private static void checkParents(String name) {
+        int ix = 1;
+        for (;;) {
+            int ix2 = name.indexOf(".", ix);
+            if (ix2 < 0) {
+                break;
+            }
+            String pname = name.substring(0, ix2);
+            if (INSTANCE.getProperty(pname + ".level") != null ||
+                INSTANCE.getProperty(pname + ".handlers") != null) {
+                // This pname has a level/handlers definition.
+                // Make sure it exists.
+                checkLogger(pname);
+            }
+            ix = ix2 + 1;
+        }
+    }
+
+    // only CustomLogManager can create an instance of CustomHandler
+    private class CustomHandler extends StreamHandler {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/logging/CustomLogManagerTest.java	Thu Jan 10 19:43:36 2013 -0800
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2013, 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.*;
+import java.util.*;
+
+import java.util.logging.*;
+import sun.util.logging.PlatformLogger;
+
+/*
+ * @test
+ * @bug 8005615
+ * @summary Add loggers to custom log manager
+ *
+ * @compile -XDignore.symbol.file CustomLogManagerTest.java CustomLogManager.java
+ * @run main/othervm -Djava.util.logging.manager=CustomLogManager CustomLogManagerTest
+ */
+public class CustomLogManagerTest {
+    private static final String RESOURCE_BUNDLE = "sun.util.logging.resources.logging";
+    public static void main(String[] args) {
+        String mgr = System.getProperty("java.util.logging.manager");
+        if (!mgr.equals("CustomLogManager")) {
+            throw new RuntimeException("java.util.logging.manager not set");
+        }
+
+        Logger.getLogger(CustomLogManagerTest.class.getName());
+        Logger.getLogger("org.foo.Foo");
+        Logger.getLogger("org.foo.bar.Foo", RESOURCE_BUNDLE);
+        // platform logger will be set with the default system resource bundle
+        PlatformLogger.getLogger("org.openjdk.core.logger");
+
+        if (LogManager.getLogManager() != CustomLogManager.INSTANCE) {
+             throw new RuntimeException(LogManager.getLogManager() + " not CustomLogManager");
+        }
+
+        CustomLogManager.checkLogger(CustomLogManagerTest.class.getName());
+        CustomLogManager.checkLogger("org.foo.Foo");
+        CustomLogManager.checkLogger("org.foo.bar.Foo", RESOURCE_BUNDLE);
+        CustomLogManager.checkLogger(Logger.GLOBAL_LOGGER_NAME);
+        CustomLogManager.checkLogger("");
+        CustomLogManager.checkLogger("org.openjdk.core.logger", RESOURCE_BUNDLE);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/logging/SimpleLogManager.java	Thu Jan 10 19:43:36 2013 -0800
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2013, 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.util.*;
+import java.util.logging.*;
+import sun.util.logging.PlatformLogger;
+
+/*
+ * @test
+ * @bug 8005615
+ * @summary A LogManager subclass overrides its own implementation of named
+ *          logger (see the subclassing information in the Logger class specification)
+ *
+ * @compile -XDignore.symbol.file CustomLogManager.java SimpleLogManager.java
+ * @run main/othervm -Djava.util.logging.manager=SimpleLogManager SimpleLogManager
+ */
+public class SimpleLogManager extends CustomLogManager {
+    public static void main(String[] args) {
+        String classname = System.getProperty("java.util.logging.manager");
+        if (!classname.equals("SimpleLogManager")) {
+            throw new RuntimeException("java.util.logging.manager not set");
+        }
+
+        Logger logger = Logger.getLogger(SimpleLogManager.class.getName());
+        Logger.getLogger("org.foo.bar.Foo");
+
+        // a platform logger used by the system code is just a Logger instance.
+        PlatformLogger.getLogger("org.openjdk.core.logger");
+
+        LogManager mgr = LogManager.getLogManager();
+        if (mgr != CustomLogManager.INSTANCE || !(mgr instanceof SimpleLogManager)) {
+             throw new RuntimeException(LogManager.getLogManager() + " not SimpleLogManager");
+        }
+
+        checkCustomLogger(SimpleLogManager.class.getName(), null);
+        checkCustomLogger("org.foo.bar.Foo", null);
+        checkCustomLogger("org.openjdk.core.logger", "sun.util.logging.resources.logging");
+
+        // ## The LogManager.demandLogger method does not handle custom log manager
+        // ## that overrides the getLogger method to return a custom logger
+        // ## (see the test case in 8005640).  Logger.getLogger may return
+        // ## a Logger instance but LogManager overrides it with a custom Logger
+        // ## instance like this case.
+        //
+        // However, the specification of LogManager and Logger subclassing is
+        // not clear whether this is supported or not.  The following check
+        // just captures the current behavior.
+        if (logger instanceof CustomLogger) {
+            throw new RuntimeException(logger + " not CustomLogger");
+        }
+    }
+
+    private static void checkCustomLogger(String name, String resourceBundleName) {
+        CustomLogManager.checkLogger(name, resourceBundleName);
+        Logger logger1 = Logger.getLogger(name);
+        Logger logger2 = LogManager.getLogManager().getLogger(name);
+        if (logger1 != logger2) {
+            throw new RuntimeException(logger1 + " != " + logger2);
+        }
+        if (!(logger1 instanceof CustomLogger)) {
+            throw new RuntimeException(logger1 + " not CustomLogger");
+        }
+    }
+
+    /*
+     * This SimpleLogManager overrides the addLogger method to replace
+     * the given logger with a custom logger.
+     *
+     * It's unclear what the recommended way to use custom logger is.
+     * A LogManager subclass might override the getLogger method to return
+     * a custom Logger and create a new custom logger if not exist so that
+     * Logger.getLogger() can return a custom Logger instance but that violates
+     * the LogManager.getLogger() spec which should return null if not found.
+     */
+    public synchronized boolean addLogger(Logger logger) {
+        String name = logger.getName();
+        if (namedLoggers.containsKey(name)) {
+            return false;
+        }
+        CustomLogger newLogger = new CustomLogger(logger);
+        super.addLogger(newLogger);
+        return true;
+    }
+
+    public class CustomLogger extends Logger {
+        CustomLogger(Logger logger) {
+            super(logger.getName(), logger.getResourceBundleName());
+        }
+        CustomLogger(String name) {
+            super(name, null);
+        }
+    }
+}