changeset 5730:693b931cd7cf

8005615: Java Logger fails to load tomcat logger implementation (JULI) Reviewed-by: alanb, ahgross
author mchung
date Thu, 10 Jan 2013 10:52:39 -0800
parents 10517eb141bb
children 85aa22a416b1
files src/share/classes/java/util/logging/LogManager.java src/share/classes/java/util/logging/Logger.java test/java/util/logging/CustomLogManager.java test/java/util/logging/CustomLogManagerTest.java test/java/util/logging/SimpleLogManager.java
diffstat 5 files changed, 509 insertions(+), 140 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/util/logging/LogManager.java	Wed Dec 19 08:45:04 2012 -0500
+++ b/src/share/classes/java/util/logging/LogManager.java	Thu Jan 10 10:52:39 2013 -0800
@@ -159,7 +159,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?
@@ -197,13 +197,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.global.setLogManager(manager);
-                    manager.systemContext.addLogger(Logger.global);
+                    manager.addLogger(Logger.global);
 
                     // We don't call readConfiguration() here, as we may be running
                     // very early in the JVM startup sequence.  Instead readConfiguration
@@ -341,7 +341,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();
@@ -362,8 +362,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);
                 }
@@ -374,10 +374,6 @@
         return context;
     }
 
-    LoggerContext getSystemContext() {
-        return systemContext;
-    }
-
     private List<LoggerContext> contexts() {
         List<LoggerContext> cxs = new ArrayList<>();
         cxs.add(systemContext);
@@ -385,6 +381,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<>();
@@ -395,6 +443,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) {
@@ -409,7 +463,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();
@@ -442,9 +498,6 @@
                 doSetLevel(logger, level);
             }
 
-            // Do we have a per logger handler too?
-            // Note: this will add a 200ms penalty
-            manager.loadLoggerHandlers(logger, name, name + ".handlers");
             processParentHandlers(logger, name);
 
             // Find the new node and its parent.
@@ -481,50 +534,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);
@@ -532,12 +556,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;
             }
@@ -575,53 +598,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;
         }
     }
@@ -631,22 +652,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");
@@ -668,7 +683,8 @@
                     }
                 }
                 return null;
-            }});
+            }
+        });
     }
 
 
@@ -807,13 +823,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) {
@@ -832,8 +852,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) {
@@ -868,15 +886,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);
     }
 
     /**
@@ -896,10 +906,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();
     }
 
     /**
@@ -1221,7 +1228,6 @@
         loadLoggerHandlers(rootLogger, null, "handlers");
     }
 
-
     private final Permission controlPermission = new LoggingPermission("control", null);
 
     void checkPermission() {
@@ -1280,7 +1286,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/src/share/classes/java/util/logging/Logger.java	Wed Dec 19 08:45:04 2012 -0500
+++ b/src/share/classes/java/util/logging/Logger.java	Thu Jan 10 10:52:39 2013 -0800
@@ -30,7 +30,6 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.security.*;
 import java.lang.ref.WeakReference;
-import java.util.logging.LogManager.LoggerContext;
 
 /**
  * A Logger object is used to log messages for a specific
@@ -290,18 +289,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);
     }
 
     /**
@@ -345,8 +358,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);
     }
 
     /**
@@ -393,8 +405,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);
         if (result.resourceBundleName == null) {
             // Note: we may get a MissingResourceException here.
             result.setupResourceInfo(resourceBundleName);
@@ -410,11 +421,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;
     }
 
@@ -1326,7 +1336,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/test/java/util/logging/CustomLogManager.java	Thu Jan 10 10:52:39 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/test/java/util/logging/CustomLogManagerTest.java	Thu Jan 10 10:52:39 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/test/java/util/logging/SimpleLogManager.java	Thu Jan 10 10:52:39 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);
+        }
+    }
+}