Frames | No Frames |
1: /* LogManager.java -- a class for maintaining Loggers and managing 2: configuration properties 3: Copyright (C) 2002 Free Software Foundation, Inc. 4: 5: This file is part of GNU Classpath. 6: 7: GNU Classpath is free software; you can redistribute it and/or modify 8: it under the terms of the GNU General Public License as published by 9: the Free Software Foundation; either version 2, or (at your option) 10: any later version. 11: 12: GNU Classpath is distributed in the hope that it will be useful, but 13: WITHOUT ANY WARRANTY; without even the implied warranty of 14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15: General Public License for more details. 16: 17: You should have received a copy of the GNU General Public License 18: along with GNU Classpath; see the file COPYING. If not, write to the 19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 20: 02110-1301 USA. 21: 22: Linking this library statically or dynamically with other modules is 23: making a combined work based on this library. Thus, the terms and 24: conditions of the GNU General Public License cover the whole 25: combination. 26: 27: As a special exception, the copyright holders of this library give you 28: permission to link this library with independent modules to produce an 29: executable, regardless of the license terms of these independent 30: modules, and to copy and distribute the resulting executable under 31: terms of your choice, provided that you also meet, for each linked 32: independent module, the terms and conditions of the license of that 33: module. An independent module is a module which is not derived from 34: or based on this library. If you modify this library, you may extend 35: this exception to your version of the library, but you are not 36: obligated to do so. If you do not wish to do so, delete this 37: exception statement from your version. */ 38: 39: 40: package java.util.logging; 41: 42: import java.beans.PropertyChangeListener; 43: import java.beans.PropertyChangeSupport; 44: import java.io.IOException; 45: import java.io.InputStream; 46: import java.lang.ref.WeakReference; 47: import java.net.URL; 48: import java.util.Collections; 49: import java.util.Enumeration; 50: import java.util.Iterator; 51: import java.util.Map; 52: import java.util.Properties; 53: import java.util.StringTokenizer; 54: 55: /** 56: * The <code>LogManager</code> maintains a hierarchical namespace 57: * of Logger objects and manages properties for configuring the logging 58: * framework. There exists only one single <code>LogManager</code> 59: * per virtual machine. This instance can be retrieved using the 60: * static method {@link #getLogManager()}. 61: * 62: * <p><strong>Configuration Process:</strong> The global LogManager 63: * object is created and configured when the class 64: * <code>java.util.logging.LogManager</code> is initialized. 65: * The configuration process includes the subsequent steps: 66: * 67: * <ul> 68: * <li>If the system property <code>java.util.logging.manager</code> 69: * is set to the name of a subclass of 70: * <code>java.util.logging.LogManager</code>, an instance of 71: * that subclass is created and becomes the global LogManager. 72: * Otherwise, a new instance of LogManager is created.</li> 73: * <li>The <code>LogManager</code> constructor tries to create 74: * a new instance of the class specified by the system 75: * property <code>java.util.logging.config.class</code>. 76: * Typically, the constructor of this class will call 77: * <code>LogManager.getLogManager().readConfiguration(java.io.InputStream)</code> 78: * for configuring the logging framework. 79: * The configuration process stops at this point if 80: * the system property <code>java.util.logging.config.class</code> 81: * is set (irrespective of whether the class constructor 82: * could be called or an exception was thrown).</li> 83: * 84: * <li>If the system property <code>java.util.logging.config.class</code> 85: * is <em>not</em> set, the configuration parameters are read in from 86: * a file and passed to 87: * {@link #readConfiguration(java.io.InputStream)}. 88: * The name and location of this file are specified by the system 89: * property <code>java.util.logging.config.file</code>.</li> 90: * <li>If the system property <code>java.util.logging.config.file</code> 91: * is not set, however, the contents of the URL 92: * "{gnu.classpath.home.url}/logging.properties" are passed to 93: * {@link #readConfiguration(java.io.InputStream)}. 94: * Here, "{gnu.classpath.home.url}" stands for the value of 95: * the system property <code>gnu.classpath.home.url</code>.</li> 96: * </ul> 97: * 98: * @author Sascha Brawer (brawer@acm.org) 99: */ 100: public class LogManager 101: { 102: /** 103: * The singleton LogManager instance. 104: */ 105: private static LogManager logManager; 106: 107: /** 108: * The registered named loggers; maps the name of a Logger to 109: * a WeakReference to it. 110: */ 111: private Map loggers; 112: final Logger rootLogger; 113: 114: /** 115: * The properties for the logging framework which have been 116: * read in last. 117: */ 118: private Properties properties; 119: 120: /** 121: * A delegate object that provides support for handling 122: * PropertyChangeEvents. The API specification does not 123: * mention which bean should be the source in the distributed 124: * PropertyChangeEvents, but Mauve test code has determined that 125: * the Sun J2SE 1.4 reference implementation uses the LogManager 126: * class object. This is somewhat strange, as the class object 127: * is not the bean with which listeners have to register, but 128: * there is no reason for the GNU Classpath implementation to 129: * behave differently from the reference implementation in 130: * this case. 131: */ 132: private final PropertyChangeSupport pcs = new PropertyChangeSupport( /* source bean */ 133: LogManager.class); 134: 135: protected LogManager() 136: { 137: if (logManager != null) 138: throw new IllegalStateException("there can be only one LogManager; use LogManager.getLogManager()"); 139: 140: logManager = this; 141: loggers = new java.util.HashMap(); 142: rootLogger = new Logger("", null); 143: addLogger(rootLogger); 144: 145: /* Make sure that Logger.global has the rootLogger as its parent. 146: * 147: * Logger.global is set during class initialization of Logger, 148: * which may or may not be before this code is being executed. 149: * For example, on the Sun 1.3.1 and 1.4.0 JVMs, Logger.global 150: * has been set before this code is being executed. In contrast, 151: * Logger.global still is null on GCJ 3.2. Since the LogManager 152: * and Logger classes are mutually dependent, both behaviors are 153: * correct. 154: * 155: * This means that we cannot depend on Logger.global to have its 156: * value when this code executes, although that variable is final. 157: * Since Logger.getLogger will always return the same logger for 158: * the same name, the subsequent line works fine irrespective of 159: * the order in which classes are initialized. 160: */ 161: Logger.getLogger("global").setParent(rootLogger); 162: Logger.getLogger("global").setUseParentHandlers(true); 163: } 164: 165: /** 166: * Returns the globally shared LogManager instance. 167: */ 168: public static LogManager getLogManager() 169: { 170: return logManager; 171: } 172: 173: static 174: { 175: makeLogManager(); 176: 177: /* The Javadoc description of the class explains 178: * what is going on here. 179: */ 180: Object configurator = createInstance(System.getProperty("java.util.logging.config.class"), 181: /* must be instance of */ Object.class); 182: 183: try 184: { 185: if (configurator == null) 186: getLogManager().readConfiguration(); 187: } 188: catch (IOException ex) 189: { 190: /* FIXME: Is it ok to ignore exceptions here? */ 191: } 192: } 193: 194: private static LogManager makeLogManager() 195: { 196: String managerClassName; 197: LogManager manager; 198: 199: managerClassName = System.getProperty("java.util.logging.manager"); 200: manager = (LogManager) createInstance(managerClassName, LogManager.class); 201: if (manager != null) 202: return manager; 203: 204: if (managerClassName != null) 205: System.err.println("WARNING: System property \"java.util.logging.manager\"" 206: + " should be the name of a subclass of java.util.logging.LogManager"); 207: 208: return new LogManager(); 209: } 210: 211: /** 212: * Registers a listener which will be notified when the 213: * logging properties are re-read. 214: */ 215: public synchronized void addPropertyChangeListener(PropertyChangeListener listener) 216: { 217: /* do not register null. */ 218: listener.getClass(); 219: 220: pcs.addPropertyChangeListener(listener); 221: } 222: 223: /** 224: * Unregisters a listener. 225: * 226: * If <code>listener</code> has not been registered previously, 227: * nothing happens. Also, no exception is thrown if 228: * <code>listener</code> is <code>null</code>. 229: */ 230: public synchronized void removePropertyChangeListener(PropertyChangeListener listener) 231: { 232: if (listener != null) 233: pcs.removePropertyChangeListener(listener); 234: } 235: 236: /** 237: * Adds a named logger. If a logger with the same name has 238: * already been registered, the method returns <code>false</code> 239: * without adding the logger. 240: * 241: * <p>The <code>LogManager</code> only keeps weak references 242: * to registered loggers. Therefore, names can become available 243: * after automatic garbage collection. 244: * 245: * @param logger the logger to be added. 246: * 247: * @return <code>true</code>if <code>logger</code> was added, 248: * <code>false</code> otherwise. 249: * 250: * @throws NullPointerException if <code>name</code> is 251: * <code>null</code>. 252: */ 253: public synchronized boolean addLogger(Logger logger) 254: { 255: /* To developers thinking about to remove the 'synchronized' 256: * declaration from this method: Please read the comment 257: * in java.util.logging.Logger.getLogger(String, String) 258: * and make sure that whatever you change wrt. synchronization 259: * does not endanger thread-safety of Logger.getLogger. 260: * The current implementation of Logger.getLogger assumes 261: * that LogManager does its synchronization on the globally 262: * shared instance of LogManager. 263: */ 264: String name; 265: WeakReference ref; 266: 267: /* This will throw a NullPointerException if logger is null, 268: * as required by the API specification. 269: */ 270: name = logger.getName(); 271: 272: ref = (WeakReference) loggers.get(name); 273: if (ref != null) 274: { 275: if (ref.get() != null) 276: return false; 277: 278: /* There has been a logger under this name in the past, 279: * but it has been garbage collected. 280: */ 281: loggers.remove(ref); 282: } 283: 284: /* Adding a named logger requires a security permission. */ 285: if ((name != null) && ! name.equals("")) 286: checkAccess(); 287: 288: Logger parent = findAncestor(logger); 289: loggers.put(name, new WeakReference(logger)); 290: if (parent != logger.getParent()) 291: logger.setParent(parent); 292: 293: /* It can happen that existing loggers should be children of 294: * the newly added logger. For example, assume that there 295: * already exist loggers under the names "", "foo", and "foo.bar.baz". 296: * When adding "foo.bar", the logger "foo.bar.baz" should change 297: * its parent to "foo.bar". 298: */ 299: if (parent != rootLogger) 300: { 301: for (Iterator iter = loggers.keySet().iterator(); iter.hasNext();) 302: { 303: Logger possChild = (Logger) ((WeakReference) loggers.get(iter.next())) 304: .get(); 305: if ((possChild == null) || (possChild == logger) 306: || (possChild.getParent() != parent)) 307: continue; 308: 309: if (! possChild.getName().startsWith(name)) 310: continue; 311: 312: if (possChild.getName().charAt(name.length()) != '.') 313: continue; 314: 315: possChild.setParent(logger); 316: } 317: } 318: 319: return true; 320: } 321: 322: /** 323: * Finds the closest ancestor for a logger among the currently 324: * registered ones. For example, if the currently registered 325: * loggers have the names "", "foo", and "foo.bar", the result for 326: * "foo.bar.baz" will be the logger whose name is "foo.bar". 327: * 328: * @param child a logger for whose name no logger has been 329: * registered. 330: * 331: * @return the closest ancestor for <code>child</code>, 332: * or <code>null</code> if <code>child</code> 333: * is the root logger. 334: * 335: * @throws NullPointerException if <code>child</code> 336: * is <code>null</code>. 337: */ 338: private synchronized Logger findAncestor(Logger child) 339: { 340: String childName = child.getName(); 341: int childNameLength = childName.length(); 342: Logger best = rootLogger; 343: int bestNameLength = 0; 344: 345: Logger cand; 346: String candName; 347: int candNameLength; 348: 349: if (child == rootLogger) 350: return null; 351: 352: for (Iterator iter = loggers.keySet().iterator(); iter.hasNext();) 353: { 354: candName = (String) iter.next(); 355: candNameLength = candName.length(); 356: 357: if (candNameLength > bestNameLength 358: && childNameLength > candNameLength 359: && childName.startsWith(candName) 360: && childName.charAt(candNameLength) == '.') 361: { 362: cand = (Logger) ((WeakReference) loggers.get(candName)).get(); 363: if ((cand == null) || (cand == child)) 364: continue; 365: 366: bestNameLength = candName.length(); 367: best = cand; 368: } 369: } 370: 371: return best; 372: } 373: 374: /** 375: * Returns a Logger given its name. 376: * 377: * @param name the name of the logger. 378: * 379: * @return a named Logger, or <code>null</code> if there is no 380: * logger with that name. 381: * 382: * @throw java.lang.NullPointerException if <code>name</code> 383: * is <code>null</code>. 384: */ 385: public synchronized Logger getLogger(String name) 386: { 387: WeakReference ref; 388: 389: /* Throw a NullPointerException if name is null. */ 390: name.getClass(); 391: 392: ref = (WeakReference) loggers.get(name); 393: if (ref != null) 394: return (Logger) ref.get(); 395: else 396: return null; 397: } 398: 399: /** 400: * Returns an Enumeration of currently registered Logger names. 401: * Since other threads can register loggers at any time, the 402: * result could be different any time this method is called. 403: * 404: * @return an Enumeration with the names of the currently 405: * registered Loggers. 406: */ 407: public synchronized Enumeration getLoggerNames() 408: { 409: return Collections.enumeration(loggers.keySet()); 410: } 411: 412: /** 413: * Resets the logging configuration by removing all handlers for 414: * registered named loggers and setting their level to <code>null</code>. 415: * The level of the root logger will be set to <code>Level.INFO</code>. 416: * 417: * @throws SecurityException if a security manager exists and 418: * the caller is not granted the permission to control 419: * the logging infrastructure. 420: */ 421: public synchronized void reset() throws SecurityException 422: { 423: /* Throw a SecurityException if the caller does not have the 424: * permission to control the logging infrastructure. 425: */ 426: checkAccess(); 427: 428: properties = new Properties(); 429: 430: Iterator iter = loggers.values().iterator(); 431: while (iter.hasNext()) 432: { 433: WeakReference ref; 434: Logger logger; 435: 436: ref = (WeakReference) iter.next(); 437: if (ref != null) 438: { 439: logger = (Logger) ref.get(); 440: 441: if (logger == null) 442: iter.remove(); 443: else if (logger != rootLogger) 444: logger.setLevel(null); 445: } 446: } 447: 448: rootLogger.setLevel(Level.INFO); 449: } 450: 451: /** 452: * Configures the logging framework by reading a configuration file. 453: * The name and location of this file are specified by the system 454: * property <code>java.util.logging.config.file</code>. If this 455: * property is not set, the URL 456: * "{gnu.classpath.home.url}/logging.properties" is taken, where 457: * "{gnu.classpath.home.url}" stands for the value of the system 458: * property <code>gnu.classpath.home.url</code>. 459: * 460: * <p>The task of configuring the framework is then delegated to 461: * {@link #readConfiguration(java.io.InputStream)}, which will 462: * notify registered listeners after having read the properties. 463: * 464: * @throws SecurityException if a security manager exists and 465: * the caller is not granted the permission to control 466: * the logging infrastructure, or if the caller is 467: * not granted the permission to read the configuration 468: * file. 469: * 470: * @throws IOException if there is a problem reading in the 471: * configuration file. 472: */ 473: public synchronized void readConfiguration() 474: throws IOException, SecurityException 475: { 476: String path; 477: InputStream inputStream; 478: 479: path = System.getProperty("java.util.logging.config.file"); 480: if ((path == null) || (path.length() == 0)) 481: { 482: String url = (System.getProperty("gnu.classpath.home.url") 483: + "/logging.properties"); 484: inputStream = new URL(url).openStream(); 485: } 486: else 487: inputStream = new java.io.FileInputStream(path); 488: 489: try 490: { 491: readConfiguration(inputStream); 492: } 493: finally 494: { 495: /* Close the stream in order to save 496: * resources such as file descriptors. 497: */ 498: inputStream.close(); 499: } 500: } 501: 502: public synchronized void readConfiguration(InputStream inputStream) 503: throws IOException, SecurityException 504: { 505: Properties newProperties; 506: Enumeration keys; 507: 508: checkAccess(); 509: newProperties = new Properties(); 510: newProperties.load(inputStream); 511: this.properties = newProperties; 512: keys = newProperties.propertyNames(); 513: 514: while (keys.hasMoreElements()) 515: { 516: String key = ((String) keys.nextElement()).trim(); 517: String value = newProperties.getProperty(key); 518: 519: if (value == null) 520: continue; 521: 522: value = value.trim(); 523: 524: if ("handlers".equals(key)) 525: { 526: StringTokenizer tokenizer = new StringTokenizer(value); 527: while (tokenizer.hasMoreTokens()) 528: { 529: String handlerName = tokenizer.nextToken(); 530: try 531: { 532: Class handlerClass = Class.forName(handlerName); 533: getLogger("").addHandler((Handler) handlerClass 534: .newInstance()); 535: } 536: catch (ClassCastException ex) 537: { 538: System.err.println("[LogManager] class " + handlerName 539: + " is not subclass of java.util.logging.Handler"); 540: } 541: catch (Exception ex) 542: { 543: //System.out.println("[LogManager.readConfiguration]"+ex); 544: } 545: } 546: } 547: 548: if (key.endsWith(".level")) 549: { 550: String loggerName = key.substring(0, key.length() - 6); 551: Logger logger = getLogger(loggerName); 552: 553: if (logger == null) 554: { 555: logger = Logger.getLogger(loggerName); 556: addLogger(logger); 557: } 558: try 559: { 560: logger.setLevel(Level.parse(value)); 561: } 562: catch (Exception _) 563: { 564: //System.out.println("[LogManager.readConfiguration] "+_); 565: } 566: continue; 567: } 568: } 569: 570: /* The API specification does not talk about the 571: * property name that is distributed with the 572: * PropertyChangeEvent. With test code, it could 573: * be determined that the Sun J2SE 1.4 reference 574: * implementation uses null for the property name. 575: */ 576: pcs.firePropertyChange(null, null, null); 577: } 578: 579: /** 580: * Returns the value of a configuration property as a String. 581: */ 582: public synchronized String getProperty(String name) 583: { 584: if (properties != null) 585: return properties.getProperty(name); 586: else 587: return null; 588: } 589: 590: /** 591: * Returns the value of a configuration property as an integer. 592: * This function is a helper used by the Classpath implementation 593: * of java.util.logging, it is <em>not</em> specified in the 594: * logging API. 595: * 596: * @param name the name of the configuration property. 597: * 598: * @param defaultValue the value that will be returned if the 599: * property is not defined, or if its value is not an integer 600: * number. 601: */ 602: static int getIntProperty(String name, int defaultValue) 603: { 604: try 605: { 606: return Integer.parseInt(getLogManager().getProperty(name)); 607: } 608: catch (Exception ex) 609: { 610: return defaultValue; 611: } 612: } 613: 614: /** 615: * Returns the value of a configuration property as an integer, 616: * provided it is inside the acceptable range. 617: * This function is a helper used by the Classpath implementation 618: * of java.util.logging, it is <em>not</em> specified in the 619: * logging API. 620: * 621: * @param name the name of the configuration property. 622: * 623: * @param minValue the lowest acceptable value. 624: * 625: * @param maxValue the highest acceptable value. 626: * 627: * @param defaultValue the value that will be returned if the 628: * property is not defined, or if its value is not an integer 629: * number, or if it is less than the minimum value, 630: * or if it is greater than the maximum value. 631: */ 632: static int getIntPropertyClamped(String name, int defaultValue, 633: int minValue, int maxValue) 634: { 635: int val = getIntProperty(name, defaultValue); 636: if ((val < minValue) || (val > maxValue)) 637: val = defaultValue; 638: return val; 639: } 640: 641: /** 642: * Returns the value of a configuration property as a boolean. 643: * This function is a helper used by the Classpath implementation 644: * of java.util.logging, it is <em>not</em> specified in the 645: * logging API. 646: * 647: * @param name the name of the configuration property. 648: * 649: * @param defaultValue the value that will be returned if the 650: * property is not defined, or if its value is neither 651: * <code>"true"</code> nor <code>"false"</code>. 652: */ 653: static boolean getBooleanProperty(String name, boolean defaultValue) 654: { 655: try 656: { 657: return (new Boolean(getLogManager().getProperty(name))).booleanValue(); 658: } 659: catch (Exception ex) 660: { 661: return defaultValue; 662: } 663: } 664: 665: /** 666: * Returns the value of a configuration property as a Level. 667: * This function is a helper used by the Classpath implementation 668: * of java.util.logging, it is <em>not</em> specified in the 669: * logging API. 670: * 671: * @param propertyName the name of the configuration property. 672: * 673: * @param defaultValue the value that will be returned if the 674: * property is not defined, or if 675: * {@link Level.parse(java.lang.String)} does not like 676: * the property value. 677: */ 678: static Level getLevelProperty(String propertyName, Level defaultValue) 679: { 680: try 681: { 682: return Level.parse(getLogManager().getProperty(propertyName)); 683: } 684: catch (Exception ex) 685: { 686: return defaultValue; 687: } 688: } 689: 690: /** 691: * Returns the value of a configuration property as a Class. 692: * This function is a helper used by the Classpath implementation 693: * of java.util.logging, it is <em>not</em> specified in the 694: * logging API. 695: * 696: * @param propertyName the name of the configuration property. 697: * 698: * @param defaultValue the value that will be returned if the 699: * property is not defined, or if it does not specify 700: * the name of a loadable class. 701: */ 702: static final Class getClassProperty(String propertyName, Class defaultValue) 703: { 704: Class usingClass = null; 705: 706: try 707: { 708: String propertyValue = logManager.getProperty(propertyName); 709: if (propertyValue != null) 710: usingClass = Class.forName(propertyValue); 711: if (usingClass != null) 712: return usingClass; 713: } 714: catch (Exception _) 715: { 716: } 717: 718: return defaultValue; 719: } 720: 721: static final Object getInstanceProperty(String propertyName, Class ofClass, 722: Class defaultClass) 723: { 724: Class klass = getClassProperty(propertyName, defaultClass); 725: if (klass == null) 726: return null; 727: 728: try 729: { 730: Object obj = klass.newInstance(); 731: if (ofClass.isInstance(obj)) 732: return obj; 733: } 734: catch (Exception _) 735: { 736: } 737: 738: if (defaultClass == null) 739: return null; 740: 741: try 742: { 743: return defaultClass.newInstance(); 744: } 745: catch (java.lang.InstantiationException ex) 746: { 747: throw new RuntimeException(ex.getMessage()); 748: } 749: catch (java.lang.IllegalAccessException ex) 750: { 751: throw new RuntimeException(ex.getMessage()); 752: } 753: } 754: 755: /** 756: * An instance of <code>LoggingPermission("control")</code> 757: * that is shared between calls to <code>checkAccess()</code>. 758: */ 759: private static final LoggingPermission controlPermission = new LoggingPermission("control", 760: null); 761: 762: /** 763: * Checks whether the current security context allows changing 764: * the configuration of the logging framework. For the security 765: * context to be trusted, it has to be granted 766: * a LoggingPermission("control"). 767: * 768: * @throws SecurityException if a security manager exists and 769: * the caller is not granted the permission to control 770: * the logging infrastructure. 771: */ 772: public void checkAccess() throws SecurityException 773: { 774: SecurityManager sm = System.getSecurityManager(); 775: if (sm != null) 776: sm.checkPermission(controlPermission); 777: } 778: 779: /** 780: * Creates a new instance of a class specified by name. 781: * 782: * @param className the name of the class of which a new instance 783: * should be created. 784: * 785: * @param ofClass the class to which the new instance should 786: * be either an instance or an instance of a subclass. 787: * FIXME: This description is just terrible. 788: * 789: * @return the new instance, or <code>null</code> if 790: * <code>className</code> is <code>null</code>, if no class 791: * with that name could be found, if there was an error 792: * loading that class, or if the constructor of the class 793: * has thrown an exception. 794: */ 795: static final Object createInstance(String className, Class ofClass) 796: { 797: Class klass; 798: 799: if ((className == null) || (className.length() == 0)) 800: return null; 801: 802: try 803: { 804: klass = Class.forName(className); 805: if (! ofClass.isAssignableFrom(klass)) 806: return null; 807: 808: return klass.newInstance(); 809: } 810: catch (Exception _) 811: { 812: return null; 813: } 814: catch (java.lang.LinkageError _) 815: { 816: return null; 817: } 818: } 819: }