1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.util.logging; 19 20// BEGIN android-note 21// this file contains cleaned up documentation and style for contribution 22// upstream. 23// javax.management support (MBeans) has been dropped. 24// END android-note 25 26import org.apache.harmony.logging.internal.nls.Messages; 27 28import java.beans.PropertyChangeListener; 29import java.beans.PropertyChangeSupport; 30import java.io.BufferedInputStream; 31import java.io.File; 32import java.io.FileInputStream; 33import java.io.IOException; 34import java.io.InputStream; 35import java.security.AccessController; 36import java.security.PrivilegedAction; 37import java.util.Collection; 38import java.util.Enumeration; 39import java.util.Hashtable; 40import java.util.Properties; 41import java.util.StringTokenizer; 42 43/** 44 * {@code LogManager} is used to maintain configuration properties of the 45 * logging framework, and to manage a hierarchical namespace of all named 46 * {@code Logger} objects. 47 * <p> 48 * There is only one global {@code LogManager} instance in the 49 * application, which can be get by calling static method 50 * {@link #getLogManager()}. This instance is created and 51 * initialized during class initialization and cannot be changed. 52 * <p> 53 * The {@code LogManager} class can be specified by 54 * java.util.logging.manager system property, if the property is unavailable or 55 * invalid, the default class {@link java.util.logging.LogManager} will 56 * be used. 57 * <p> 58 * On initialization, {@code LogManager} reads its configuration from a 59 * properties file, which by default is the "lib/logging.properties" in the JRE 60 * directory. 61 * <p> 62 * However, two optional system properties can be used to customize the initial 63 * configuration process of {@code LogManager}. 64 * <ul> 65 * <li>"java.util.logging.config.class"</li> 66 * <li>"java.util.logging.config.file"</li> 67 * </ul> 68 * <p> 69 * These two properties can be set in three ways, by the Preferences API, by the 70 * "java" command line property definitions, or by system property definitions 71 * passed to JNI_CreateJavaVM. 72 * <p> 73 * The "java.util.logging.config.class" should specifies a class name. If it is 74 * set, this given class will be loaded and instantiated during 75 * {@code LogManager} initialization, so that this object's default 76 * constructor can read the initial configuration and define properties for 77 * {@code LogManager}. 78 * <p> 79 * If "java.util.logging.config.class" property is not set, or it is invalid, or 80 * some exception is thrown during the instantiation, then the 81 * "java.util.logging.config.file" system property can be used to specify a 82 * properties file. The {@code LogManager} will read initial 83 * configuration from this file. 84 * <p> 85 * If neither of these properties is defined, or some exception is thrown 86 * during these two properties using, the {@code LogManager} will read 87 * its initial configuration from default properties file, as described above. 88 * <p> 89 * The global logging properties may include: 90 * <ul> 91 * <li>"handlers". This property's values should be a list of class names for 92 * handler classes separated by whitespace, these classes must be subclasses of 93 * {@code Handler} and each must have a default constructor, these 94 * classes will be loaded, instantiated and registered as handlers on the root 95 * {@code Logger} (the {@code Logger} named ""). These 96 * {@code Handler}s maybe initialized lazily.</li> 97 * <li>"config". The property defines a list of class names separated by 98 * whitespace. Each class must have a default constructor, in which it can 99 * update the logging configuration, such as levels, handlers, or filters for 100 * some logger, etc. These classes will be loaded and instantiated during 101 * {@code LogManager} configuration</li> 102 * </ul> 103 * <p> 104 * This class, together with any handler and configuration classes associated 105 * with it, <b>must</b> be loaded from the system classpath when 106 * {@code LogManager} configuration occurs. 107 * <p> 108 * Besides global properties, the properties for loggers and Handlers can be 109 * specified in the property files. The names of these properties will start 110 * with the complete dot separated names for the handlers or loggers. 111 * <p> 112 * In the {@code LogManager}'s hierarchical namespace, 113 * {@code Loggers} are organized based on their dot separated names. For 114 * example, "x.y.z" is child of "x.y". 115 * <p> 116 * Levels for {@code Loggers} can be defined by properties whose name end 117 * with ".level". Thus "alogger.level" defines a level for the logger named as 118 * "alogger" and for all its children in the naming hierarchy. Log levels 119 * properties are read and applied in the same order as they are specified in 120 * the property file. The root logger's level can be defined by the property 121 * named as ".level". 122 * <p> 123 * This class is thread safe. It is an error to synchronize on a 124 * {@code LogManager} while synchronized on a {@code Logger}. 125 */ 126public class LogManager { 127 128 /** The line separator of the underlying OS. */ 129 private static final String lineSeparator = getPrivilegedSystemProperty("line.separator"); //$NON-NLS-1$ 130 131 /** The shared logging permission. */ 132 private static final LoggingPermission perm = new LoggingPermission( 133 "control", null); //$NON-NLS-1$ 134 135 /** The singleton instance. */ 136 static LogManager manager; 137 138 /** 139 * The {@code String} value of the {@link LoggingMXBean}'s ObjectName. 140 */ 141 public static final String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging"; //$NON-NLS-1$ 142 143 /** 144 * Get the {@code LoggingMXBean} instance. this implementation always throws 145 * an UnsupportedOperationException. 146 * 147 * @return the {@code LoggingMXBean} instance 148 */ 149 public static LoggingMXBean getLoggingMXBean() { 150 // BEGIN android-added 151 throw new UnsupportedOperationException(); 152 // END android-added 153 // BEGIN android-removed 154 // try { 155 // ObjectName loggingMXBeanName = new ObjectName(LOGGING_MXBEAN_NAME); 156 // MBeanServer platformBeanServer = ManagementFactory 157 // .getPlatformMBeanServer(); 158 // Set<?> loggingMXBeanSet = platformBeanServer.queryMBeans( 159 // loggingMXBeanName, null); 160 // 161 // if (loggingMXBeanSet.size() != 1) { 162 // // logging.21=There Can Be Only One logging MX bean. 163 // throw new AssertionError(Messages.getString("logging.21")); //$NON-NLS-1$ 164 // } 165 // 166 // Iterator<?> i = loggingMXBeanSet.iterator(); 167 // ObjectInstance loggingMXBeanOI = (ObjectInstance) i.next(); 168 // String lmxbcn = loggingMXBeanOI.getClassName(); 169 // Class<?> lmxbc = Class.forName(lmxbcn); 170 // Method giMethod = lmxbc.getDeclaredMethod("getInstance"); //$NON-NLS-1$ 171 // giMethod.setAccessible(true); 172 // LoggingMXBean lmxb = (LoggingMXBean) giMethod.invoke(null, 173 // new Object[] {}); 174 // 175 // return lmxb; 176 // } catch (Exception e) { 177 // // TODO 178 // // e.printStackTrace(); 179 // } 180 // // logging.22=Exception occurred while getting the logging MX bean. 181 // throw new AssertionError(Messages.getString("logging.22")); //$NON-NLS-1$ 182 // END android-removed 183 } 184 185 // FIXME: use weak reference to avoid heap memory leak 186 private Hashtable<String, Logger> loggers; 187 188 /** The configuration properties */ 189 private Properties props; 190 191 /** the property change listener */ 192 private PropertyChangeSupport listeners; 193 194 static { 195 // init LogManager singleton instance 196 AccessController.doPrivileged(new PrivilegedAction<Object>() { 197 public Object run() { 198 String className = System 199 .getProperty("java.util.logging.manager"); //$NON-NLS-1$ 200 201 if (null != className) { 202 manager = (LogManager) getInstanceByClass(className); 203 } 204 if (null == manager) { 205 manager = new LogManager(); 206 } 207 208 // read configuration 209 try { 210 manager.readConfiguration(); 211 } catch (Exception e) { 212 e.printStackTrace(); 213 } 214 215 // if global logger has been initialized, set root as its parent 216 Logger root = new Logger("", null); //$NON-NLS-1$ 217 root.setLevel(Level.INFO); 218 Logger.global.setParent(root); 219 220 manager.addLogger(root); 221 manager.addLogger(Logger.global); 222 return null; 223 } 224 }); 225 } 226 227 /** 228 * Default constructor. This is not public because there should be only one 229 * {@code LogManager} instance, which can be get by 230 * {@code LogManager.getLogManager()}. This is protected so that 231 * application can subclass the object. 232 */ 233 protected LogManager() { 234 loggers = new Hashtable<String, Logger>(); 235 props = new Properties(); 236 listeners = new PropertyChangeSupport(this); 237 // add shutdown hook to ensure that the associated resource will be 238 // freed when JVM exits 239 AccessController.doPrivileged(new PrivilegedAction<Void>() { 240 public Void run() { 241 Runtime.getRuntime().addShutdownHook(new Thread() { 242 @Override 243 public void run() { 244 reset(); 245 } 246 }); 247 return null; 248 } 249 }); 250 } 251 252 /* 253 * Package private utilities Returns the line separator of the underlying 254 * OS. 255 */ 256 static String getSystemLineSeparator() { 257 return lineSeparator; 258 } 259 260 /** 261 * Check that the caller has {@code LoggingPermission("control")} so 262 * that it is trusted to modify the configuration for logging framework. If 263 * the check passes, just return, otherwise {@code SecurityException} 264 * will be thrown. 265 * 266 * @throws SecurityException 267 * if there is a security manager in operation and the invoker 268 * of this method does not have the required security permission 269 * {@code LoggingPermission("control")} 270 */ 271 public void checkAccess() { 272 if (null != System.getSecurityManager()) { 273 System.getSecurityManager().checkPermission(perm); 274 } 275 } 276 277 /** 278 * Add a given logger into the hierarchical namespace. The 279 * {@code Logger.addLogger()} factory methods call this method to add newly 280 * created Logger. This returns false if a logger with the given name has 281 * existed in the namespace 282 * <p> 283 * Note that the {@code LogManager} may only retain weak references to 284 * registered loggers. In order to prevent {@code Logger} objects from being 285 * unexpectedly garbage collected it is necessary for <i>applications</i> 286 * to maintain references to them. 287 * </p> 288 * 289 * @param logger 290 * the logger to be added. 291 * @return true if the given logger is added into the namespace 292 * successfully, false if the given logger exists in the namespace. 293 */ 294 public synchronized boolean addLogger(Logger logger) { 295 String name = logger.getName(); 296 if (null != loggers.get(name)) { 297 return false; 298 } 299 addToFamilyTree(logger, name); 300 loggers.put(name, logger); 301 logger.setManager(this); 302 return true; 303 } 304 305 private void addToFamilyTree(Logger logger, String name) { 306 Logger parent = null; 307 // find parent 308 int lastSeparator; 309 String parentName = name; 310 while ((lastSeparator = parentName.lastIndexOf('.')) != -1) { 311 parentName = parentName.substring(0, lastSeparator); 312 parent = loggers.get(parentName); 313 if (parent != null) { 314 setParent(logger, parent); 315 break; 316 } else if (getProperty(parentName + ".level") != null || //$NON-NLS-1$ 317 getProperty(parentName + ".handlers") != null) { //$NON-NLS-1$ 318 parent = Logger.getLogger(parentName); 319 setParent(logger, parent); 320 break; 321 } 322 } 323 if (parent == null && null != (parent = loggers.get(""))) { //$NON-NLS-1$ 324 setParent(logger, parent); 325 } 326 327 // find children 328 // TODO: performance can be improved here? 329 String nameDot = name + '.'; 330 Collection<Logger> allLoggers = loggers.values(); 331 for (final Logger child : allLoggers) { 332 Logger oldParent = child.getParent(); 333 if (parent == oldParent 334 && (name.length() == 0 || child.getName().startsWith( 335 nameDot))) { 336 final Logger thisLogger = logger; 337 AccessController.doPrivileged(new PrivilegedAction<Object>() { 338 public Object run() { 339 child.setParent(thisLogger); 340 return null; 341 } 342 }); 343 if (null != oldParent) { 344 // -- remove from old parent as the parent has been changed 345 oldParent.children.remove(child); 346 } 347 } 348 } 349 } 350 351 /** 352 * Get the logger with the given name. 353 * 354 * @param name 355 * name of logger 356 * @return logger with given name, or {@code null} if nothing is found. 357 */ 358 public synchronized Logger getLogger(String name) { 359 return loggers.get(name); 360 } 361 362 /** 363 * Get a {@code Enumeration} of all registered logger names. 364 * 365 * @return enumeration of registered logger names 366 */ 367 public synchronized Enumeration<String> getLoggerNames() { 368 return loggers.keys(); 369 } 370 371 /** 372 * Get the global {@code LogManager} instance. 373 * 374 * @return the global {@code LogManager} instance 375 */ 376 public static LogManager getLogManager() { 377 return manager; 378 } 379 380 /** 381 * Get the value of property with given name. 382 * 383 * @param name 384 * the name of property 385 * @return the value of property 386 */ 387 public String getProperty(String name) { 388 return props.getProperty(name); 389 } 390 391 /** 392 * Re-initialize the properties and configuration. The initialization 393 * process is same as the {@code LogManager} instantiation. 394 * <p> 395 * Notice : No {@code PropertyChangeEvent} are fired. 396 * </p> 397 * 398 * @throws IOException 399 * if any IO related problems happened. 400 * @throws SecurityException 401 * if security manager exists and it determines that caller does 402 * not have the required permissions to perform this action. 403 */ 404 public void readConfiguration() throws IOException { 405 // check config class 406 String configClassName = System 407 .getProperty("java.util.logging.config.class"); //$NON-NLS-1$ 408 if (null == configClassName 409 || null == getInstanceByClass(configClassName)) { 410 // if config class failed, check config file 411 String configFile = System 412 .getProperty("java.util.logging.config.file"); //$NON-NLS-1$ 413 414 if (null == configFile) { 415 // if cannot find configFile, use default logging.properties 416 configFile = new StringBuilder().append( 417 System.getProperty("java.home")).append(File.separator) //$NON-NLS-1$ 418 .append("lib").append(File.separator).append( //$NON-NLS-1$ 419 "logging.properties").toString(); //$NON-NLS-1$ 420 } 421 422 InputStream input = null; 423 try { 424 // BEGIN android-removed 425 // input = new BufferedInputStream(new FileInputStream(configFile)); 426 // END android-removed 427 428 // BEGIN android-added 429 try { 430 input = new BufferedInputStream( 431 new FileInputStream(configFile), 8192); 432 } catch (Exception ex) { 433 // consult fixed resource as a last resort 434 input = new BufferedInputStream( 435 getClass().getResourceAsStream( 436 "logging.properties"), 8192); 437 } 438 // END android-added 439 readConfiguration(input); 440 } finally { 441 if (input != null) { 442 try { 443 input.close(); 444 } catch (Exception e) {// ignore 445 } 446 } 447 } 448 } 449 } 450 451 // use privilege code to get system property 452 static String getPrivilegedSystemProperty(final String key) { 453 return AccessController.doPrivileged(new PrivilegedAction<String>() { 454 public String run() { 455 return System.getProperty(key); 456 } 457 }); 458 } 459 460 // use SystemClassLoader to load class from system classpath 461 static Object getInstanceByClass(final String className) { 462 try { 463 Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass( 464 className); 465 return clazz.newInstance(); 466 } catch (Exception e) { 467 try { 468 Class<?> clazz = Thread.currentThread().getContextClassLoader() 469 .loadClass(className); 470 return clazz.newInstance(); 471 } catch (Exception innerE) { 472 // logging.20=Loading class "{0}" failed 473 System.err.println(Messages.getString("logging.20", className)); //$NON-NLS-1$ 474 System.err.println(innerE); 475 return null; 476 } 477 } 478 479 } 480 481 // actual initialization process from a given input stream 482 private synchronized void readConfigurationImpl(InputStream ins) 483 throws IOException { 484 reset(); 485 props.load(ins); 486 487 // The RI treats the root logger as special. For compatibility, always 488 // update the root logger's handlers. 489 Logger root = loggers.get(""); 490 if (root != null) { 491 root.setManager(this); 492 } 493 494 // parse property "config" and apply setting 495 String configs = props.getProperty("config"); //$NON-NLS-1$ 496 if (null != configs) { 497 StringTokenizer st = new StringTokenizer(configs, " "); //$NON-NLS-1$ 498 while (st.hasMoreTokens()) { 499 String configerName = st.nextToken(); 500 getInstanceByClass(configerName); 501 } 502 } 503 504 // set levels for logger 505 Collection<Logger> allLoggers = loggers.values(); 506 for (Logger logger : allLoggers) { 507 String property = props.getProperty(logger.getName() + ".level"); //$NON-NLS-1$ 508 if (null != property) { 509 logger.setLevel(Level.parse(property)); 510 } 511 } 512 listeners.firePropertyChange(null, null, null); 513 } 514 515 /** 516 * Re-initialize the properties and configuration from the given 517 * {@code InputStream} 518 * <p> 519 * Notice : No {@code PropertyChangeEvent} are fired. 520 * </p> 521 * 522 * @param ins 523 * the input stream 524 * @throws IOException 525 * if any IO related problems happened. 526 * @throws SecurityException 527 * if security manager exists and it determines that caller does 528 * not have the required permissions to perform this action. 529 */ 530 public void readConfiguration(InputStream ins) throws IOException { 531 checkAccess(); 532 readConfigurationImpl(ins); 533 } 534 535 /** 536 * Reset configuration. 537 * <p> 538 * All handlers are closed and removed from any named loggers. All loggers' 539 * level is set to null, except the root logger's level is set to 540 * {@code Level.INFO}. 541 * </p> 542 * 543 * @throws SecurityException 544 * if security manager exists and it determines that caller does 545 * not have the required permissions to perform this action. 546 */ 547 public synchronized void reset() { 548 checkAccess(); 549 props = new Properties(); 550 Enumeration<String> names = getLoggerNames(); 551 while (names.hasMoreElements()) { 552 String name = names.nextElement(); 553 Logger logger = getLogger(name); 554 if (logger != null) { 555 logger.reset(); 556 } 557 } 558 Logger root = loggers.get(""); //$NON-NLS-1$ 559 if (null != root) { 560 root.setLevel(Level.INFO); 561 } 562 } 563 564 /** 565 * Add a {@code PropertyChangeListener}, which will be invoked when 566 * the properties are reread. 567 * 568 * @param l 569 * the {@code PropertyChangeListener} to be added. 570 * @throws SecurityException 571 * if security manager exists and it determines that caller does 572 * not have the required permissions to perform this action. 573 */ 574 public void addPropertyChangeListener(PropertyChangeListener l) { 575 if (l == null) { 576 throw new NullPointerException(); 577 } 578 checkAccess(); 579 listeners.addPropertyChangeListener(l); 580 } 581 582 /** 583 * Remove a {@code PropertyChangeListener}, do nothing if the given 584 * listener is not found. 585 * 586 * @param l 587 * the {@code PropertyChangeListener} to be removed. 588 * @throws SecurityException 589 * if security manager exists and it determines that caller does 590 * not have the required permissions to perform this action. 591 */ 592 public void removePropertyChangeListener(PropertyChangeListener l) { 593 checkAccess(); 594 listeners.removePropertyChangeListener(l); 595 } 596 597 /** 598 * Returns a named logger associated with the supplied resource bundle. 599 * 600 * @param resourceBundleName the resource bundle to associate, or null for 601 * no associated resource bundle. 602 */ 603 synchronized Logger getOrCreate(String name, String resourceBundleName) { 604 Logger result = getLogger(name); 605 if (result == null) { 606 result = new Logger(name, resourceBundleName); 607 addLogger(result); 608 } 609 return result; 610 } 611 612 613 /** 614 * Sets the parent of this logger in the namespace. Callers must first 615 * {@link #checkAccess() check security}. 616 * 617 * @param newParent 618 * the parent logger to set. 619 */ 620 synchronized void setParent(Logger logger, Logger newParent) { 621 logger.parent = newParent; 622 623 if (logger.levelObjVal == null) { 624 setLevelRecursively(logger, null); 625 } 626 newParent.children.add(logger); 627 logger.updateDalvikLogHandler(); // android-only 628 } 629 630 /** 631 * Sets the level on {@code logger} to {@code newLevel}. Any child loggers 632 * currently inheriting their level from {@code logger} will be updated 633 * recursively. 634 * 635 * @param newLevel the new minimum logging threshold. If null, the logger's 636 * parent level will be used; or {@code Level.INFO} for loggers with no 637 * parent. 638 */ 639 synchronized void setLevelRecursively(Logger logger, Level newLevel) { 640 int previous = logger.levelIntVal; 641 logger.levelObjVal = newLevel; 642 643 if (newLevel == null) { 644 logger.levelIntVal = logger.parent != null 645 ? logger.parent.levelIntVal 646 : Level.INFO.intValue(); 647 } else { 648 logger.levelIntVal = newLevel.intValue(); 649 } 650 651 if (previous != logger.levelIntVal) { 652 for (Logger child : logger.children) { 653 if (child.levelObjVal == null) { 654 setLevelRecursively(child, null); 655 } 656 } 657 } 658 } 659} 660