1 /* 2 * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.logging; 27 28 import java.io.*; 29 import java.util.*; 30 import java.security.*; 31 import java.lang.ref.ReferenceQueue; 32 import java.lang.ref.WeakReference; 33 import java.util.concurrent.ConcurrentHashMap; 34 import java.nio.file.Paths; 35 import java.util.concurrent.CopyOnWriteArrayList; 36 import java.util.concurrent.locks.ReentrantLock; 37 import java.util.function.BiFunction; 38 import java.util.function.Function; 39 import java.util.function.Predicate; 40 import java.util.stream.Collectors; 41 import java.util.stream.Stream; 42 import sun.util.logging.internal.LoggingProviderImpl; 43 import static jdk.internal.logger.DefaultLoggerFinder.isSystem; 44 45 /** 46 * There is a single global LogManager object that is used to 47 * maintain a set of shared state about Loggers and log services. 48 * <p> 49 * This LogManager object: 50 * <ul> 51 * <li> Manages a hierarchical namespace of Logger objects. All 52 * named Loggers are stored in this namespace. 53 * <li> Manages a set of logging control properties. These are 54 * simple key-value pairs that can be used by Handlers and 55 * other logging objects to configure themselves. 56 * </ul> 57 * <p> 58 * The global LogManager object can be retrieved using LogManager.getLogManager(). 59 * The LogManager object is created during class initialization and 60 * cannot subsequently be changed. 61 * <p> 62 * At startup the LogManager class is located using the 63 * java.util.logging.manager system property. 64 * 65 * <h2>LogManager Configuration</h2> 66 * 67 * A LogManager initializes the logging configuration via 68 * the {@link #readConfiguration()} method during LogManager initialization. 69 * By default, LogManager default configuration is used. 70 * The logging configuration read by LogManager must be in the 71 * {@linkplain Properties properties file} format. 72 * <p> 73 * The LogManager defines two optional system properties that allow control over 74 * the initial configuration, as specified in the {@link #readConfiguration()} 75 * method: 76 * <ul> 77 * <li>{@systemProperty java.util.logging.config.class} 78 * <li>{@systemProperty java.util.logging.config.file} 79 * </ul> 80 * <p> 81 * These two system properties may be specified on the command line to the "java" 82 * command, or as system property definitions passed to JNI_CreateJavaVM. 83 * <p> 84 * The {@linkplain Properties properties} for loggers and Handlers will have 85 * names starting with the dot-separated name for the handler or logger.<br> 86 * The global logging properties may include: 87 * <ul> 88 * <li>A property "handlers". This defines a whitespace or comma separated 89 * list of class names for handler classes to load and register as 90 * handlers on the root Logger (the Logger named ""). Each class 91 * name must be for a Handler class which has a default constructor. 92 * Note that these Handlers may be created lazily, when they are 93 * first used. 94 * 95 * <li>A property "<logger>.handlers". This defines a whitespace or 96 * comma separated list of class names for handlers classes to 97 * load and register as handlers to the specified logger. Each class 98 * name must be for a Handler class which has a default constructor. 99 * Note that these Handlers may be created lazily, when they are 100 * first used. 101 * 102 * <li>A property "<logger>.handlers.ensureCloseOnReset". This defines a 103 * a boolean value. If "<logger>.handlers" is not defined or is empty, 104 * this property is ignored. Otherwise it defaults to {@code true}. When the 105 * value is {@code true}, the handlers associated with the logger are guaranteed 106 * to be closed on {@linkplain #reset} and shutdown. This can be turned off 107 * by explicitly setting "<logger>.handlers.ensureCloseOnReset=false" in 108 * the configuration. Note that turning this property off causes the risk of 109 * introducing a resource leak, as the logger may get garbage collected before 110 * {@code reset()} is called, thus preventing its handlers from being closed 111 * on {@code reset()}. In that case it is the responsibility of the application 112 * to ensure that the handlers are closed before the logger is garbage 113 * collected. 114 * 115 * <li>A property "<logger>.useParentHandlers". This defines a boolean 116 * value. By default every logger calls its parent in addition to 117 * handling the logging message itself, this often result in messages 118 * being handled by the root logger as well. When setting this property 119 * to false a Handler needs to be configured for this logger otherwise 120 * no logging messages are delivered. 121 * 122 * <li>A property "config". This property is intended to allow 123 * arbitrary configuration code to be run. The property defines a 124 * whitespace or comma separated list of class names. A new instance will be 125 * created for each named class. The default constructor of each class 126 * may execute arbitrary code to update the logging configuration, such as 127 * setting logger levels, adding handlers, adding filters, etc. 128 * </ul> 129 * <p> 130 * Note that all classes loaded during LogManager configuration are 131 * first searched on the system class path before any user class path. 132 * That includes the LogManager class, any config classes, and any 133 * handler classes. 134 * <p> 135 * Loggers are organized into a naming hierarchy based on their 136 * dot separated names. Thus "a.b.c" is a child of "a.b", but 137 * "a.b1" and a.b2" are peers. 138 * <p> 139 * All properties whose names end with ".level" are assumed to define 140 * log levels for Loggers. Thus "foo.level" defines a log level for 141 * the logger called "foo" and (recursively) for any of its children 142 * in the naming hierarchy. Log Levels are applied in the order they 143 * are defined in the properties file. Thus level settings for child 144 * nodes in the tree should come after settings for their parents. 145 * The property name ".level" can be used to set the level for the 146 * root of the tree. 147 * <p> 148 * All methods on the LogManager object are multi-thread safe. 149 * 150 * @since 1.4 151 */ 152 153 public class LogManager { 154 // The global LogManager object 155 private static final LogManager manager; 156 157 // 'props' is assigned within a lock but accessed without it. 158 // Declaring it volatile makes sure that another thread will not 159 // be able to see a partially constructed 'props' object. 160 // (seeing a partially constructed 'props' object can result in 161 // NPE being thrown in Hashtable.get(), because it leaves the door 162 // open for props.getProperties() to be called before the construcor 163 // of Hashtable is actually completed). 164 private volatile Properties props = new Properties(); 165 private final static Level defaultLevel = Level.INFO; 166 167 // LoggerContext for system loggers and user loggers 168 private final LoggerContext systemContext = new SystemLoggerContext(); 169 private final LoggerContext userContext = new LoggerContext(); 170 // non final field - make it volatile to make sure that other threads 171 // will see the new value once ensureLogManagerInitialized() has finished 172 // executing. 173 private volatile Logger rootLogger; 174 // Have we done the primordial reading of the configuration file? 175 // (Must be done after a suitable amount of java.lang.System 176 // initialization has been done) 177 private volatile boolean readPrimordialConfiguration; 178 // Have we initialized global (root) handlers yet? 179 // This gets set to STATE_UNINITIALIZED in readConfiguration 180 private static final int 181 STATE_INITIALIZED = 0, // initial state 182 STATE_INITIALIZING = 1, 183 STATE_READING_CONFIG = 2, 184 STATE_UNINITIALIZED = 3, 185 STATE_SHUTDOWN = 4; // terminal state 186 private volatile int globalHandlersState; // = STATE_INITIALIZED; 187 // A concurrency lock for reset(), readConfiguration() and Cleaner. 188 private final ReentrantLock configurationLock = new ReentrantLock(); 189 190 // This list contains the loggers for which some handlers have been 191 // explicitly configured in the configuration file. 192 // It prevents these loggers from being arbitrarily garbage collected. 193 private static final class CloseOnReset { 194 private final Logger logger; 195 private CloseOnReset(Logger ref) { 196 this.logger = Objects.requireNonNull(ref); 197 } 198 @Override 199 public boolean equals(Object other) { 200 return (other instanceof CloseOnReset) && ((CloseOnReset)other).logger == logger; 201 } 202 @Override 203 public int hashCode() { 204 return System.identityHashCode(logger); 205 } 206 public Logger get() { 207 return logger; 208 } 209 public static CloseOnReset create(Logger logger) { 210 return new CloseOnReset(logger); 211 } 212 } 213 private final CopyOnWriteArrayList<CloseOnReset> closeOnResetLoggers = 214 new CopyOnWriteArrayList<>(); 215 216 217 private final Map<Object, Runnable> listeners = 218 Collections.synchronizedMap(new IdentityHashMap<>()); 219 220 static { 221 manager = AccessController.doPrivileged(new PrivilegedAction<LogManager>() { 222 @Override 223 public LogManager run() { 224 LogManager mgr = null; 225 String cname = null; 226 try { 227 cname = System.getProperty("java.util.logging.manager"); 228 if (cname != null) { 229 try { 230 @SuppressWarnings("deprecation") 231 Object tmp = ClassLoader.getSystemClassLoader() 232 .loadClass(cname).newInstance(); 233 mgr = (LogManager) tmp; 234 } catch (ClassNotFoundException ex) { 235 @SuppressWarnings("deprecation") 236 Object tmp = Thread.currentThread() 237 .getContextClassLoader().loadClass(cname).newInstance(); 238 mgr = (LogManager) tmp; 239 } 240 } 241 } catch (Exception ex) { 242 System.err.println("Could not load Logmanager \"" + cname + "\""); 243 ex.printStackTrace(); 244 } 245 if (mgr == null) { 246 mgr = new LogManager(); 247 } 248 return mgr; 249 250 } 251 }); 252 } 253 254 // This private class is used as a shutdown hook. 255 // It does a "reset" to close all open handlers. 256 private class Cleaner extends Thread { 257 258 private Cleaner() { 259 super(null, null, "Logging-Cleaner", 0, false); 260 /* Set context class loader to null in order to avoid 261 * keeping a strong reference to an application classloader. 262 */ 263 this.setContextClassLoader(null); 264 } 265 266 @Override 267 public void run() { 268 // This is to ensure the LogManager.<clinit> is completed 269 // before synchronized block. Otherwise deadlocks are possible. 270 LogManager mgr = manager; 271 272 // set globalHandlersState to STATE_SHUTDOWN atomically so that 273 // no attempts are made to (re)initialize the handlers or (re)read 274 // the configuration again. This is terminal state. 275 configurationLock.lock(); 276 globalHandlersState = STATE_SHUTDOWN; 277 configurationLock.unlock(); 278 279 // Do a reset to close all active handlers. 280 reset(); 281 } 282 } 283 284 285 /** 286 * Protected constructor. This is protected so that container applications 287 * (such as J2EE containers) can subclass the object. It is non-public as 288 * it is intended that there only be one LogManager object, whose value is 289 * retrieved by calling LogManager.getLogManager. 290 */ 291 protected LogManager() { 292 this(checkSubclassPermissions()); 293 } 294 295 private LogManager(Void checked) { 296 297 // Add a shutdown hook to close the global handlers. 298 try { 299 Runtime.getRuntime().addShutdownHook(new Cleaner()); 300 } catch (IllegalStateException e) { 301 // If the VM is already shutting down, 302 // We do not need to register shutdownHook. 303 } 304 } 305 306 private static Void checkSubclassPermissions() { 307 final SecurityManager sm = System.getSecurityManager(); 308 if (sm != null) { 309 // These permission will be checked in the LogManager constructor, 310 // in order to register the Cleaner() thread as a shutdown hook. 311 // Check them here to avoid the penalty of constructing the object 312 // etc... 313 sm.checkPermission(new RuntimePermission("shutdownHooks")); 314 sm.checkPermission(new RuntimePermission("setContextClassLoader")); 315 } 316 return null; 317 } 318 319 /** 320 * Lazy initialization: if this instance of manager is the global 321 * manager then this method will read the initial configuration and 322 * add the root logger and global logger by calling addLogger(). 323 * 324 * Note that it is subtly different from what we do in LoggerContext. 325 * In LoggerContext we're patching up the logger context tree in order to add 326 * the root and global logger *to the context tree*. 327 * 328 * For this to work, addLogger() must have already have been called 329 * once on the LogManager instance for the default logger being 330 * added. 331 * 332 * This is why ensureLogManagerInitialized() needs to be called before 333 * any logger is added to any logger context. 334 * 335 */ 336 private boolean initializedCalled = false; 337 private volatile boolean initializationDone = false; 338 final void ensureLogManagerInitialized() { 339 final LogManager owner = this; 340 if (initializationDone || owner != manager) { 341 // we don't want to do this twice, and we don't want to do 342 // this on private manager instances. 343 return; 344 } 345 346 // Maybe another thread has called ensureLogManagerInitialized() 347 // before us and is still executing it. If so we will block until 348 // the log manager has finished initialized, then acquire the monitor, 349 // notice that initializationDone is now true and return. 350 // Otherwise - we have come here first! We will acquire the monitor, 351 // see that initializationDone is still false, and perform the 352 // initialization. 353 // 354 configurationLock.lock(); 355 try { 356 // If initializedCalled is true it means that we're already in 357 // the process of initializing the LogManager in this thread. 358 // There has been a recursive call to ensureLogManagerInitialized(). 359 final boolean isRecursiveInitialization = (initializedCalled == true); 360 361 assert initializedCalled || !initializationDone 362 : "Initialization can't be done if initialized has not been called!"; 363 364 if (isRecursiveInitialization || initializationDone) { 365 // If isRecursiveInitialization is true it means that we're 366 // already in the process of initializing the LogManager in 367 // this thread. There has been a recursive call to 368 // ensureLogManagerInitialized(). We should not proceed as 369 // it would lead to infinite recursion. 370 // 371 // If initializationDone is true then it means the manager 372 // has finished initializing; just return: we're done. 373 return; 374 } 375 // Calling addLogger below will in turn call requiresDefaultLogger() 376 // which will call ensureLogManagerInitialized(). 377 // We use initializedCalled to break the recursion. 378 initializedCalled = true; 379 try { 380 AccessController.doPrivileged(new PrivilegedAction<Object>() { 381 @Override 382 public Object run() { 383 assert rootLogger == null; 384 assert initializedCalled && !initializationDone; 385 386 // create root logger before reading primordial 387 // configuration - to ensure that it will be added 388 // before the global logger, and not after. 389 final Logger root = owner.rootLogger = owner.new RootLogger(); 390 391 // Read configuration. 392 owner.readPrimordialConfiguration(); 393 394 // Create and retain Logger for the root of the namespace. 395 owner.addLogger(root); 396 397 // Initialize level if not yet initialized 398 if (!root.isLevelInitialized()) { 399 root.setLevel(defaultLevel); 400 } 401 402 // Adding the global Logger. 403 // Do not call Logger.getGlobal() here as this might trigger 404 // subtle inter-dependency issues. 405 @SuppressWarnings("deprecation") 406 final Logger global = Logger.global; 407 408 // Make sure the global logger will be registered in the 409 // global manager 410 owner.addLogger(global); 411 return null; 412 } 413 }); 414 } finally { 415 initializationDone = true; 416 } 417 } finally { 418 configurationLock.unlock(); 419 } 420 } 421 422 /** 423 * Returns the global LogManager object. 424 * @return the global LogManager object 425 */ 426 public static LogManager getLogManager() { 427 if (manager != null) { 428 manager.ensureLogManagerInitialized(); 429 } 430 return manager; 431 } 432 433 private void readPrimordialConfiguration() { // must be called while holding configurationLock 434 if (!readPrimordialConfiguration) { 435 // If System.in/out/err are null, it's a good 436 // indication that we're still in the 437 // bootstrapping phase 438 if (System.out == null) { 439 return; 440 } 441 readPrimordialConfiguration = true; 442 try { 443 readConfiguration(); 444 445 // Platform loggers begin to delegate to java.util.logging.Logger 446 jdk.internal.logger.BootstrapLogger.redirectTemporaryLoggers(); 447 448 } catch (Exception ex) { 449 assert false : "Exception raised while reading logging configuration: " + ex; 450 } 451 } 452 } 453 454 // Returns the LoggerContext for the user code (i.e. application). 455 private LoggerContext getUserContext() { 456 return userContext; 457 } 458 459 // The system context. 460 final LoggerContext getSystemContext() { 461 return systemContext; 462 } 463 464 private List<LoggerContext> contexts() { 465 List<LoggerContext> cxs = new ArrayList<>(); 466 cxs.add(getSystemContext()); 467 cxs.add(getUserContext()); 468 return cxs; 469 } 470 471 // Find or create a specified logger instance. If a logger has 472 // already been created with the given name it is returned. 473 // Otherwise a new logger instance is created and registered 474 // in the LogManager global namespace. 475 // This method will always return a non-null Logger object. 476 // Synchronization is not required here. All synchronization for 477 // adding a new Logger object is handled by addLogger(). 478 // 479 // This method must delegate to the LogManager implementation to 480 // add a new Logger or return the one that has been added previously 481 // as a LogManager subclass may override the addLogger, getLogger, 482 // readConfiguration, and other methods. 483 Logger demandLogger(String name, String resourceBundleName, Class<?> caller) { 484 final Module module = caller == null ? null : caller.getModule(); 485 return demandLogger(name, resourceBundleName, module); 486 } 487 488 Logger demandLogger(String name, String resourceBundleName, Module module) { 489 Logger result = getLogger(name); 490 if (result == null) { 491 // only allocate the new logger once 492 Logger newLogger = new Logger(name, resourceBundleName, 493 module, this, false); 494 do { 495 if (addLogger(newLogger)) { 496 // We successfully added the new Logger that we 497 // created above so return it without refetching. 498 return newLogger; 499 } 500 501 // We didn't add the new Logger that we created above 502 // because another thread added a Logger with the same 503 // name after our null check above and before our call 504 // to addLogger(). We have to refetch the Logger because 505 // addLogger() returns a boolean instead of the Logger 506 // reference itself. However, if the thread that created 507 // the other Logger is not holding a strong reference to 508 // the other Logger, then it is possible for the other 509 // Logger to be GC'ed after we saw it in addLogger() and 510 // before we can refetch it. If it has been GC'ed then 511 // we'll just loop around and try again. 512 result = getLogger(name); 513 } while (result == null); 514 } 515 return result; 516 } 517 518 Logger demandSystemLogger(String name, String resourceBundleName, Class<?> caller) { 519 final Module module = caller == null ? null : caller.getModule(); 520 return demandSystemLogger(name, resourceBundleName, module); 521 } 522 523 Logger demandSystemLogger(String name, String resourceBundleName, Module module) { 524 // Add a system logger in the system context's namespace 525 final Logger sysLogger = getSystemContext() 526 .demandLogger(name, resourceBundleName, module); 527 528 // Add the system logger to the LogManager's namespace if not exist 529 // so that there is only one single logger of the given name. 530 // System loggers are visible to applications unless a logger of 531 // the same name has been added. 532 Logger logger; 533 do { 534 // First attempt to call addLogger instead of getLogger 535 // This would avoid potential bug in custom LogManager.getLogger 536 // implementation that adds a logger if does not exist 537 if (addLogger(sysLogger)) { 538 // successfully added the new system logger 539 logger = sysLogger; 540 } else { 541 logger = getLogger(name); 542 } 543 } while (logger == null); 544 545 // LogManager will set the sysLogger's handlers via LogManager.addLogger method. 546 if (logger != sysLogger) { 547 // if logger already exists we merge the two logger configurations. 548 final Logger l = logger; 549 AccessController.doPrivileged(new PrivilegedAction<Void>() { 550 @Override 551 public Void run() { 552 l.mergeWithSystemLogger(sysLogger); 553 return null; 554 } 555 }); 556 } 557 return sysLogger; 558 } 559 560 // LoggerContext maintains the logger namespace per context. 561 // The default LogManager implementation has one system context and user 562 // context. The system context is used to maintain the namespace for 563 // all system loggers and is queried by the system code. If a system logger 564 // doesn't exist in the user context, it'll also be added to the user context. 565 // The user context is queried by the user code and all other loggers are 566 // added in the user context. 567 class LoggerContext { 568 // Table of named Loggers that maps names to Loggers. 569 private final ConcurrentHashMap<String,LoggerWeakRef> namedLoggers = 570 new ConcurrentHashMap<>(); 571 // Tree of named Loggers 572 private final LogNode root; 573 private LoggerContext() { 574 this.root = new LogNode(null, this); 575 } 576 577 578 // Tells whether default loggers are required in this context. 579 // If true, the default loggers will be lazily added. 580 final boolean requiresDefaultLoggers() { 581 final boolean requiresDefaultLoggers = (getOwner() == manager); 582 if (requiresDefaultLoggers) { 583 getOwner().ensureLogManagerInitialized(); 584 } 585 return requiresDefaultLoggers; 586 } 587 588 // This context's LogManager. 589 final LogManager getOwner() { 590 return LogManager.this; 591 } 592 593 // This context owner's root logger, which if not null, and if 594 // the context requires default loggers, will be added to the context 595 // logger's tree. 596 final Logger getRootLogger() { 597 return getOwner().rootLogger; 598 } 599 600 // The global logger, which if not null, and if 601 // the context requires default loggers, will be added to the context 602 // logger's tree. 603 final Logger getGlobalLogger() { 604 @SuppressWarnings("deprecation") // avoids initialization cycles. 605 final Logger global = Logger.global; 606 return global; 607 } 608 609 Logger demandLogger(String name, String resourceBundleName, Module module) { 610 // a LogManager subclass may have its own implementation to add and 611 // get a Logger. So delegate to the LogManager to do the work. 612 final LogManager owner = getOwner(); 613 return owner.demandLogger(name, resourceBundleName, module); 614 } 615 616 617 // Due to subtle deadlock issues getUserContext() no longer 618 // calls addLocalLogger(rootLogger); 619 // Therefore - we need to add the default loggers later on. 620 // Checks that the context is properly initialized 621 // This is necessary before calling e.g. find(name) 622 // or getLoggerNames() 623 // 624 private void ensureInitialized() { 625 if (requiresDefaultLoggers()) { 626 // Ensure that the root and global loggers are set. 627 ensureDefaultLogger(getRootLogger()); 628 ensureDefaultLogger(getGlobalLogger()); 629 } 630 } 631 632 633 Logger findLogger(String name) { 634 // Attempt to find logger without locking. 635 LoggerWeakRef ref = namedLoggers.get(name); 636 Logger logger = ref == null ? null : ref.get(); 637 638 // if logger is not null, then we can return it right away. 639 // if name is "" or "global" and logger is null 640 // we need to fall through and check that this context is 641 // initialized. 642 // if ref is not null and logger is null we also need to 643 // fall through. 644 if (logger != null || (ref == null && !name.isEmpty() 645 && !name.equals(Logger.GLOBAL_LOGGER_NAME))) { 646 return logger; 647 } 648 649 // We either found a stale reference, or we were looking for 650 // "" or "global" and didn't find them. 651 // Make sure context is initialized (has the default loggers), 652 // and look up again, cleaning the stale reference if it hasn't 653 // been cleaned up in between. All this needs to be done inside 654 // a synchronized block. 655 synchronized(this) { 656 // ensure that this context is properly initialized before 657 // looking for loggers. 658 ensureInitialized(); 659 ref = namedLoggers.get(name); 660 if (ref == null) { 661 return null; 662 } 663 logger = ref.get(); 664 if (logger == null) { 665 // The namedLoggers map holds stale weak reference 666 // to a logger which has been GC-ed. 667 ref.dispose(); 668 } 669 return logger; 670 } 671 } 672 673 // This method is called before adding a logger to the 674 // context. 675 // 'logger' is the context that will be added. 676 // This method will ensure that the defaults loggers are added 677 // before adding 'logger'. 678 // 679 private void ensureAllDefaultLoggers(Logger logger) { 680 if (requiresDefaultLoggers()) { 681 final String name = logger.getName(); 682 if (!name.isEmpty()) { 683 ensureDefaultLogger(getRootLogger()); 684 if (!Logger.GLOBAL_LOGGER_NAME.equals(name)) { 685 ensureDefaultLogger(getGlobalLogger()); 686 } 687 } 688 } 689 } 690 691 private void ensureDefaultLogger(Logger logger) { 692 // Used for lazy addition of root logger and global logger 693 // to a LoggerContext. 694 695 // This check is simple sanity: we do not want that this 696 // method be called for anything else than Logger.global 697 // or owner.rootLogger. 698 if (!requiresDefaultLoggers() || logger == null 699 || logger != getGlobalLogger() && logger != LogManager.this.rootLogger ) { 700 701 // the case where we have a non null logger which is neither 702 // Logger.global nor manager.rootLogger indicates a serious 703 // issue - as ensureDefaultLogger should never be called 704 // with any other loggers than one of these two (or null - if 705 // e.g manager.rootLogger is not yet initialized)... 706 assert logger == null; 707 708 return; 709 } 710 711 // Adds the logger if it's not already there. 712 if (!namedLoggers.containsKey(logger.getName())) { 713 // It is important to prevent addLocalLogger to 714 // call ensureAllDefaultLoggers when we're in the process 715 // off adding one of those default loggers - as this would 716 // immediately cause a stack overflow. 717 // Therefore we must pass addDefaultLoggersIfNeeded=false, 718 // even if requiresDefaultLoggers is true. 719 addLocalLogger(logger, false); 720 } 721 } 722 723 boolean addLocalLogger(Logger logger) { 724 // no need to add default loggers if it's not required 725 return addLocalLogger(logger, requiresDefaultLoggers()); 726 } 727 728 // Add a logger to this context. This method will only set its level 729 // and process parent loggers. It doesn't set its handlers. 730 synchronized boolean addLocalLogger(Logger logger, boolean addDefaultLoggersIfNeeded) { 731 // addDefaultLoggersIfNeeded serves to break recursion when adding 732 // default loggers. If we're adding one of the default loggers 733 // (we're being called from ensureDefaultLogger()) then 734 // addDefaultLoggersIfNeeded will be false: we don't want to 735 // call ensureAllDefaultLoggers again. 736 // 737 // Note: addDefaultLoggersIfNeeded can also be false when 738 // requiresDefaultLoggers is false - since calling 739 // ensureAllDefaultLoggers would have no effect in this case. 740 if (addDefaultLoggersIfNeeded) { 741 ensureAllDefaultLoggers(logger); 742 } 743 744 final String name = logger.getName(); 745 if (name == null) { 746 throw new NullPointerException(); 747 } 748 LoggerWeakRef ref = namedLoggers.get(name); 749 if (ref != null) { 750 if (ref.get() == null) { 751 // It's possible that the Logger was GC'ed after a 752 // drainLoggerRefQueueBounded() call above so allow 753 // a new one to be registered. 754 ref.dispose(); 755 } else { 756 // We already have a registered logger with the given name. 757 return false; 758 } 759 } 760 761 // We're adding a new logger. 762 // Note that we are creating a weak reference here. 763 final LogManager owner = getOwner(); 764 logger.setLogManager(owner); 765 ref = owner.new LoggerWeakRef(logger); 766 767 // Apply any initial level defined for the new logger, unless 768 // the logger's level is already initialized 769 Level level = owner.getLevelProperty(name + ".level", null); 770 if (level != null && !logger.isLevelInitialized()) { 771 doSetLevel(logger, level); 772 } 773 774 // instantiation of the handler is done in the LogManager.addLogger 775 // implementation as a handler class may be only visible to LogManager 776 // subclass for the custom log manager case 777 processParentHandlers(logger, name, VisitedLoggers.NEVER); 778 779 // Find the new node and its parent. 780 LogNode node = getNode(name); 781 node.loggerRef = ref; 782 Logger parent = null; 783 LogNode nodep = node.parent; 784 while (nodep != null) { 785 LoggerWeakRef nodeRef = nodep.loggerRef; 786 if (nodeRef != null) { 787 parent = nodeRef.get(); 788 if (parent != null) { 789 break; 790 } 791 } 792 nodep = nodep.parent; 793 } 794 795 if (parent != null) { 796 doSetParent(logger, parent); 797 } 798 // Walk over the children and tell them we are their new parent. 799 node.walkAndSetParent(logger); 800 // new LogNode is ready so tell the LoggerWeakRef about it 801 ref.setNode(node); 802 803 // Do not publish 'ref' in namedLoggers before the logger tree 804 // is fully updated - because the named logger will be visible as 805 // soon as it is published in namedLoggers (findLogger takes 806 // benefit of the ConcurrentHashMap implementation of namedLoggers 807 // to avoid synchronizing on retrieval when that is possible). 808 namedLoggers.put(name, ref); 809 return true; 810 } 811 812 void removeLoggerRef(String name, LoggerWeakRef ref) { 813 namedLoggers.remove(name, ref); 814 } 815 816 synchronized Enumeration<String> getLoggerNames() { 817 // ensure that this context is properly initialized before 818 // returning logger names. 819 ensureInitialized(); 820 return Collections.enumeration(namedLoggers.keySet()); 821 } 822 823 // If logger.getUseParentHandlers() returns 'true' and any of the logger's 824 // parents have levels or handlers defined, make sure they are instantiated. 825 private void processParentHandlers(final Logger logger, final String name, 826 Predicate<Logger> visited) { 827 final LogManager owner = getOwner(); 828 AccessController.doPrivileged(new PrivilegedAction<Void>() { 829 @Override 830 public Void run() { 831 if (logger != owner.rootLogger) { 832 boolean useParent = owner.getBooleanProperty(name + ".useParentHandlers", true); 833 if (!useParent) { 834 logger.setUseParentHandlers(false); 835 } 836 } 837 return null; 838 } 839 }); 840 841 int ix = 1; 842 for (;;) { 843 int ix2 = name.indexOf('.', ix); 844 if (ix2 < 0) { 845 break; 846 } 847 String pname = name.substring(0, ix2); 848 if (owner.getProperty(pname + ".level") != null || 849 owner.getProperty(pname + ".handlers") != null) { 850 // This pname has a level/handlers definition. 851 // Make sure it exists. 852 if (visited.test(demandLogger(pname, null, null))) { 853 break; 854 } 855 } 856 ix = ix2+1; 857 } 858 } 859 860 // Gets a node in our tree of logger nodes. 861 // If necessary, create it. 862 LogNode getNode(String name) { 863 if (name == null || name.isEmpty()) { 864 return root; 865 } 866 LogNode node = root; 867 while (name.length() > 0) { 868 int ix = name.indexOf('.'); 869 String head; 870 if (ix > 0) { 871 head = name.substring(0, ix); 872 name = name.substring(ix + 1); 873 } else { 874 head = name; 875 name = ""; 876 } 877 if (node.children == null) { 878 node.children = new HashMap<>(); 879 } 880 LogNode child = node.children.get(head); 881 if (child == null) { 882 child = new LogNode(node, this); 883 node.children.put(head, child); 884 } 885 node = child; 886 } 887 return node; 888 } 889 } 890 891 final class SystemLoggerContext extends LoggerContext { 892 // Add a system logger in the system context's namespace as well as 893 // in the LogManager's namespace if not exist so that there is only 894 // one single logger of the given name. System loggers are visible 895 // to applications unless a logger of the same name has been added. 896 @Override 897 Logger demandLogger(String name, String resourceBundleName, 898 Module module) { 899 Logger result = findLogger(name); 900 if (result == null) { 901 // only allocate the new system logger once 902 Logger newLogger = new Logger(name, resourceBundleName, 903 module, getOwner(), true); 904 do { 905 if (addLocalLogger(newLogger)) { 906 // We successfully added the new Logger that we 907 // created above so return it without refetching. 908 result = newLogger; 909 } else { 910 // We didn't add the new Logger that we created above 911 // because another thread added a Logger with the same 912 // name after our null check above and before our call 913 // to addLogger(). We have to refetch the Logger because 914 // addLogger() returns a boolean instead of the Logger 915 // reference itself. However, if the thread that created 916 // the other Logger is not holding a strong reference to 917 // the other Logger, then it is possible for the other 918 // Logger to be GC'ed after we saw it in addLogger() and 919 // before we can refetch it. If it has been GC'ed then 920 // we'll just loop around and try again. 921 result = findLogger(name); 922 } 923 } while (result == null); 924 } 925 return result; 926 } 927 } 928 929 // Add new per logger handlers. 930 // We need to raise privilege here. All our decisions will 931 // be made based on the logging configuration, which can 932 // only be modified by trusted code. 933 private void loadLoggerHandlers(final Logger logger, final String name, 934 final String handlersPropertyName) 935 { 936 AccessController.doPrivileged(new PrivilegedAction<Void>() { 937 @Override 938 public Void run() { 939 setLoggerHandlers(logger, name, handlersPropertyName, 940 createLoggerHandlers(name, handlersPropertyName)); 941 return null; 942 } 943 }); 944 } 945 946 private void setLoggerHandlers(final Logger logger, final String name, 947 final String handlersPropertyName, 948 List<Handler> handlers) 949 { 950 final boolean ensureCloseOnReset = ! handlers.isEmpty() 951 && getBooleanProperty(handlersPropertyName + ".ensureCloseOnReset",true); 952 int count = 0; 953 for (Handler hdl : handlers) { 954 logger.addHandler(hdl); 955 if (++count == 1 && ensureCloseOnReset) { 956 // add this logger to the closeOnResetLoggers list. 957 closeOnResetLoggers.addIfAbsent(CloseOnReset.create(logger)); 958 } 959 } 960 } 961 962 private List<Handler> createLoggerHandlers(final String name, 963 final String handlersPropertyName) 964 { 965 String names[] = parseClassNames(handlersPropertyName); 966 List<Handler> handlers = new ArrayList<>(names.length); 967 for (String type : names) { 968 try { 969 @SuppressWarnings("deprecation") 970 Object o = ClassLoader.getSystemClassLoader().loadClass(type).newInstance(); 971 Handler hdl = (Handler) o; 972 // Check if there is a property defining the 973 // this handler's level. 974 String levs = getProperty(type + ".level"); 975 if (levs != null) { 976 Level l = Level.findLevel(levs); 977 if (l != null) { 978 hdl.setLevel(l); 979 } else { 980 // Probably a bad level. Drop through. 981 System.err.println("Can't set level for " + type); 982 } 983 } 984 // Add this Handler to the logger 985 handlers.add(hdl); 986 } catch (Exception ex) { 987 System.err.println("Can't load log handler \"" + type + "\""); 988 System.err.println("" + ex); 989 ex.printStackTrace(); 990 } 991 } 992 993 return handlers; 994 } 995 996 997 // loggerRefQueue holds LoggerWeakRef objects for Logger objects 998 // that have been GC'ed. 999 private final ReferenceQueue<Logger> loggerRefQueue 1000 = new ReferenceQueue<>(); 1001 1002 // Package-level inner class. 1003 // Helper class for managing WeakReferences to Logger objects. 1004 // 1005 // LogManager.namedLoggers 1006 // - has weak references to all named Loggers 1007 // - namedLoggers keeps the LoggerWeakRef objects for the named 1008 // Loggers around until we can deal with the book keeping for 1009 // the named Logger that is being GC'ed. 1010 // LogManager.LogNode.loggerRef 1011 // - has a weak reference to a named Logger 1012 // - the LogNode will also keep the LoggerWeakRef objects for 1013 // the named Loggers around; currently LogNodes never go away. 1014 // Logger.kids 1015 // - has a weak reference to each direct child Logger; this 1016 // includes anonymous and named Loggers 1017 // - anonymous Loggers are always children of the rootLogger 1018 // which is a strong reference; rootLogger.kids keeps the 1019 // LoggerWeakRef objects for the anonymous Loggers around 1020 // until we can deal with the book keeping. 1021 // 1022 final class LoggerWeakRef extends WeakReference<Logger> { 1023 private String name; // for namedLoggers cleanup 1024 private LogNode node; // for loggerRef cleanup 1025 private WeakReference<Logger> parentRef; // for kids cleanup 1026 private boolean disposed = false; // avoid calling dispose twice 1027 1028 LoggerWeakRef(Logger logger) { 1029 super(logger, loggerRefQueue); 1030 1031 name = logger.getName(); // save for namedLoggers cleanup 1032 } 1033 1034 // dispose of this LoggerWeakRef object 1035 void dispose() { 1036 // Avoid calling dispose twice. When a Logger is gc'ed, its 1037 // LoggerWeakRef will be enqueued. 1038 // However, a new logger of the same name may be added (or looked 1039 // up) before the queue is drained. When that happens, dispose() 1040 // will be called by addLocalLogger() or findLogger(). 1041 // Later when the queue is drained, dispose() will be called again 1042 // for the same LoggerWeakRef. Marking LoggerWeakRef as disposed 1043 // avoids processing the data twice (even though the code should 1044 // now be reentrant). 1045 synchronized(this) { 1046 // Note to maintainers: 1047 // Be careful not to call any method that tries to acquire 1048 // another lock from within this block - as this would surely 1049 // lead to deadlocks, given that dispose() can be called by 1050 // multiple threads, and from within different synchronized 1051 // methods/blocks. 1052 if (disposed) return; 1053 disposed = true; 1054 } 1055 1056 final LogNode n = node; 1057 if (n != null) { 1058 // n.loggerRef can only be safely modified from within 1059 // a lock on LoggerContext. removeLoggerRef is already 1060 // synchronized on LoggerContext so calling 1061 // n.context.removeLoggerRef from within this lock is safe. 1062 synchronized (n.context) { 1063 // if we have a LogNode, then we were a named Logger 1064 // so clear namedLoggers weak ref to us 1065 n.context.removeLoggerRef(name, this); 1066 name = null; // clear our ref to the Logger's name 1067 1068 // LogNode may have been reused - so only clear 1069 // LogNode.loggerRef if LogNode.loggerRef == this 1070 if (n.loggerRef == this) { 1071 n.loggerRef = null; // clear LogNode's weak ref to us 1072 } 1073 node = null; // clear our ref to LogNode 1074 } 1075 } 1076 1077 if (parentRef != null) { 1078 // this LoggerWeakRef has or had a parent Logger 1079 Logger parent = parentRef.get(); 1080 if (parent != null) { 1081 // the parent Logger is still there so clear the 1082 // parent Logger's weak ref to us 1083 parent.removeChildLogger(this); 1084 } 1085 parentRef = null; // clear our weak ref to the parent Logger 1086 } 1087 } 1088 1089 // set the node field to the specified value 1090 void setNode(LogNode node) { 1091 this.node = node; 1092 } 1093 1094 // set the parentRef field to the specified value 1095 void setParentRef(WeakReference<Logger> parentRef) { 1096 this.parentRef = parentRef; 1097 } 1098 } 1099 1100 // Package-level method. 1101 // Drain some Logger objects that have been GC'ed. 1102 // 1103 // drainLoggerRefQueueBounded() is called by addLogger() below 1104 // and by Logger.getAnonymousLogger(String) so we'll drain up to 1105 // MAX_ITERATIONS GC'ed Loggers for every Logger we add. 1106 // 1107 // On a WinXP VMware client, a MAX_ITERATIONS value of 400 gives 1108 // us about a 50/50 mix in increased weak ref counts versus 1109 // decreased weak ref counts in the AnonLoggerWeakRefLeak test. 1110 // Here are stats for cleaning up sets of 400 anonymous Loggers: 1111 // - test duration 1 minute 1112 // - sample size of 125 sets of 400 1113 // - average: 1.99 ms 1114 // - minimum: 0.57 ms 1115 // - maximum: 25.3 ms 1116 // 1117 // The same config gives us a better decreased weak ref count 1118 // than increased weak ref count in the LoggerWeakRefLeak test. 1119 // Here are stats for cleaning up sets of 400 named Loggers: 1120 // - test duration 2 minutes 1121 // - sample size of 506 sets of 400 1122 // - average: 0.57 ms 1123 // - minimum: 0.02 ms 1124 // - maximum: 10.9 ms 1125 // 1126 private final static int MAX_ITERATIONS = 400; 1127 final void drainLoggerRefQueueBounded() { 1128 for (int i = 0; i < MAX_ITERATIONS; i++) { 1129 if (loggerRefQueue == null) { 1130 // haven't finished loading LogManager yet 1131 break; 1132 } 1133 1134 LoggerWeakRef ref = (LoggerWeakRef) loggerRefQueue.poll(); 1135 if (ref == null) { 1136 break; 1137 } 1138 // a Logger object has been GC'ed so clean it up 1139 ref.dispose(); 1140 } 1141 } 1142 1143 /** 1144 * Add a named logger. This does nothing and returns false if a logger 1145 * with the same name is already registered. 1146 * <p> 1147 * The Logger factory methods call this method to register each 1148 * newly created Logger. 1149 * <p> 1150 * The application should retain its own reference to the Logger 1151 * object to avoid it being garbage collected. The LogManager 1152 * may only retain a weak reference. 1153 * 1154 * @param logger the new logger. 1155 * @return true if the argument logger was registered successfully, 1156 * false if a logger of that name already exists. 1157 * @exception NullPointerException if the logger name is null. 1158 */ 1159 public boolean addLogger(Logger logger) { 1160 final String name = logger.getName(); 1161 if (name == null) { 1162 throw new NullPointerException(); 1163 } 1164 drainLoggerRefQueueBounded(); 1165 LoggerContext cx = getUserContext(); 1166 if (cx.addLocalLogger(logger) || forceLoadHandlers(logger)) { 1167 // Do we have a per logger handler too? 1168 // Note: this will add a 200ms penalty 1169 loadLoggerHandlers(logger, name, name + ".handlers"); 1170 return true; 1171 } else { 1172 return false; 1173 } 1174 } 1175 1176 1177 // Checks whether the given logger is a special logger 1178 // that still requires handler initialization. 1179 // This method will only return true for the root and 1180 // global loggers and only if called by the thread that 1181 // performs initialization of the LogManager, during that 1182 // initialization. Must only be called by addLogger. 1183 @SuppressWarnings("deprecation") 1184 private boolean forceLoadHandlers(Logger logger) { 1185 // Called just after reading the primordial configuration, in 1186 // the same thread that reads it. 1187 // The root and global logger would already be present in the context 1188 // by this point, but we would not have called loadLoggerHandlers 1189 // yet. 1190 return (logger == rootLogger || logger == Logger.global) 1191 && !initializationDone 1192 && initializedCalled 1193 && configurationLock.isHeldByCurrentThread(); 1194 } 1195 1196 // Private method to set a level on a logger. 1197 // If necessary, we raise privilege before doing the call. 1198 private static void doSetLevel(final Logger logger, final Level level) { 1199 SecurityManager sm = System.getSecurityManager(); 1200 if (sm == null) { 1201 // There is no security manager, so things are easy. 1202 logger.setLevel(level); 1203 return; 1204 } 1205 // There is a security manager. Raise privilege before 1206 // calling setLevel. 1207 AccessController.doPrivileged(new PrivilegedAction<Object>() { 1208 @Override 1209 public Object run() { 1210 logger.setLevel(level); 1211 return null; 1212 }}); 1213 } 1214 1215 // Private method to set a parent on a logger. 1216 // If necessary, we raise privilege before doing the setParent call. 1217 private static void doSetParent(final Logger logger, final Logger parent) { 1218 SecurityManager sm = System.getSecurityManager(); 1219 if (sm == null) { 1220 // There is no security manager, so things are easy. 1221 logger.setParent(parent); 1222 return; 1223 } 1224 // There is a security manager. Raise privilege before 1225 // calling setParent. 1226 AccessController.doPrivileged(new PrivilegedAction<Object>() { 1227 @Override 1228 public Object run() { 1229 logger.setParent(parent); 1230 return null; 1231 }}); 1232 } 1233 1234 /** 1235 * Method to find a named logger. 1236 * <p> 1237 * Note that since untrusted code may create loggers with 1238 * arbitrary names this method should not be relied on to 1239 * find Loggers for security sensitive logging. 1240 * It is also important to note that the Logger associated with the 1241 * String {@code name} may be garbage collected at any time if there 1242 * is no strong reference to the Logger. The caller of this method 1243 * must check the return value for null in order to properly handle 1244 * the case where the Logger has been garbage collected. 1245 * 1246 * @param name name of the logger 1247 * @return matching logger or null if none is found 1248 */ 1249 public Logger getLogger(String name) { 1250 return getUserContext().findLogger(name); 1251 } 1252 1253 /** 1254 * Get an enumeration of known logger names. 1255 * <p> 1256 * Note: Loggers may be added dynamically as new classes are loaded. 1257 * This method only reports on the loggers that are currently registered. 1258 * It is also important to note that this method only returns the name 1259 * of a Logger, not a strong reference to the Logger itself. 1260 * The returned String does nothing to prevent the Logger from being 1261 * garbage collected. In particular, if the returned name is passed 1262 * to {@code LogManager.getLogger()}, then the caller must check the 1263 * return value from {@code LogManager.getLogger()} for null to properly 1264 * handle the case where the Logger has been garbage collected in the 1265 * time since its name was returned by this method. 1266 * 1267 * @return enumeration of logger name strings 1268 */ 1269 public Enumeration<String> getLoggerNames() { 1270 return getUserContext().getLoggerNames(); 1271 } 1272 1273 /** 1274 * Reads and initializes the logging configuration. 1275 * <p> 1276 * If the "java.util.logging.config.class" system property is set, then the 1277 * property value is treated as a class name. The given class will be 1278 * loaded, an object will be instantiated, and that object's constructor 1279 * is responsible for reading in the initial configuration. (That object 1280 * may use other system properties to control its configuration.) The 1281 * alternate configuration class can use {@code readConfiguration(InputStream)} 1282 * to define properties in the LogManager. 1283 * <p> 1284 * If "java.util.logging.config.class" system property is <b>not</b> set, 1285 * then this method will read the initial configuration from a properties 1286 * file and calls the {@link #readConfiguration(InputStream)} method to initialize 1287 * the configuration. The "java.util.logging.config.file" system property can be used 1288 * to specify the properties file that will be read as the initial configuration; 1289 * if not set, then the LogManager default configuration is used. 1290 * The default configuration is typically loaded from the 1291 * properties file "{@code conf/logging.properties}" in the Java installation 1292 * directory. 1293 * 1294 * <p> 1295 * Any {@linkplain #addConfigurationListener registered configuration 1296 * listener} will be invoked after the properties are read. 1297 * 1298 * @apiNote This {@code readConfiguration} method should only be used for 1299 * initializing the configuration during LogManager initialization or 1300 * used with the "java.util.logging.config.class" property. 1301 * When this method is called after loggers have been created, and 1302 * the "java.util.logging.config.class" system property is not set, all 1303 * existing loggers will be {@linkplain #reset() reset}. Then any 1304 * existing loggers that have a level property specified in the new 1305 * configuration stream will be {@linkplain 1306 * Logger#setLevel(java.util.logging.Level) set} to the specified log level. 1307 * <p> 1308 * To properly update the logging configuration, use the 1309 * {@link #updateConfiguration(java.util.function.Function)} or 1310 * {@link #updateConfiguration(java.io.InputStream, java.util.function.Function)} 1311 * methods instead. 1312 * 1313 * @throws SecurityException if a security manager exists and if 1314 * the caller does not have LoggingPermission("control"). 1315 * @throws IOException if there are IO problems reading the configuration. 1316 */ 1317 public void readConfiguration() throws IOException, SecurityException { 1318 checkPermission(); 1319 1320 // if a configuration class is specified, load it and use it. 1321 String cname = System.getProperty("java.util.logging.config.class"); 1322 if (cname != null) { 1323 try { 1324 // Instantiate the named class. It is its constructor's 1325 // responsibility to initialize the logging configuration, by 1326 // calling readConfiguration(InputStream) with a suitable stream. 1327 try { 1328 Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname); 1329 @SuppressWarnings("deprecation") 1330 Object witness = clz.newInstance(); 1331 return; 1332 } catch (ClassNotFoundException ex) { 1333 Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass(cname); 1334 @SuppressWarnings("deprecation") 1335 Object witness = clz.newInstance(); 1336 return; 1337 } 1338 } catch (Exception ex) { 1339 System.err.println("Logging configuration class \"" + cname + "\" failed"); 1340 System.err.println("" + ex); 1341 // keep going and useful config file. 1342 } 1343 } 1344 1345 String fname = getConfigurationFileName(); 1346 try (final InputStream in = new FileInputStream(fname)) { 1347 final BufferedInputStream bin = new BufferedInputStream(in); 1348 readConfiguration(bin); 1349 } 1350 } 1351 1352 String getConfigurationFileName() throws IOException { 1353 String fname = System.getProperty("java.util.logging.config.file"); 1354 if (fname == null) { 1355 fname = System.getProperty("java.home"); 1356 if (fname == null) { 1357 throw new Error("Can't find java.home ??"); 1358 } 1359 fname = Paths.get(fname, "conf", "logging.properties") 1360 .toAbsolutePath().normalize().toString(); 1361 } 1362 return fname; 1363 } 1364 1365 /** 1366 * Reset the logging configuration. 1367 * <p> 1368 * For all named loggers, the reset operation removes and closes 1369 * all Handlers and (except for the root logger) sets the level 1370 * to {@code null}. The root logger's level is set to {@code Level.INFO}. 1371 * 1372 * @apiNote Calling this method also clears the LogManager {@linkplain 1373 * #getProperty(java.lang.String) properties}. The {@link 1374 * #updateConfiguration(java.util.function.Function) 1375 * updateConfiguration(Function)} or 1376 * {@link #updateConfiguration(java.io.InputStream, java.util.function.Function) 1377 * updateConfiguration(InputStream, Function)} method can be used to 1378 * properly update to a new configuration. 1379 * 1380 * @throws SecurityException if a security manager exists and if 1381 * the caller does not have LoggingPermission("control"). 1382 */ 1383 1384 public void reset() throws SecurityException { 1385 checkPermission(); 1386 1387 List<CloseOnReset> persistent; 1388 1389 // We don't want reset() and readConfiguration() 1390 // to run in parallel 1391 configurationLock.lock(); 1392 try { 1393 // install new empty properties 1394 props = new Properties(); 1395 // make sure we keep the loggers persistent until reset is done. 1396 // Those are the loggers for which we previously created a 1397 // handler from the configuration, and we need to prevent them 1398 // from being gc'ed until those handlers are closed. 1399 persistent = new ArrayList<>(closeOnResetLoggers); 1400 closeOnResetLoggers.clear(); 1401 1402 // if reset has been called from shutdown-hook (Cleaner), 1403 // or if reset has been called from readConfiguration() which 1404 // already holds the lock and will change the state itself, 1405 // then do not change state here... 1406 if (globalHandlersState != STATE_SHUTDOWN && 1407 globalHandlersState != STATE_READING_CONFIG) { 1408 // ...else user called reset()... 1409 // Since we are doing a reset we no longer want to initialize 1410 // the global handlers, if they haven't been initialized yet. 1411 globalHandlersState = STATE_INITIALIZED; 1412 } 1413 1414 for (LoggerContext cx : contexts()) { 1415 resetLoggerContext(cx); 1416 } 1417 1418 persistent.clear(); 1419 } finally { 1420 configurationLock.unlock(); 1421 } 1422 } 1423 1424 private void resetLoggerContext(LoggerContext cx) { 1425 Enumeration<String> enum_ = cx.getLoggerNames(); 1426 while (enum_.hasMoreElements()) { 1427 String name = enum_.nextElement(); 1428 Logger logger = cx.findLogger(name); 1429 if (logger != null) { 1430 resetLogger(logger); 1431 } 1432 } 1433 } 1434 1435 private void closeHandlers(Logger logger) { 1436 Handler[] targets = logger.getHandlers(); 1437 for (Handler h : targets) { 1438 logger.removeHandler(h); 1439 try { 1440 h.close(); 1441 } catch (Exception ex) { 1442 // Problems closing a handler? Keep going... 1443 } catch (Error e) { 1444 // ignore Errors while shutting down 1445 if (globalHandlersState != STATE_SHUTDOWN) { 1446 throw e; 1447 } 1448 } 1449 } 1450 } 1451 1452 // Private method to reset an individual target logger. 1453 private void resetLogger(Logger logger) { 1454 // Close all the Logger handlers. 1455 closeHandlers(logger); 1456 1457 // Reset Logger level 1458 String name = logger.getName(); 1459 if (name != null && name.isEmpty()) { 1460 // This is the root logger. 1461 logger.setLevel(defaultLevel); 1462 } else { 1463 logger.setLevel(null); 1464 } 1465 } 1466 1467 // get a list of whitespace separated classnames from a property. 1468 private String[] parseClassNames(String propertyName) { 1469 String hands = getProperty(propertyName); 1470 if (hands == null) { 1471 return new String[0]; 1472 } 1473 hands = hands.trim(); 1474 int ix = 0; 1475 final List<String> result = new ArrayList<>(); 1476 while (ix < hands.length()) { 1477 int end = ix; 1478 while (end < hands.length()) { 1479 if (Character.isWhitespace(hands.charAt(end))) { 1480 break; 1481 } 1482 if (hands.charAt(end) == ',') { 1483 break; 1484 } 1485 end++; 1486 } 1487 String word = hands.substring(ix, end); 1488 ix = end+1; 1489 word = word.trim(); 1490 if (word.length() == 0) { 1491 continue; 1492 } 1493 result.add(word); 1494 } 1495 return result.toArray(new String[result.size()]); 1496 } 1497 1498 /** 1499 * Reads and initializes the logging configuration from the given input stream. 1500 * 1501 * <p> 1502 * Any {@linkplain #addConfigurationListener registered configuration 1503 * listener} will be invoked after the properties are read. 1504 * 1505 * @apiNote This {@code readConfiguration} method should only be used for 1506 * initializing the configuration during LogManager initialization or 1507 * used with the "java.util.logging.config.class" property. 1508 * When this method is called after loggers have been created, all 1509 * existing loggers will be {@linkplain #reset() reset}. Then any 1510 * existing loggers that have a level property specified in the 1511 * given input stream will be {@linkplain 1512 * Logger#setLevel(java.util.logging.Level) set} to the specified log level. 1513 * <p> 1514 * To properly update the logging configuration, use the 1515 * {@link #updateConfiguration(java.util.function.Function)} or 1516 * {@link #updateConfiguration(java.io.InputStream, java.util.function.Function)} 1517 * method instead. 1518 * 1519 * @param ins stream to read properties from 1520 * @throws SecurityException if a security manager exists and if 1521 * the caller does not have LoggingPermission("control"). 1522 * @throws IOException if there are problems reading from the stream, 1523 * or the given stream is not in the 1524 * {@linkplain java.util.Properties properties file} format. 1525 */ 1526 public void readConfiguration(InputStream ins) throws IOException, SecurityException { 1527 checkPermission(); 1528 1529 // We don't want reset() and readConfiguration() to run 1530 // in parallel. 1531 configurationLock.lock(); 1532 try { 1533 if (globalHandlersState == STATE_SHUTDOWN) { 1534 // already in terminal state: don't even bother 1535 // to read the configuration 1536 return; 1537 } 1538 1539 // change state to STATE_READING_CONFIG to signal reset() to not change it 1540 globalHandlersState = STATE_READING_CONFIG; 1541 try { 1542 // reset configuration which leaves globalHandlersState at STATE_READING_CONFIG 1543 // so that while reading configuration, any ongoing logging requests block and 1544 // wait for the outcome (see the end of this try statement) 1545 reset(); 1546 1547 try { 1548 // Load the properties 1549 props.load(ins); 1550 } catch (IllegalArgumentException x) { 1551 // props.load may throw an IllegalArgumentException if the stream 1552 // contains malformed Unicode escape sequences. 1553 // We wrap that in an IOException as readConfiguration is 1554 // specified to throw IOException if there are problems reading 1555 // from the stream. 1556 // Note: new IOException(x.getMessage(), x) allow us to get a more 1557 // concise error message than new IOException(x); 1558 throw new IOException(x.getMessage(), x); 1559 } 1560 1561 // Instantiate new configuration objects. 1562 String names[] = parseClassNames("config"); 1563 1564 for (String word : names) { 1565 try { 1566 Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(word); 1567 @SuppressWarnings("deprecation") 1568 Object witness = clz.newInstance(); 1569 } catch (Exception ex) { 1570 System.err.println("Can't load config class \"" + word + "\""); 1571 System.err.println("" + ex); 1572 // ex.printStackTrace(); 1573 } 1574 } 1575 1576 // Set levels on any pre-existing loggers, based on the new properties. 1577 setLevelsOnExistingLoggers(); 1578 1579 // Note that we need to reinitialize global handles when 1580 // they are first referenced. 1581 globalHandlersState = STATE_UNINITIALIZED; 1582 } catch (Throwable t) { 1583 // If there were any trouble, then set state to STATE_INITIALIZED 1584 // so that no global handlers reinitialization is performed on not fully 1585 // initialized configuration. 1586 globalHandlersState = STATE_INITIALIZED; 1587 // re-throw 1588 throw t; 1589 } 1590 } finally { 1591 configurationLock.unlock(); 1592 } 1593 1594 // should be called out of lock to avoid dead-lock situations 1595 // when user code is involved 1596 invokeConfigurationListeners(); 1597 } 1598 1599 // This enum enumerate the configuration properties that will be 1600 // updated on existing loggers when the configuration is updated 1601 // with LogManager.updateConfiguration(). 1602 // 1603 // Note that this works properly only for the global LogManager - as 1604 // Handler and its subclasses get their configuration from 1605 // LogManager.getLogManager(). 1606 // 1607 static enum ConfigProperty { 1608 LEVEL(".level"), HANDLERS(".handlers"), USEPARENT(".useParentHandlers"); 1609 final String suffix; 1610 final int length; 1611 private ConfigProperty(String suffix) { 1612 this.suffix = Objects.requireNonNull(suffix); 1613 length = suffix.length(); 1614 } 1615 1616 public boolean handleKey(String key) { 1617 if (this == HANDLERS && suffix.substring(1).equals(key)) return true; 1618 if (this == HANDLERS && suffix.equals(key)) return false; 1619 return key.endsWith(suffix); 1620 } 1621 String key(String loggerName) { 1622 if (this == HANDLERS && (loggerName == null || loggerName.isEmpty())) { 1623 return suffix.substring(1); 1624 } 1625 return loggerName + suffix; 1626 } 1627 String loggerName(String key) { 1628 assert key.equals(suffix.substring(1)) && this == HANDLERS || key.endsWith(suffix); 1629 if (this == HANDLERS && suffix.substring(1).equals(key)) return ""; 1630 return key.substring(0, key.length() - length); 1631 } 1632 1633 /** 1634 * If the property is one that should be updated on existing loggers by 1635 * updateConfiguration, returns the name of the logger for which the 1636 * property is configured. Otherwise, returns null. 1637 * @param property a property key in 'props' 1638 * @return the name of the logger on which the property is to be set, 1639 * if the property is one that should be updated on existing 1640 * loggers, {@code null} otherwise. 1641 */ 1642 static String getLoggerName(String property) { 1643 for (ConfigProperty p : ConfigProperty.ALL) { 1644 if (p.handleKey(property)) { 1645 return p.loggerName(property); 1646 } 1647 } 1648 return null; // Not a property that should be updated. 1649 } 1650 1651 /** 1652 * Find the ConfigProperty corresponding to the given 1653 * property key (may find none). 1654 * @param property a property key in 'props' 1655 * @return An optional containing a ConfigProperty object, 1656 * if the property is one that should be updated on existing 1657 * loggers, empty otherwise. 1658 */ 1659 static Optional<ConfigProperty> find(String property) { 1660 return ConfigProperty.ALL.stream() 1661 .filter(p -> p.handleKey(property)) 1662 .findFirst(); 1663 } 1664 1665 /** 1666 * Returns true if the given property is one that should be updated 1667 * on existing loggers. 1668 * Used to filter property name streams. 1669 * @param property a property key from the configuration. 1670 * @return true if this property is of interest for updateConfiguration. 1671 */ 1672 static boolean matches(String property) { 1673 return find(property).isPresent(); 1674 } 1675 1676 /** 1677 * Returns true if the new property value is different from the old, 1678 * and therefore needs to be updated on existing loggers. 1679 * @param k a property key in the configuration 1680 * @param previous the old configuration 1681 * @param next the new configuration 1682 * @return true if the property is changing value between the two 1683 * configurations. 1684 */ 1685 static boolean needsUpdating(String k, Properties previous, Properties next) { 1686 final String p = trim(previous.getProperty(k, null)); 1687 final String n = trim(next.getProperty(k, null)); 1688 return ! Objects.equals(p,n); 1689 } 1690 1691 /** 1692 * Applies the mapping function for the given key to the next 1693 * configuration. 1694 * If the mapping function is null then this method does nothing. 1695 * Otherwise, it calls the mapping function to compute the value 1696 * that should be associated with {@code key} in the resulting 1697 * configuration, and applies it to {@code next}. 1698 * If the mapping function returns {@code null} the key is removed 1699 * from {@code next}. 1700 * 1701 * @param k a property key in the configuration 1702 * @param previous the old configuration 1703 * @param next the new configuration (modified by this function) 1704 * @param mappingFunction the mapping function. 1705 */ 1706 static void merge(String k, Properties previous, Properties next, 1707 BiFunction<String, String, String> mappingFunction) { 1708 String p = trim(previous.getProperty(k, null)); 1709 String n = trim(next.getProperty(k, null)); 1710 String mapped = trim(mappingFunction.apply(p,n)); 1711 if (!Objects.equals(n, mapped)) { 1712 if (mapped == null) { 1713 next.remove(k); 1714 } else { 1715 next.setProperty(k, mapped); 1716 } 1717 } 1718 } 1719 1720 private static final EnumSet<ConfigProperty> ALL = 1721 EnumSet.allOf(ConfigProperty.class); 1722 } 1723 1724 // trim the value if not null. 1725 private static String trim(String value) { 1726 return value == null ? null : value.trim(); 1727 } 1728 1729 /** 1730 * An object that keep track of loggers we have already visited. 1731 * Used when updating configuration, to avoid processing the same logger 1732 * twice. 1733 */ 1734 static final class VisitedLoggers implements Predicate<Logger> { 1735 final IdentityHashMap<Logger,Boolean> visited; 1736 private VisitedLoggers(IdentityHashMap<Logger,Boolean> visited) { 1737 this.visited = visited; 1738 } 1739 VisitedLoggers() { 1740 this(new IdentityHashMap<>()); 1741 } 1742 @Override 1743 public boolean test(Logger logger) { 1744 return visited != null && visited.put(logger, Boolean.TRUE) != null; 1745 } 1746 public void clear() { 1747 if (visited != null) visited.clear(); 1748 } 1749 1750 // An object that considers that no logger has ever been visited. 1751 // This is used when processParentHandlers is called from 1752 // LoggerContext.addLocalLogger 1753 static final VisitedLoggers NEVER = new VisitedLoggers(null); 1754 } 1755 1756 1757 /** 1758 * Type of the modification for a given property. One of SAME, ADDED, CHANGED, 1759 * or REMOVED. 1760 */ 1761 static enum ModType { 1762 SAME, // property had no value in the old and new conf, or had the 1763 // same value in both. 1764 ADDED, // property had no value in the old conf, but has one in the new. 1765 CHANGED, // property has a different value in the old conf and the new conf. 1766 REMOVED; // property has no value in the new conf, but had one in the old. 1767 static ModType of(String previous, String next) { 1768 if (previous == null && next != null) { 1769 return ADDED; 1770 } 1771 if (next == null && previous != null) { 1772 return REMOVED; 1773 } 1774 if (!Objects.equals(trim(previous), trim(next))) { 1775 return CHANGED; 1776 } 1777 return SAME; 1778 } 1779 } 1780 1781 /** 1782 * Updates the logging configuration. 1783 * <p> 1784 * If the "java.util.logging.config.file" system property is set, 1785 * then the property value specifies the properties file to be read 1786 * as the new configuration. Otherwise, the LogManager default 1787 * configuration is used. 1788 * <br>The default configuration is typically loaded from the 1789 * properties file "{@code conf/logging.properties}" in the 1790 * Java installation directory. 1791 * <p> 1792 * This method reads the new configuration and calls the {@link 1793 * #updateConfiguration(java.io.InputStream, java.util.function.Function) 1794 * updateConfiguration(ins, mapper)} method to 1795 * update the configuration. 1796 * 1797 * @apiNote 1798 * This method updates the logging configuration from reading 1799 * a properties file and ignores the "java.util.logging.config.class" 1800 * system property. The "java.util.logging.config.class" property is 1801 * only used by the {@link #readConfiguration()} method to load a custom 1802 * configuration class as an initial configuration. 1803 * 1804 * @param mapper a functional interface that takes a configuration 1805 * key <i>k</i> and returns a function <i>f(o,n)</i> whose returned 1806 * value will be applied to the resulting configuration. The 1807 * function <i>f</i> may return {@code null} to indicate that the property 1808 * <i>k</i> will not be added to the resulting configuration. 1809 * <br> 1810 * If {@code mapper} is {@code null} then {@code (k) -> ((o, n) -> n)} is 1811 * assumed. 1812 * <br> 1813 * For each <i>k</i>, the mapped function <i>f</i> will 1814 * be invoked with the value associated with <i>k</i> in the old 1815 * configuration (i.e <i>o</i>) and the value associated with 1816 * <i>k</i> in the new configuration (i.e. <i>n</i>). 1817 * <br>A {@code null} value for <i>o</i> or <i>n</i> indicates that no 1818 * value was present for <i>k</i> in the corresponding configuration. 1819 * 1820 * @throws SecurityException if a security manager exists and if 1821 * the caller does not have LoggingPermission("control"), or 1822 * does not have the permissions required to set up the 1823 * configuration (e.g. open file specified for FileHandlers 1824 * etc...) 1825 * 1826 * @throws NullPointerException if {@code mapper} returns a {@code null} 1827 * function when invoked. 1828 * 1829 * @throws IOException if there are problems reading from the 1830 * logging configuration file. 1831 * 1832 * @see #updateConfiguration(java.io.InputStream, java.util.function.Function) 1833 * @since 9 1834 */ 1835 public void updateConfiguration(Function<String, BiFunction<String,String,String>> mapper) 1836 throws IOException { 1837 checkPermission(); 1838 ensureLogManagerInitialized(); 1839 drainLoggerRefQueueBounded(); 1840 1841 String fname = getConfigurationFileName(); 1842 try (final InputStream in = new FileInputStream(fname)) { 1843 final BufferedInputStream bin = new BufferedInputStream(in); 1844 updateConfiguration(bin, mapper); 1845 } 1846 } 1847 1848 /** 1849 * Updates the logging configuration. 1850 * <p> 1851 * For each configuration key in the {@linkplain 1852 * #getProperty(java.lang.String) existing configuration} and 1853 * the given input stream configuration, the given {@code mapper} function 1854 * is invoked to map from the configuration key to a function, 1855 * <i>f(o,n)</i>, that takes the old value and new value and returns 1856 * the resulting value to be applied in the resulting configuration, 1857 * as specified in the table below. 1858 * <p>Let <i>k</i> be a configuration key in the old or new configuration, 1859 * <i>o</i> be the old value (i.e. the value associated 1860 * with <i>k</i> in the old configuration), <i>n</i> be the 1861 * new value (i.e. the value associated with <i>k</i> in the new 1862 * configuration), and <i>f</i> be the function returned 1863 * by {@code mapper.apply(}<i>k</i>{@code )}: then <i>v = f(o,n)</i> is the 1864 * resulting value. If <i>v</i> is not {@code null}, then a property 1865 * <i>k</i> with value <i>v</i> will be added to the resulting configuration. 1866 * Otherwise, it will be omitted. 1867 * <br>A {@code null} value may be passed to function 1868 * <i>f</i> to indicate that the corresponding configuration has no 1869 * configuration key <i>k</i>. 1870 * The function <i>f</i> may return {@code null} to indicate that 1871 * there will be no value associated with <i>k</i> in the resulting 1872 * configuration. 1873 * <p> 1874 * If {@code mapper} is {@code null}, then <i>v</i> will be set to 1875 * <i>n</i>. 1876 * <p> 1877 * LogManager {@linkplain #getProperty(java.lang.String) properties} are 1878 * updated with the resulting value in the resulting configuration. 1879 * <p> 1880 * The registered {@linkplain #addConfigurationListener configuration 1881 * listeners} will be invoked after the configuration is successfully updated. 1882 * <br><br> 1883 * <table class="striped"> 1884 * <caption style="display:none">Updating configuration properties</caption> 1885 * <thead> 1886 * <tr> 1887 * <th scope="col">Property</th> 1888 * <th scope="col">Resulting Behavior</th> 1889 * </tr> 1890 * </thead> 1891 * <tbody> 1892 * <tr> 1893 * <th scope="row" valign="top">{@code <logger>.level}</th> 1894 * <td> 1895 * <ul> 1896 * <li>If the resulting configuration defines a level for a logger and 1897 * if the resulting level is different than the level specified in the 1898 * the old configuration, or not specified in 1899 * the old configuration, then if the logger exists or if children for 1900 * that logger exist, the level for that logger will be updated, 1901 * and the change propagated to any existing logger children. 1902 * This may cause the logger to be created, if necessary. 1903 * </li> 1904 * <li>If the old configuration defined a level for a logger, and the 1905 * resulting configuration doesn't, then this change will not be 1906 * propagated to existing loggers, if any. 1907 * To completely replace a configuration - the caller should therefore 1908 * call {@link #reset() reset} to empty the current configuration, 1909 * before calling {@code updateConfiguration}. 1910 * </li> 1911 * </ul> 1912 * </td> 1913 * <tr> 1914 * <th scope="row" valign="top">{@code <logger>.useParentHandlers}</th> 1915 * <td> 1916 * <ul> 1917 * <li>If either the resulting or the old value for the useParentHandlers 1918 * property is not null, then if the logger exists or if children for 1919 * that logger exist, that logger will be updated to the resulting 1920 * value. 1921 * The value of the useParentHandlers property is the value specified 1922 * in the configuration; if not specified, the default is true. 1923 * </li> 1924 * </ul> 1925 * </td> 1926 * </tr> 1927 * <tr> 1928 * <th scope="row" valign="top">{@code <logger>.handlers}</th> 1929 * <td> 1930 * <ul> 1931 * <li>If the resulting configuration defines a list of handlers for a 1932 * logger, and if the resulting list is different than the list 1933 * specified in the old configuration for that logger (that could be 1934 * empty), then if the logger exists or its children exist, the 1935 * handlers associated with that logger are closed and removed and 1936 * the new handlers will be created per the resulting configuration 1937 * and added to that logger, creating that logger if necessary. 1938 * </li> 1939 * <li>If the old configuration defined some handlers for a logger, and 1940 * the resulting configuration doesn't, if that logger exists, 1941 * its handlers will be removed and closed. 1942 * </li> 1943 * <li>Changing the list of handlers on an existing logger will cause all 1944 * its previous handlers to be removed and closed, regardless of whether 1945 * they had been created from the configuration or programmatically. 1946 * The old handlers will be replaced by new handlers, if any. 1947 * </li> 1948 * </ul> 1949 * </td> 1950 * </tr> 1951 * <tr> 1952 * <th scope="row" valign="top">{@code <handler-name>.*}</th> 1953 * <td> 1954 * <ul> 1955 * <li>Properties configured/changed on handler classes will only affect 1956 * newly created handlers. If a node is configured with the same list 1957 * of handlers in the old and the resulting configuration, then these 1958 * handlers will remain unchanged. 1959 * </li> 1960 * </ul> 1961 * </td> 1962 * </tr> 1963 * <tr> 1964 * <th scope="row" valign="top">{@code config} and any other property</th> 1965 * <td> 1966 * <ul> 1967 * <li>The resulting value for these property will be stored in the 1968 * LogManager properties, but {@code updateConfiguration} will not parse 1969 * or process their values. 1970 * </li> 1971 * </ul> 1972 * </td> 1973 * </tr> 1974 * </tbody> 1975 * </table> 1976 * <p> 1977 * <em>Example mapper functions:</em> 1978 * <br><br> 1979 * <ul> 1980 * <li>Replace all logging properties with the new configuration: 1981 * <br><br>{@code (k) -> ((o, n) -> n)}: 1982 * <br><br>this is equivalent to passing a null {@code mapper} parameter. 1983 * </li> 1984 * <li>Merge the new configuration and old configuration and use the 1985 * new value if <i>k</i> exists in the new configuration: 1986 * <br><br>{@code (k) -> ((o, n) -> n == null ? o : n)}: 1987 * <br><br>as if merging two collections as follows: 1988 * {@code result.putAll(oldc); result.putAll(newc)}.<br></li> 1989 * <li>Merge the new configuration and old configuration and use the old 1990 * value if <i>k</i> exists in the old configuration: 1991 * <br><br>{@code (k) -> ((o, n) -> o == null ? n : o)}: 1992 * <br><br>as if merging two collections as follows: 1993 * {@code result.putAll(newc); result.putAll(oldc)}.<br></li> 1994 * <li>Replace all properties with the new configuration except the handler 1995 * property to configure Logger's handler that is not root logger: 1996 * <br> 1997 * <pre>{@code (k) -> k.endsWith(".handlers")} 1998 * {@code ? ((o, n) -> (o == null ? n : o))} 1999 * {@code : ((o, n) -> n)}</pre> 2000 * </li> 2001 * </ul> 2002 * <p> 2003 * To completely reinitialize a configuration, an application can first call 2004 * {@link #reset() reset} to fully remove the old configuration, followed by 2005 * {@code updateConfiguration} to initialize the new configuration. 2006 * 2007 * @param ins a stream to read properties from 2008 * @param mapper a functional interface that takes a configuration 2009 * key <i>k</i> and returns a function <i>f(o,n)</i> whose returned 2010 * value will be applied to the resulting configuration. The 2011 * function <i>f</i> may return {@code null} to indicate that the property 2012 * <i>k</i> will not be added to the resulting configuration. 2013 * <br> 2014 * If {@code mapper} is {@code null} then {@code (k) -> ((o, n) -> n)} is 2015 * assumed. 2016 * <br> 2017 * For each <i>k</i>, the mapped function <i>f</i> will 2018 * be invoked with the value associated with <i>k</i> in the old 2019 * configuration (i.e <i>o</i>) and the value associated with 2020 * <i>k</i> in the new configuration (i.e. <i>n</i>). 2021 * <br>A {@code null} value for <i>o</i> or <i>n</i> indicates that no 2022 * value was present for <i>k</i> in the corresponding configuration. 2023 * 2024 * @throws SecurityException if a security manager exists and if 2025 * the caller does not have LoggingPermission("control"), or 2026 * does not have the permissions required to set up the 2027 * configuration (e.g. open files specified for FileHandlers) 2028 * 2029 * @throws NullPointerException if {@code ins} is null or if 2030 * {@code mapper} returns a null function when invoked. 2031 * 2032 * @throws IOException if there are problems reading from the stream, 2033 * or the given stream is not in the 2034 * {@linkplain java.util.Properties properties file} format. 2035 * @since 9 2036 */ 2037 public void updateConfiguration(InputStream ins, 2038 Function<String, BiFunction<String,String,String>> mapper) 2039 throws IOException { 2040 checkPermission(); 2041 ensureLogManagerInitialized(); 2042 drainLoggerRefQueueBounded(); 2043 2044 final Properties previous; 2045 final Set<String> updatePropertyNames; 2046 List<LoggerContext> cxs = Collections.emptyList(); 2047 final VisitedLoggers visited = new VisitedLoggers(); 2048 final Properties next = new Properties(); 2049 2050 try { 2051 // Load the properties 2052 next.load(ins); 2053 } catch (IllegalArgumentException x) { 2054 // props.load may throw an IllegalArgumentException if the stream 2055 // contains malformed Unicode escape sequences. 2056 // We wrap that in an IOException as updateConfiguration is 2057 // specified to throw IOException if there are problems reading 2058 // from the stream. 2059 // Note: new IOException(x.getMessage(), x) allow us to get a more 2060 // concise error message than new IOException(x); 2061 throw new IOException(x.getMessage(), x); 2062 } 2063 2064 if (globalHandlersState == STATE_SHUTDOWN) return; 2065 2066 // exclusive lock: readConfiguration/reset/updateConfiguration can't 2067 // run concurrently. 2068 // configurationLock.writeLock().lock(); 2069 configurationLock.lock(); 2070 try { 2071 if (globalHandlersState == STATE_SHUTDOWN) return; 2072 previous = props; 2073 2074 // Builds a TreeSet of all (old and new) property names. 2075 updatePropertyNames = 2076 Stream.concat(previous.stringPropertyNames().stream(), 2077 next.stringPropertyNames().stream()) 2078 .collect(Collectors.toCollection(TreeSet::new)); 2079 2080 if (mapper != null) { 2081 // mapper will potentially modify the content of 2082 // 'next', so we need to call it before affecting props=next. 2083 // give a chance to the mapper to control all 2084 // properties - not just those we will reset. 2085 updatePropertyNames.stream() 2086 .forEachOrdered(k -> ConfigProperty 2087 .merge(k, previous, next, 2088 Objects.requireNonNull(mapper.apply(k)))); 2089 } 2090 2091 props = next; 2092 2093 // allKeys will contain all keys: 2094 // - which correspond to a configuration property we are interested in 2095 // (first filter) 2096 // - whose value needs to be updated (because it's new, removed, or 2097 // different) in the resulting configuration (second filter) 2098 final Stream<String> allKeys = updatePropertyNames.stream() 2099 .filter(ConfigProperty::matches) 2100 .filter(k -> ConfigProperty.needsUpdating(k, previous, next)); 2101 2102 // Group configuration properties by logger name 2103 // We use a TreeMap so that parent loggers will be visited before 2104 // child loggers. 2105 final Map<String, TreeSet<String>> loggerConfigs = 2106 allKeys.collect(Collectors.groupingBy(ConfigProperty::getLoggerName, 2107 TreeMap::new, 2108 Collectors.toCollection(TreeSet::new))); 2109 2110 if (!loggerConfigs.isEmpty()) { 2111 cxs = contexts(); 2112 } 2113 final List<Logger> loggers = cxs.isEmpty() 2114 ? Collections.emptyList() : new ArrayList<>(cxs.size()); 2115 for (Map.Entry<String, TreeSet<String>> e : loggerConfigs.entrySet()) { 2116 // This can be a logger name, or something else... 2117 // The only thing we know is that we found a property 2118 // we are interested in. 2119 // For instance, if we found x.y.z.level, then x.y.z could be 2120 // a logger, but it could also be a handler class... 2121 // Anyway... 2122 final String name = e.getKey(); 2123 final Set<String> properties = e.getValue(); 2124 loggers.clear(); 2125 for (LoggerContext cx : cxs) { 2126 Logger l = cx.findLogger(name); 2127 if (l != null && !visited.test(l)) { 2128 loggers.add(l); 2129 } 2130 } 2131 if (loggers.isEmpty()) continue; 2132 for (String pk : properties) { 2133 ConfigProperty cp = ConfigProperty.find(pk).get(); 2134 String p = previous.getProperty(pk, null); 2135 String n = next.getProperty(pk, null); 2136 2137 // Determines the type of modification. 2138 ModType mod = ModType.of(p, n); 2139 2140 // mod == SAME means that the two values are equals, there 2141 // is nothing to do. Usually, this should not happen as such 2142 // properties should have been filtered above. 2143 // It could happen however if the properties had 2144 // trailing/leading whitespaces. 2145 if (mod == ModType.SAME) continue; 2146 2147 switch (cp) { 2148 case LEVEL: 2149 if (mod == ModType.REMOVED) continue; 2150 Level level = Level.findLevel(trim(n)); 2151 if (level != null) { 2152 if (name.isEmpty()) { 2153 rootLogger.setLevel(level); 2154 } 2155 for (Logger l : loggers) { 2156 if (!name.isEmpty() || l != rootLogger) { 2157 l.setLevel(level); 2158 } 2159 } 2160 } 2161 break; 2162 case USEPARENT: 2163 if (!name.isEmpty()) { 2164 boolean useParent = getBooleanProperty(pk, true); 2165 if (n != null || p != null) { 2166 // reset the flag only if the previous value 2167 // or the new value are not null. 2168 for (Logger l : loggers) { 2169 l.setUseParentHandlers(useParent); 2170 } 2171 } 2172 } 2173 break; 2174 case HANDLERS: 2175 List<Handler> hdls = null; 2176 if (name.isEmpty()) { 2177 // special handling for the root logger. 2178 globalHandlersState = STATE_READING_CONFIG; 2179 try { 2180 closeHandlers(rootLogger); 2181 globalHandlersState = STATE_UNINITIALIZED; 2182 } catch (Throwable t) { 2183 globalHandlersState = STATE_INITIALIZED; 2184 throw t; 2185 } 2186 } 2187 for (Logger l : loggers) { 2188 if (l == rootLogger) continue; 2189 closeHandlers(l); 2190 if (mod == ModType.REMOVED) { 2191 closeOnResetLoggers.removeIf(c -> c.logger == l); 2192 continue; 2193 } 2194 if (hdls == null) { 2195 hdls = name.isEmpty() 2196 ? Arrays.asList(rootLogger.getHandlers()) 2197 : createLoggerHandlers(name, pk); 2198 } 2199 setLoggerHandlers(l, name, pk, hdls); 2200 } 2201 break; 2202 default: break; 2203 } 2204 } 2205 } 2206 } finally { 2207 configurationLock.unlock(); 2208 visited.clear(); 2209 } 2210 2211 // Now ensure that if an existing logger has acquired a new parent 2212 // in the configuration, this new parent will be created - if needed, 2213 // and added to the context of the existing child. 2214 // 2215 drainLoggerRefQueueBounded(); 2216 for (LoggerContext cx : cxs) { 2217 for (Enumeration<String> names = cx.getLoggerNames() ; names.hasMoreElements();) { 2218 String name = names.nextElement(); 2219 if (name.isEmpty()) continue; // don't need to process parents on root. 2220 Logger l = cx.findLogger(name); 2221 if (l != null && !visited.test(l)) { 2222 // should pass visited here to cut the processing when 2223 // reaching a logger already visited. 2224 cx.processParentHandlers(l, name, visited); 2225 } 2226 } 2227 } 2228 2229 // We changed the configuration: invoke configuration listeners 2230 invokeConfigurationListeners(); 2231 } 2232 2233 /** 2234 * Get the value of a logging property. 2235 * The method returns null if the property is not found. 2236 * @param name property name 2237 * @return property value 2238 */ 2239 public String getProperty(String name) { 2240 return props.getProperty(name); 2241 } 2242 2243 // Package private method to get a String property. 2244 // If the property is not defined we return the given 2245 // default value. 2246 String getStringProperty(String name, String defaultValue) { 2247 String val = getProperty(name); 2248 if (val == null) { 2249 return defaultValue; 2250 } 2251 return val.trim(); 2252 } 2253 2254 // Package private method to get an integer property. 2255 // If the property is not defined or cannot be parsed 2256 // we return the given default value. 2257 int getIntProperty(String name, int defaultValue) { 2258 String val = getProperty(name); 2259 if (val == null) { 2260 return defaultValue; 2261 } 2262 try { 2263 return Integer.parseInt(val.trim()); 2264 } catch (Exception ex) { 2265 return defaultValue; 2266 } 2267 } 2268 2269 // Package private method to get a long property. 2270 // If the property is not defined or cannot be parsed 2271 // we return the given default value. 2272 long getLongProperty(String name, long defaultValue) { 2273 String val = getProperty(name); 2274 if (val == null) { 2275 return defaultValue; 2276 } 2277 try { 2278 return Long.parseLong(val.trim()); 2279 } catch (Exception ex) { 2280 return defaultValue; 2281 } 2282 } 2283 2284 // Package private method to get a boolean property. 2285 // If the property is not defined or cannot be parsed 2286 // we return the given default value. 2287 boolean getBooleanProperty(String name, boolean defaultValue) { 2288 String val = getProperty(name); 2289 if (val == null) { 2290 return defaultValue; 2291 } 2292 val = val.toLowerCase(); 2293 if (val.equals("true") || val.equals("1")) { 2294 return true; 2295 } else if (val.equals("false") || val.equals("0")) { 2296 return false; 2297 } 2298 return defaultValue; 2299 } 2300 2301 // Package private method to get a Level property. 2302 // If the property is not defined or cannot be parsed 2303 // we return the given default value. 2304 Level getLevelProperty(String name, Level defaultValue) { 2305 String val = getProperty(name); 2306 if (val == null) { 2307 return defaultValue; 2308 } 2309 Level l = Level.findLevel(val.trim()); 2310 return l != null ? l : defaultValue; 2311 } 2312 2313 // Package private method to get a filter property. 2314 // We return an instance of the class named by the "name" 2315 // property. If the property is not defined or has problems 2316 // we return the defaultValue. 2317 Filter getFilterProperty(String name, Filter defaultValue) { 2318 String val = getProperty(name); 2319 try { 2320 if (val != null) { 2321 @SuppressWarnings("deprecation") 2322 Object o = ClassLoader.getSystemClassLoader().loadClass(val).newInstance(); 2323 return (Filter) o; 2324 } 2325 } catch (Exception ex) { 2326 // We got one of a variety of exceptions in creating the 2327 // class or creating an instance. 2328 // Drop through. 2329 } 2330 // We got an exception. Return the defaultValue. 2331 return defaultValue; 2332 } 2333 2334 2335 // Package private method to get a formatter property. 2336 // We return an instance of the class named by the "name" 2337 // property. If the property is not defined or has problems 2338 // we return the defaultValue. 2339 Formatter getFormatterProperty(String name, Formatter defaultValue) { 2340 String val = getProperty(name); 2341 try { 2342 if (val != null) { 2343 @SuppressWarnings("deprecation") 2344 Object o = ClassLoader.getSystemClassLoader().loadClass(val).newInstance(); 2345 return (Formatter) o; 2346 } 2347 } catch (Exception ex) { 2348 // We got one of a variety of exceptions in creating the 2349 // class or creating an instance. 2350 // Drop through. 2351 } 2352 // We got an exception. Return the defaultValue. 2353 return defaultValue; 2354 } 2355 2356 // Private method to load the global handlers. 2357 // We do the real work lazily, when the global handlers 2358 // are first used. 2359 private void initializeGlobalHandlers() { 2360 int state = globalHandlersState; 2361 if (state == STATE_INITIALIZED || 2362 state == STATE_SHUTDOWN) { 2363 // Nothing to do: return. 2364 return; 2365 } 2366 2367 // If we have not initialized global handlers yet (or need to 2368 // reinitialize them), lets do it now (this case is indicated by 2369 // globalHandlersState == STATE_UNINITIALIZED). 2370 // If we are in the process of initializing global handlers we 2371 // also need to lock & wait (this case is indicated by 2372 // globalHandlersState == STATE_INITIALIZING). 2373 // If we are in the process of reading configuration we also need to 2374 // wait to see what the outcome will be (this case 2375 // is indicated by globalHandlersState == STATE_READING_CONFIG) 2376 // So in either case we need to wait for the lock. 2377 configurationLock.lock(); 2378 try { 2379 if (globalHandlersState != STATE_UNINITIALIZED) { 2380 return; // recursive call or nothing to do 2381 } 2382 // set globalHandlersState to STATE_INITIALIZING first to avoid 2383 // getting an infinite recursion when loadLoggerHandlers(...) 2384 // is going to call addHandler(...) 2385 globalHandlersState = STATE_INITIALIZING; 2386 try { 2387 loadLoggerHandlers(rootLogger, null, "handlers"); 2388 } finally { 2389 globalHandlersState = STATE_INITIALIZED; 2390 } 2391 } finally { 2392 configurationLock.unlock(); 2393 } 2394 } 2395 2396 static final Permission controlPermission = 2397 new LoggingPermission("control", null); 2398 2399 void checkPermission() { 2400 SecurityManager sm = System.getSecurityManager(); 2401 if (sm != null) 2402 sm.checkPermission(controlPermission); 2403 } 2404 2405 /** 2406 * Check that the current context is trusted to modify the logging 2407 * configuration. This requires LoggingPermission("control"). 2408 * <p> 2409 * If the check fails we throw a SecurityException, otherwise 2410 * we return normally. 2411 * 2412 * @exception SecurityException if a security manager exists and if 2413 * the caller does not have LoggingPermission("control"). 2414 */ 2415 public void checkAccess() throws SecurityException { 2416 checkPermission(); 2417 } 2418 2419 // Nested class to represent a node in our tree of named loggers. 2420 private static class LogNode { 2421 HashMap<String,LogNode> children; 2422 LoggerWeakRef loggerRef; 2423 LogNode parent; 2424 final LoggerContext context; 2425 2426 LogNode(LogNode parent, LoggerContext context) { 2427 this.parent = parent; 2428 this.context = context; 2429 } 2430 2431 // Recursive method to walk the tree below a node and set 2432 // a new parent logger. 2433 void walkAndSetParent(Logger parent) { 2434 if (children == null) { 2435 return; 2436 } 2437 for (LogNode node : children.values()) { 2438 LoggerWeakRef ref = node.loggerRef; 2439 Logger logger = (ref == null) ? null : ref.get(); 2440 if (logger == null) { 2441 node.walkAndSetParent(parent); 2442 } else { 2443 doSetParent(logger, parent); 2444 } 2445 } 2446 } 2447 } 2448 2449 // We use a subclass of Logger for the root logger, so 2450 // that we only instantiate the global handlers when they 2451 // are first needed. 2452 private final class RootLogger extends Logger { 2453 private RootLogger() { 2454 // We do not call the protected Logger two args constructor here, 2455 // to avoid calling LogManager.getLogManager() from within the 2456 // RootLogger constructor. 2457 super("", null, null, LogManager.this, true); 2458 } 2459 2460 @Override 2461 public void log(LogRecord record) { 2462 // Make sure that the global handlers have been instantiated. 2463 initializeGlobalHandlers(); 2464 super.log(record); 2465 } 2466 2467 @Override 2468 public void addHandler(Handler h) { 2469 initializeGlobalHandlers(); 2470 super.addHandler(h); 2471 } 2472 2473 @Override 2474 public void removeHandler(Handler h) { 2475 initializeGlobalHandlers(); 2476 super.removeHandler(h); 2477 } 2478 2479 @Override 2480 Handler[] accessCheckedHandlers() { 2481 initializeGlobalHandlers(); 2482 return super.accessCheckedHandlers(); 2483 } 2484 } 2485 2486 2487 // Private method to be called when the configuration has 2488 // changed to apply any level settings to any pre-existing loggers. 2489 private void setLevelsOnExistingLoggers() { 2490 Enumeration<?> enum_ = props.propertyNames(); 2491 while (enum_.hasMoreElements()) { 2492 String key = (String)enum_.nextElement(); 2493 if (!key.endsWith(".level")) { 2494 // Not a level definition. 2495 continue; 2496 } 2497 int ix = key.length() - 6; 2498 String name = key.substring(0, ix); 2499 Level level = getLevelProperty(key, null); 2500 if (level == null) { 2501 System.err.println("Bad level value for property: " + key); 2502 continue; 2503 } 2504 for (LoggerContext cx : contexts()) { 2505 Logger l = cx.findLogger(name); 2506 if (l == null) { 2507 continue; 2508 } 2509 l.setLevel(level); 2510 } 2511 } 2512 } 2513 2514 /** 2515 * String representation of the 2516 * {@link javax.management.ObjectName} for the management interface 2517 * for the logging facility. 2518 * 2519 * @see java.lang.management.PlatformLoggingMXBean 2520 * 2521 * @since 1.5 2522 */ 2523 public final static String LOGGING_MXBEAN_NAME 2524 = "java.util.logging:type=Logging"; 2525 2526 /** 2527 * Returns {@code LoggingMXBean} for managing loggers. 2528 * 2529 * @return a {@link LoggingMXBean} object. 2530 * 2531 * @deprecated {@code java.util.logging.LoggingMXBean} is deprecated and 2532 * replaced with {@code java.lang.management.PlatformLoggingMXBean}. Use 2533 * {@link java.lang.management.ManagementFactory#getPlatformMXBean(Class) 2534 * ManagementFactory.getPlatformMXBean}(PlatformLoggingMXBean.class) 2535 * instead. 2536 * 2537 * @see java.lang.management.PlatformLoggingMXBean 2538 * @since 1.5 2539 */ 2540 @Deprecated(since="9") 2541 public static synchronized LoggingMXBean getLoggingMXBean() { 2542 return Logging.getInstance(); 2543 } 2544 2545 /** 2546 * Adds a configuration listener to be invoked each time the logging 2547 * configuration is read. 2548 * If the listener is already registered the method does nothing. 2549 * <p> 2550 * The listener is invoked with privileges that are restricted by the 2551 * calling context of this method. 2552 * The order in which the listeners are invoked is unspecified. 2553 * <p> 2554 * It is recommended that listeners do not throw errors or exceptions. 2555 * 2556 * If a listener terminates with an uncaught error or exception then 2557 * the first exception will be propagated to the caller of 2558 * {@link #readConfiguration()} (or {@link #readConfiguration(java.io.InputStream)}) 2559 * after all listeners have been invoked. 2560 * 2561 * @implNote If more than one listener terminates with an uncaught error or 2562 * exception, an implementation may record the additional errors or 2563 * exceptions as {@linkplain Throwable#addSuppressed(java.lang.Throwable) 2564 * suppressed exceptions}. 2565 * 2566 * @param listener A configuration listener that will be invoked after the 2567 * configuration changed. 2568 * @return This LogManager. 2569 * @throws SecurityException if a security manager exists and if the 2570 * caller does not have LoggingPermission("control"). 2571 * @throws NullPointerException if the listener is null. 2572 * 2573 * @since 9 2574 */ 2575 public LogManager addConfigurationListener(Runnable listener) { 2576 final Runnable r = Objects.requireNonNull(listener); 2577 checkPermission(); 2578 final SecurityManager sm = System.getSecurityManager(); 2579 final AccessControlContext acc = 2580 sm == null ? null : AccessController.getContext(); 2581 final PrivilegedAction<Void> pa = 2582 acc == null ? null : () -> { r.run() ; return null; }; 2583 final Runnable pr = 2584 acc == null ? r : () -> AccessController.doPrivileged(pa, acc); 2585 // Will do nothing if already registered. 2586 listeners.putIfAbsent(r, pr); 2587 return this; 2588 } 2589 2590 /** 2591 * Removes a previously registered configuration listener. 2592 * 2593 * Returns silently if the listener is not found. 2594 * 2595 * @param listener the configuration listener to remove. 2596 * @throws NullPointerException if the listener is null. 2597 * @throws SecurityException if a security manager exists and if the 2598 * caller does not have LoggingPermission("control"). 2599 * 2600 * @since 9 2601 */ 2602 public void removeConfigurationListener(Runnable listener) { 2603 final Runnable key = Objects.requireNonNull(listener); 2604 checkPermission(); 2605 listeners.remove(key); 2606 } 2607 2608 private void invokeConfigurationListeners() { 2609 Throwable t = null; 2610 2611 // We're using an IdentityHashMap because we want to compare 2612 // keys using identity (==). 2613 // We don't want to loop within a block synchronized on 'listeners' 2614 // to avoid invoking listeners from yet another synchronized block. 2615 // So we're taking a snapshot of the values list to avoid the risk of 2616 // ConcurrentModificationException while looping. 2617 // 2618 for (Runnable c : listeners.values().toArray(new Runnable[0])) { 2619 try { 2620 c.run(); 2621 } catch (ThreadDeath death) { 2622 throw death; 2623 } catch (Error | RuntimeException x) { 2624 if (t == null) t = x; 2625 else t.addSuppressed(x); 2626 } 2627 } 2628 // Listeners are not supposed to throw exceptions, but if that 2629 // happens, we will rethrow the first error or exception that is raised 2630 // after all listeners have been invoked. 2631 if (t instanceof Error) throw (Error)t; 2632 if (t instanceof RuntimeException) throw (RuntimeException)t; 2633 } 2634 2635 /** 2636 * This class allows the {@link LoggingProviderImpl} to demand loggers on 2637 * behalf of system and application classes. 2638 */ 2639 private static final class LoggingProviderAccess 2640 implements LoggingProviderImpl.LogManagerAccess, 2641 PrivilegedAction<Void> { 2642 2643 private LoggingProviderAccess() { 2644 } 2645 2646 /** 2647 * Demands a logger on behalf of the given {@code module}. 2648 * <p> 2649 * If a named logger suitable for the given module is found 2650 * returns it. 2651 * Otherwise, creates a new logger suitable for the given module. 2652 * 2653 * @param name The logger name. 2654 * @param module The module on which behalf the logger is created/retrieved. 2655 * @return A logger for the given {@code module}. 2656 * 2657 * @throws NullPointerException if {@code name} is {@code null} 2658 * or {@code module} is {@code null}. 2659 * @throws IllegalArgumentException if {@code manager} is not the default 2660 * LogManager. 2661 * @throws SecurityException if a security manager is present and the 2662 * calling code doesn't have the 2663 * {@link LoggingPermission LoggingPermission("demandLogger", null)}. 2664 */ 2665 @Override 2666 public Logger demandLoggerFor(LogManager manager, String name, Module module) { 2667 if (manager != getLogManager()) { 2668 // having LogManager as parameter just ensures that the 2669 // caller will have initialized the LogManager before reaching 2670 // here. 2671 throw new IllegalArgumentException("manager"); 2672 } 2673 Objects.requireNonNull(name); 2674 Objects.requireNonNull(module); 2675 SecurityManager sm = System.getSecurityManager(); 2676 if (sm != null) { 2677 sm.checkPermission(controlPermission); 2678 } 2679 if (isSystem(module)) { 2680 return manager.demandSystemLogger(name, 2681 Logger.SYSTEM_LOGGER_RB_NAME, module); 2682 } else { 2683 return manager.demandLogger(name, null, module); 2684 } 2685 } 2686 2687 @Override 2688 public Void run() { 2689 LoggingProviderImpl.setLogManagerAccess(INSTANCE); 2690 return null; 2691 } 2692 2693 static final LoggingProviderAccess INSTANCE = new LoggingProviderAccess(); 2694 } 2695 2696 static { 2697 AccessController.doPrivileged(LoggingProviderAccess.INSTANCE, null, 2698 controlPermission); 2699 } 2700 2701 }