/*
* @(#)LogManager.java 1.46 04/06/07
*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.util.logging;
import java.io.*;
import java.util.*;
import java.security.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.net.URL;
import sun.security.action.GetPropertyAction;
/**
* There is a single global LogManager object that is used to
* maintain a set of shared state about Loggers and log services.
*
* This LogManager object:
*
*
Manages a hierarchical namespace of Logger objects. All
* named Loggers are stored in this namespace.
*
Manages a set of logging control properties. These are
* simple key-value pairs that can be used by Handlers and
* other logging objects to configure themselves.
*
*
* The global LogManager object can be retrieved using LogManager.getLogManager().
* The LogManager object is created during class initialization and
* cannot subsequently be changed.
*
* At startup the LogManager class is located using the
* java.util.logging.manager system property.
*
* By default, the LogManager reads its initial configuration from
* a properties file "lib/logging.properties" in the JRE directory.
* If you edit that property file you can change the default logging
* configuration for all uses of that JRE.
*
* In addition, the LogManager uses two optional system properties that
* allow more control over reading the initial configuration:
*
*
"java.util.logging.config.class"
*
"java.util.logging.config.file"
*
* These two properties may be set via the Preferences API, or as
* command line property definitions to the "java" command, or as
* system property definitions passed to JNI_CreateJavaVM.
*
* If the "java.util.logging.config.class" property is set, then the
* property value is treated as a class name. The given class will be
* loaded, an object will be instantiated, and that object's constructor
* is responsible for reading in the initial configuration. (That object
* may use other system properties to control its configuration.) The
* alternate configuration class can use readConfiguration(InputStream)
* to define properties in the LogManager.
*
* If "java.util.logging.config.class" property is not set,
* then the "java.util.logging.config.file" system property can be used
* to specify a properties file (in java.util.Properties format). The
* initial logging configuration will be read from this file.
*
* If neither of these properties is defined then, as described
* above, the LogManager will read its initial configuration from
* a properties file "lib/logging.properties" in the JRE directory.
*
* The properties for loggers and Handlers will have names starting
* with the dot-separated name for the handler or logger.
*
* The global logging properties may include:
*
*
A property "handlers". This defines a whitespace separated
* list of class names for handler classes to load and register as
* handlers on the root Logger (the Logger named ""). Each class
* name must be for a Handler class which has a default constructor.
* Note that these Handlers may be created lazily, when they are
* first used.
*
*
A property "<logger>.handlers". This defines a whitespace or
* comma separated list of class names for handlers classes to
* load and register as handlers to the specified logger. Each class
* name must be for a Handler class which has a default constructor.
* Note that these Handlers may be created lazily, when they are
* first used.
*
*
A property "<logger>.useParentHandlers". This defines a boolean
* value. By default every logger calls its parent in addition to
* handling the logging message itself, this often result in messages
* being handled by the root logger as well. When setting this property
* to false a Handler needs to be configured for this logger otherwise
* no logging messages are delivered.
*
*
A property "config". This property is intended to allow
* arbitrary configuration code to be run. The property defines a
* whitespace separated list of class names. A new instance will be
* created for each named class. The default constructor of each class
* may execute arbitrary code to update the logging configuration, such as
* setting logger levels, adding handlers, adding filters, etc.
*
*
* Note that all classes loaded during LogManager configuration are
* first searched on the system class path before any user class path.
* That includes the LogManager class, any config classes, and any
* handler classes.
*
* Loggers are organized into a naming hierarchy based on their
* dot separated names. Thus "a.b.c" is a child of "a.b", but
* "a.b1" and a.b2" are peers.
*
* All properties whose names end with ".level" are assumed to define
* log levels for Loggers. Thus "foo.level" defines a log level for
* the logger called "foo" and (recursively) for any of its children
* in the naming hierarchy. Log Levels are applied in the order they
* are defined in the properties file. Thus level settings for child
* nodes in the tree should come after settings for their parents.
* The property name ".level" can be used to set the level for the
* root of the tree.
*
* All methods on the LogManager object are multi-thread safe.
*
* @version 1.46, 06/07/04
* @since 1.4
*/
public class LogManager {
// The global LogManager object
private static LogManager manager;
private final static Handler[] emptyHandlers = { };
private Properties props = new Properties();
private PropertyChangeSupport changes
= new PropertyChangeSupport(LogManager.class);
private final static Level defaultLevel = Level.INFO;
// Table of known loggers. Maps names to Loggers.
private Hashtable loggers = new Hashtable();
// Tree of known loggers
private LogNode root = new LogNode(null);
private Logger rootLogger;
// Have we done the primordial reading of the configuration file?
// (Must be done after a suitable amount of java.lang.System
// initialization has been done)
private volatile boolean readPrimordialConfiguration;
// Have we initialized global (root) handlers yet?
// This gets set to false in readConfiguration
private boolean initializedGlobalHandlers = true;
// True if JVM death is imminent and the exit hook has been called.
private boolean deathImminent;
static {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
String cname = null;
try {
cname = System.getProperty("java.util.logging.manager");
if (cname != null) {
try {
Class clz = ClassLoader.getSystemClassLoader().loadClass(cname);
manager = (LogManager) clz.newInstance();
} catch (ClassNotFoundException ex) {
Class clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
manager = (LogManager) clz.newInstance();
}
}
} catch (Exception ex) {
System.err.println("Could not load Logmanager \"" + cname + "\"");
ex.printStackTrace();
}
if (manager == null) {
manager = new LogManager();
}
// Create and retain Logger for the root of the namespace.
manager.rootLogger = manager.new RootLogger();
manager.addLogger(manager.rootLogger);
// We don't call readConfiguration() here, as we may be running
// very early in the JVM startup sequence. Instead readConfiguration
// will be called lazily in getLogManager().
return null;
}
});
}
// This private class is used as a shutdown hook.
// It does a "reset" to close all open handlers.
private class Cleaner extends Thread {
public void run() {
// If the global handlers haven't been initialized yet, we
// don't want to initialize them just so we can close them!
synchronized (LogManager.this) {
// Note that death is imminent.
deathImminent = true;
initializedGlobalHandlers = true;
}
// Do a reset to close all active handlers.
reset();
}
}
/**
* Protected constructor. This is protected so that container applications
* (such as J2EE containers) can subclass the object. It is non-public as
* it is intended that there only be one LogManager object, whose value is
* retrieved by calling Logmanager.getLogManager.
*/
protected LogManager() {
// Add a shutdown hook to close the global handlers.
try {
Runtime.getRuntime().addShutdownHook(new Cleaner());
} catch (IllegalStateException e) {
// If the VM is already shutting down,
// We do not need to register shutdownHook.
}
}
/**
* Return the global LogManager object.
*/
public static LogManager getLogManager() {
if (manager != null) {
manager.readPrimordialConfiguration();
}
return manager;
}
private void readPrimordialConfiguration() {
if (!readPrimordialConfiguration) {
synchronized (this) {
if (!readPrimordialConfiguration) {
// If System.in/out/err are null, it's a good
// indication that we're still in the
// bootstrapping phase
if (System.out == null) {
return;
}
readPrimordialConfiguration = true;
try {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
readConfiguration();
return null;
}
});
} catch (Exception ex) {
// System.err.println("Can't read logging configuration:");
// ex.printStackTrace();
}
}
}
}
}
/**
* Adds an event listener to be invoked when the logging
* properties are re-read. Adding multiple instances of
* the same event Listener results in multiple entries
* in the property event listener table.
*
* @param l event listener
* @exception SecurityException if a security manager exists and if
* the caller does not have LoggingPermission("control").
* @exception NullPointerException if the PropertyChangeListener is null.
*/
public void addPropertyChangeListener(PropertyChangeListener l) throws SecurityException {
if (l == null) {
throw new NullPointerException();
}
checkAccess();
changes.addPropertyChangeListener(l);
}
/**
* Removes an event listener for property change events.
* If the same listener instance has been added to the listener table
* through multiple invocations of addPropertyChangeListener,
* then an equivalent number of
* removePropertyChangeListener invocations are required to remove
* all instances of that listener from the listener table.
*
* Returns silently if the given listener is not found.
*
* @param l event listener (can be null)
* @exception SecurityException if a security manager exists and if
* the caller does not have LoggingPermission("control").
*/
public void removePropertyChangeListener(PropertyChangeListener l) throws SecurityException {
checkAccess();
changes.removePropertyChangeListener(l);
}
/**
* Add a named logger. This does nothing and returns false if a logger
* with the same name is already registered.
*
* The Logger factory methods call this method to register each
* newly created Logger.
*
* The application should retain its own reference to the Logger
* object to avoid it being garbage collected. The LogManager
* may only retain a weak reference.
*
* @param logger the new logger.
* @return true if the argument logger was registered successfully,
* false if a logger of that name already exists.
* @exception NullPointerException if the logger name is null.
*/
public synchronized boolean addLogger(Logger logger) {
final String name = logger.getName();
if (name == null) {
throw new NullPointerException();
}
Logger old = loggers.get(name);
if (old != null) {
// We already have a registered logger with the given name.
return false;
}
// We're adding a new logger.
// Note that we are creating a strong reference here that will
// keep the Logger in existence indefinitely.
loggers.put(name, logger);
// Apply any initial level defined for the new logger.
Level level = getLevelProperty(name+".level", null);
if (level != null) {
doSetLevel(logger, level);
}
// Do we have a per logger handler too?
// Note: this will add a 200ms penalty
if (getProperty(name+".handlers") != null) {
// This code is taken from the root handler initialization
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
// Add new per logger handlers.
String names[] = parseClassNames(name+".handlers");
for (int i = 0; i < names.length; i++) {
String word = names[i];
try {
Class clz = ClassLoader.getSystemClassLoader().loadClass(word);
Handler h = (Handler) clz.newInstance();
try {
// Check if there is a property defining the
// this handler's level.
String levs = getProperty(word + ".level");
if (levs != null) {
h.setLevel(Level.parse(levs));
}
boolean useParent = getBooleanProperty(name + ".useParentHandlers", true);
if (!useParent) {
getLogger(name).setUseParentHandlers(false);
}
} catch (Exception ex) {
System.err.println("Can't set level for " + word);
// Probably a bad level. Drop through.
}
// Add this Handler to the logger
getLogger(name).addHandler(h);
} catch (Exception ex) {
System.err.println("Can't load log handler \"" + word + "\"");
System.err.println("" + ex);
ex.printStackTrace();
}
}
return null;
}});
} // do we have per logger handlers
// If any of the logger's parents have levels defined,
// make sure they are instantiated.
int ix = 1;
for (;;) {
int ix2 = name.indexOf(".", ix);
if (ix2 < 0) {
break;
}
String pname = name.substring(0,ix2);
if (getProperty(pname+".level") != null) {
// This pname has a level definition. Make sure it exists.
Logger plogger = Logger.getLogger(pname);
}
// While we are walking up the tree I can check for our
// own root logger and get its handlers initialized too with
// the same code
if (getProperty(pname+".handlers") != null) {
final String nname=pname;
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
String names[] = parseClassNames(nname+".handlers");
for (int i = 0; i < names.length; i++) {
String word = names[i];
try {
Class clz = ClassLoader.getSystemClassLoader().loadClass(word);
Handler h = (Handler) clz.newInstance();
try {
// Check if there is a property defining the
// handler's level.
String levs = getProperty(word + ".level");
if (levs != null) {
h.setLevel(Level.parse(levs));
}
} catch (Exception ex) {
System.err.println("Can't set level for " + word);
// Probably a bad level. Drop through.
}
if (getLogger(nname) == null ) {
Logger nplogger=Logger.getLogger(nname);
addLogger(nplogger);
}
boolean useParent = getBooleanProperty(nname + ".useParentHandlers", true);
if (!useParent) {
getLogger(nname).setUseParentHandlers(false);
}
} catch (Exception ex) {
System.err.println("Can't load log handler \"" + word + "\"");
System.err.println("" + ex);
ex.printStackTrace();
}
}
return null;
}});
} //found a parent handler
ix = ix2+1;
}
// Find the new node and its parent.
LogNode node = findNode(name);
node.logger = logger;
Logger parent = null;
LogNode nodep = node.parent;
while (nodep != null) {
if (nodep.logger != null) {
parent = nodep.logger;
break;
}
nodep = nodep.parent;
}
if (parent != null) {
doSetParent(logger, parent);
}
// Walk over the children and tell them we are their new parent.
node.walkAndSetParent(logger);
return true;
}
// 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) {
SecurityManager sm = System.getSecurityManager();
if (sm == null) {
// There is no security manager, so things are easy.
logger.setLevel(level);
return;
}
// There is a security manager. Raise privilege before
// calling setLevel.
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
logger.setLevel(level);
return null;
}});
}
// 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) {
SecurityManager sm = System.getSecurityManager();
if (sm == null) {
// There is no security manager, so things are easy.
logger.setParent(parent);
return;
}
// There is a security manager. Raise privilege before
// calling setParent.
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
logger.setParent(parent);
return null;
}});
}
// Find a node in our tree of logger nodes.
// If necessary, create it.
private LogNode findNode(String name) {
if (name == null || name.equals("")) {
return root;
}
LogNode node = root;
while (name.length() > 0) {
int ix = name.indexOf(".");
String head;
if (ix > 0) {
head = name.substring(0,ix);
name = name.substring(ix+1);
} else {
head = name;
name = "";
}
if (node.children == null) {
node.children = new HashMap