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 18 package java.util.logging; 19 20 import java.beans.PropertyChangeListener; 21 import java.beans.PropertyChangeSupport; 22 import java.io.BufferedInputStream; 23 import java.io.File; 24 import java.io.FileInputStream; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.util.Collection; 28 import java.util.Enumeration; 29 import java.util.Hashtable; 30 import java.util.Properties; 31 import java.util.StringTokenizer; 32 import libcore.io.IoUtils; 33 34 /** 35 * {@code LogManager} is used to maintain configuration properties of the 36 * logging framework, and to manage a hierarchical namespace of all named 37 * {@code Logger} objects. 38 * <p> 39 * There is only one global {@code LogManager} instance in the 40 * application, which can be get by calling static method 41 * {@link #getLogManager()}. This instance is created and 42 * initialized during class initialization and cannot be changed. 43 * <p> 44 * The {@code LogManager} class can be specified by 45 * java.util.logging.manager system property, if the property is unavailable or 46 * invalid, the default class {@link java.util.logging.LogManager} will 47 * be used. 48 * <p> 49 * On initialization, {@code LogManager} reads its configuration from a 50 * properties file, which by default is the "lib/logging.properties" in the JRE 51 * directory. 52 * <p> 53 * However, two optional system properties can be used to customize the initial 54 * configuration process of {@code LogManager}. 55 * <ul> 56 * <li>"java.util.logging.config.class"</li> 57 * <li>"java.util.logging.config.file"</li> 58 * </ul> 59 * <p> 60 * These two properties can be set in three ways, by the Preferences API, by the 61 * "java" command line property definitions, or by system property definitions 62 * passed to JNI_CreateJavaVM. 63 * <p> 64 * The "java.util.logging.config.class" should specifies a class name. If it is 65 * set, this given class will be loaded and instantiated during 66 * {@code LogManager} initialization, so that this object's default 67 * constructor can read the initial configuration and define properties for 68 * {@code LogManager}. 69 * <p> 70 * If "java.util.logging.config.class" property is not set, or it is invalid, or 71 * some exception is thrown during the instantiation, then the 72 * "java.util.logging.config.file" system property can be used to specify a 73 * properties file. The {@code LogManager} will read initial 74 * configuration from this file. 75 * <p> 76 * If neither of these properties is defined, or some exception is thrown 77 * during these two properties using, the {@code LogManager} will read 78 * its initial configuration from default properties file, as described above. 79 * <p> 80 * The global logging properties may include: 81 * <ul> 82 * <li>"handlers". This property's values should be a list of class names for 83 * handler classes separated by whitespace, these classes must be subclasses of 84 * {@code Handler} and each must have a default constructor, these 85 * classes will be loaded, instantiated and registered as handlers on the root 86 * {@code Logger} (the {@code Logger} named ""). These 87 * {@code Handler}s maybe initialized lazily.</li> 88 * <li>"config". The property defines a list of class names separated by 89 * whitespace. Each class must have a default constructor, in which it can 90 * update the logging configuration, such as levels, handlers, or filters for 91 * some logger, etc. These classes will be loaded and instantiated during 92 * {@code LogManager} configuration</li> 93 * </ul> 94 * <p> 95 * This class, together with any handler and configuration classes associated 96 * with it, <b>must</b> be loaded from the system classpath when 97 * {@code LogManager} configuration occurs. 98 * <p> 99 * Besides global properties, the properties for loggers and Handlers can be 100 * specified in the property files. The names of these properties will start 101 * with the complete dot separated names for the handlers or loggers. 102 * <p> 103 * In the {@code LogManager}'s hierarchical namespace, 104 * {@code Loggers} are organized based on their dot separated names. For 105 * example, "x.y.z" is child of "x.y". 106 * <p> 107 * Levels for {@code Loggers} can be defined by properties whose name end 108 * with ".level". Thus "alogger.level" defines a level for the logger named as 109 * "alogger" and for all its children in the naming hierarchy. Log levels 110 * properties are read and applied in the same order as they are specified in 111 * the property file. The root logger's level can be defined by the property 112 * named as ".level". 113 * <p> 114 * This class is thread safe. It is an error to synchronize on a 115 * {@code LogManager} while synchronized on a {@code Logger}. 116 */ 117 public class LogManager { 118 119 /** The shared logging permission. */ 120 private static final LoggingPermission perm = new LoggingPermission("control", null); 121 122 /** The singleton instance. */ 123 static LogManager manager; 124 125 /** 126 * The {@code String} value of the {@link LoggingMXBean}'s ObjectName. 127 */ 128 public static final String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging"; 129 130 /** 131 * Get the {@code LoggingMXBean} instance. this implementation always throws 132 * an UnsupportedOperationException. 133 * 134 * @return the {@code LoggingMXBean} instance 135 */ 136 public static LoggingMXBean getLoggingMXBean() { 137 throw new UnsupportedOperationException(); 138 } 139 140 // FIXME: use weak reference to avoid heap memory leak 141 private Hashtable<String, Logger> loggers; 142 143 /** The configuration properties */ 144 private Properties props; 145 146 /** the property change listener */ 147 private PropertyChangeSupport listeners; 148 149 static { 150 // init LogManager singleton instance 151 String className = System.getProperty("java.util.logging.manager"); 152 if (className != null) { 153 manager = (LogManager) getInstanceByClass(className); 154 } 155 if (manager == null) { 156 manager = new LogManager(); 157 } 158 159 // read configuration 160 try { 161 manager.readConfiguration(); 162 } catch (Exception e) { 163 e.printStackTrace(); 164 } 165 166 // if global logger has been initialized, set root as its parent 167 Logger root = new Logger("", null); 168 root.setLevel(Level.INFO); 169 Logger.global.setParent(root); 170 171 manager.addLogger(root); 172 manager.addLogger(Logger.global); 173 } 174 175 /** 176 * Default constructor. This is not public because there should be only one 177 * {@code LogManager} instance, which can be get by 178 * {@code LogManager.getLogManager()}. This is protected so that 179 * application can subclass the object. 180 */ 181 protected LogManager() { 182 loggers = new Hashtable<String, Logger>(); 183 props = new Properties(); 184 listeners = new PropertyChangeSupport(this); 185 // add shutdown hook to ensure that the associated resource will be 186 // freed when JVM exits 187 Runtime.getRuntime().addShutdownHook(new Thread() { 188 @Override public void run() { 189 reset(); 190 } 191 }); 192 } 193 194 /** 195 * Does nothing. 196 */ 197 public void checkAccess() { 198 } 199 200 /** 201 * Add a given logger into the hierarchical namespace. The 202 * {@code Logger.addLogger()} factory methods call this method to add newly 203 * created Logger. This returns false if a logger with the given name has 204 * existed in the namespace 205 * <p> 206 * Note that the {@code LogManager} may only retain weak references to 207 * registered loggers. In order to prevent {@code Logger} objects from being 208 * unexpectedly garbage collected it is necessary for <i>applications</i> 209 * to maintain references to them. 210 * </p> 211 * 212 * @param logger 213 * the logger to be added. 214 * @return true if the given logger is added into the namespace 215 * successfully, false if the given logger exists in the namespace. 216 */ 217 public synchronized boolean addLogger(Logger logger) { 218 String name = logger.getName(); 219 if (loggers.get(name) != null) { 220 return false; 221 } 222 addToFamilyTree(logger, name); 223 loggers.put(name, logger); 224 logger.setManager(this); 225 return true; 226 } 227 228 private void addToFamilyTree(Logger logger, String name) { 229 Logger parent = null; 230 // find parent 231 int lastSeparator; 232 String parentName = name; 233 while ((lastSeparator = parentName.lastIndexOf('.')) != -1) { 234 parentName = parentName.substring(0, lastSeparator); 235 parent = loggers.get(parentName); 236 if (parent != null) { 237 setParent(logger, parent); 238 break; 239 } else if (getProperty(parentName + ".level") != null || 240 getProperty(parentName + ".handlers") != null) { 241 parent = Logger.getLogger(parentName); 242 setParent(logger, parent); 243 break; 244 } 245 } 246 if (parent == null && (parent = loggers.get("")) != null) { 247 setParent(logger, parent); 248 } 249 250 // find children 251 // TODO: performance can be improved here? 252 String nameDot = name + '.'; 253 Collection<Logger> allLoggers = loggers.values(); 254 for (final Logger child : allLoggers) { 255 Logger oldParent = child.getParent(); 256 if (parent == oldParent && (name.length() == 0 || child.getName().startsWith(nameDot))) { 257 final Logger thisLogger = logger; 258 child.setParent(thisLogger); 259 if (oldParent != null) { 260 // -- remove from old parent as the parent has been changed 261 oldParent.children.remove(child); 262 } 263 } 264 } 265 } 266 267 /** 268 * Get the logger with the given name. 269 * 270 * @param name 271 * name of logger 272 * @return logger with given name, or {@code null} if nothing is found. 273 */ 274 public synchronized Logger getLogger(String name) { 275 return loggers.get(name); 276 } 277 278 /** 279 * Get a {@code Enumeration} of all registered logger names. 280 * 281 * @return enumeration of registered logger names 282 */ 283 public synchronized Enumeration<String> getLoggerNames() { 284 return loggers.keys(); 285 } 286 287 /** 288 * Get the global {@code LogManager} instance. 289 * 290 * @return the global {@code LogManager} instance 291 */ 292 public static LogManager getLogManager() { 293 return manager; 294 } 295 296 /** 297 * Get the value of property with given name. 298 * 299 * @param name 300 * the name of property 301 * @return the value of property 302 */ 303 public String getProperty(String name) { 304 return props.getProperty(name); 305 } 306 307 /** 308 * Re-initialize the properties and configuration. The initialization 309 * process is same as the {@code LogManager} instantiation. 310 * <p> 311 * Notice : No {@code PropertyChangeEvent} are fired. 312 * </p> 313 * 314 * @throws IOException 315 * if any IO related problems happened. 316 */ 317 public void readConfiguration() throws IOException { 318 // check config class 319 String configClassName = System.getProperty("java.util.logging.config.class"); 320 if (configClassName == null || getInstanceByClass(configClassName) == null) { 321 // if config class failed, check config file 322 String configFile = System.getProperty("java.util.logging.config.file"); 323 324 if (configFile == null) { 325 // if cannot find configFile, use default logging.properties 326 configFile = System.getProperty("java.home") + File.separator + "lib" + 327 File.separator + "logging.properties"; 328 } 329 330 InputStream input = null; 331 try { 332 try { 333 input = new FileInputStream(configFile); 334 } catch (IOException exception) { 335 // fall back to using the built-in logging.properties file 336 input = LogManager.class.getResourceAsStream("logging.properties"); 337 if (input == null) { 338 throw exception; 339 } 340 } 341 readConfiguration(new BufferedInputStream(input)); 342 } finally { 343 IoUtils.closeQuietly(input); 344 } 345 } 346 } 347 348 // use SystemClassLoader to load class from system classpath 349 static Object getInstanceByClass(final String className) { 350 try { 351 Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass(className); 352 return clazz.newInstance(); 353 } catch (Exception e) { 354 try { 355 Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className); 356 return clazz.newInstance(); 357 } catch (Exception innerE) { 358 System.err.println("Loading class '" + className + "' failed"); 359 System.err.println(innerE); 360 return null; 361 } 362 } 363 } 364 365 // actual initialization process from a given input stream 366 private synchronized void readConfigurationImpl(InputStream ins) 367 throws IOException { 368 reset(); 369 props.load(ins); 370 371 // The RI treats the root logger as special. For compatibility, always 372 // update the root logger's handlers. 373 Logger root = loggers.get(""); 374 if (root != null) { 375 root.setManager(this); 376 } 377 378 // parse property "config" and apply setting 379 String configs = props.getProperty("config"); 380 if (configs != null) { 381 StringTokenizer st = new StringTokenizer(configs, " "); 382 while (st.hasMoreTokens()) { 383 String configerName = st.nextToken(); 384 getInstanceByClass(configerName); 385 } 386 } 387 388 // set levels for logger 389 Collection<Logger> allLoggers = loggers.values(); 390 for (Logger logger : allLoggers) { 391 String property = props.getProperty(logger.getName() + ".level"); 392 if (property != null) { 393 logger.setLevel(Level.parse(property)); 394 } 395 } 396 listeners.firePropertyChange(null, null, null); 397 } 398 399 /** 400 * Re-initialize the properties and configuration from the given 401 * {@code InputStream} 402 * <p> 403 * Notice : No {@code PropertyChangeEvent} are fired. 404 * </p> 405 * 406 * @param ins 407 * the input stream 408 * @throws IOException 409 * if any IO related problems happened. 410 */ 411 public void readConfiguration(InputStream ins) throws IOException { 412 checkAccess(); 413 readConfigurationImpl(ins); 414 } 415 416 /** 417 * Reset configuration. 418 * 419 * <p>All handlers are closed and removed from any named loggers. All loggers' 420 * level is set to null, except the root logger's level is set to 421 * {@code Level.INFO}. 422 */ 423 public synchronized void reset() { 424 checkAccess(); 425 props = new Properties(); 426 Enumeration<String> names = getLoggerNames(); 427 while (names.hasMoreElements()) { 428 String name = names.nextElement(); 429 Logger logger = getLogger(name); 430 if (logger != null) { 431 logger.reset(); 432 } 433 } 434 Logger root = loggers.get(""); 435 if (root != null) { 436 root.setLevel(Level.INFO); 437 } 438 } 439 440 /** 441 * Add a {@code PropertyChangeListener}, which will be invoked when 442 * the properties are reread. 443 * 444 * @param l 445 * the {@code PropertyChangeListener} to be added. 446 */ 447 public void addPropertyChangeListener(PropertyChangeListener l) { 448 if (l == null) { 449 throw new NullPointerException("l == null"); 450 } 451 checkAccess(); 452 listeners.addPropertyChangeListener(l); 453 } 454 455 /** 456 * Remove a {@code PropertyChangeListener}, do nothing if the given 457 * listener is not found. 458 * 459 * @param l 460 * the {@code PropertyChangeListener} to be removed. 461 */ 462 public void removePropertyChangeListener(PropertyChangeListener l) { 463 checkAccess(); 464 listeners.removePropertyChangeListener(l); 465 } 466 467 /** 468 * Returns a named logger associated with the supplied resource bundle. 469 * 470 * @param resourceBundleName the resource bundle to associate, or null for 471 * no associated resource bundle. 472 */ 473 synchronized Logger getOrCreate(String name, String resourceBundleName) { 474 Logger result = getLogger(name); 475 if (result == null) { 476 result = new Logger(name, resourceBundleName); 477 addLogger(result); 478 } 479 return result; 480 } 481 482 483 /** 484 * Sets the parent of this logger in the namespace. Callers must first 485 * {@link #checkAccess() check security}. 486 * 487 * @param newParent 488 * the parent logger to set. 489 */ 490 synchronized void setParent(Logger logger, Logger newParent) { 491 logger.parent = newParent; 492 493 if (logger.levelObjVal == null) { 494 setLevelRecursively(logger, null); 495 } 496 newParent.children.add(logger); 497 logger.updateDalvikLogHandler(); 498 } 499 500 /** 501 * Sets the level on {@code logger} to {@code newLevel}. Any child loggers 502 * currently inheriting their level from {@code logger} will be updated 503 * recursively. 504 * 505 * @param newLevel the new minimum logging threshold. If null, the logger's 506 * parent level will be used; or {@code Level.INFO} for loggers with no 507 * parent. 508 */ 509 synchronized void setLevelRecursively(Logger logger, Level newLevel) { 510 int previous = logger.levelIntVal; 511 logger.levelObjVal = newLevel; 512 513 if (newLevel == null) { 514 logger.levelIntVal = logger.parent != null 515 ? logger.parent.levelIntVal 516 : Level.INFO.intValue(); 517 } else { 518 logger.levelIntVal = newLevel.intValue(); 519 } 520 521 if (previous != logger.levelIntVal) { 522 for (Logger child : logger.children) { 523 if (child.levelObjVal == null) { 524 setLevelRecursively(child, null); 525 } 526 } 527 } 528 } 529 } 530