001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core; 018 019import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER; 020 021import java.beans.PropertyChangeEvent; 022import java.beans.PropertyChangeListener; 023import java.io.File; 024import java.net.URI; 025import java.util.Collection; 026import java.util.Objects; 027import java.util.concurrent.ConcurrentMap; 028import java.util.concurrent.CopyOnWriteArrayList; 029import java.util.concurrent.TimeUnit; 030import java.util.concurrent.locks.Lock; 031import java.util.concurrent.locks.ReentrantLock; 032 033import org.apache.logging.log4j.LogManager; 034import org.apache.logging.log4j.core.config.Configuration; 035import org.apache.logging.log4j.core.config.ConfigurationFactory; 036import org.apache.logging.log4j.core.config.ConfigurationListener; 037import org.apache.logging.log4j.core.config.ConfigurationSource; 038import org.apache.logging.log4j.core.config.DefaultConfiguration; 039import org.apache.logging.log4j.core.config.NullConfiguration; 040import org.apache.logging.log4j.core.config.Reconfigurable; 041import org.apache.logging.log4j.core.impl.Log4jLogEvent; 042import org.apache.logging.log4j.core.jmx.Server; 043import org.apache.logging.log4j.core.util.Cancellable; 044import org.apache.logging.log4j.core.util.ExecutorServices; 045import org.apache.logging.log4j.core.util.Loader; 046import org.apache.logging.log4j.core.util.NetUtils; 047import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; 048import org.apache.logging.log4j.message.MessageFactory; 049import org.apache.logging.log4j.spi.AbstractLogger; 050import org.apache.logging.log4j.spi.LoggerContextFactory; 051import org.apache.logging.log4j.spi.LoggerRegistry; 052import org.apache.logging.log4j.spi.Terminable; 053import org.apache.logging.log4j.spi.ThreadContextMapFactory; 054import org.apache.logging.log4j.util.PropertiesUtil; 055 056 057/** 058 * The LoggerContext is the anchor for the logging system. It maintains a list of all the loggers requested by 059 * applications and a reference to the Configuration. The Configuration will contain the configured loggers, appenders, 060 * filters, etc and will be atomically updated whenever a reconfigure occurs. 061 */ 062public class LoggerContext extends AbstractLifeCycle 063 implements org.apache.logging.log4j.spi.LoggerContext, AutoCloseable, Terminable, ConfigurationListener { 064 065 static { 066 try { 067 // LOG4J2-1642 preload ExecutorServices as it is used in shutdown hook 068 Loader.loadClass(ExecutorServices.class.getName()); 069 } catch (final Exception e) { 070 LOGGER.error("Failed to preload ExecutorServices class.", e); 071 } 072 } 073 074 /** 075 * Property name of the property change event fired if the configuration is changed. 076 */ 077 public static final String PROPERTY_CONFIG = "config"; 078 079 private static final Configuration NULL_CONFIGURATION = new NullConfiguration(); 080 081 private final LoggerRegistry<Logger> loggerRegistry = new LoggerRegistry<>(); 082 private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<>(); 083 084 /** 085 * The Configuration is volatile to guarantee that initialization of the Configuration has completed before the 086 * reference is updated. 087 */ 088 private volatile Configuration configuration = new DefaultConfiguration(); 089 private Object externalContext; 090 private String contextName; 091 private volatile URI configLocation; 092 private Cancellable shutdownCallback; 093 094 private final Lock configLock = new ReentrantLock(); 095 096 /** 097 * Constructor taking only a name. 098 * 099 * @param name The context name. 100 */ 101 public LoggerContext(final String name) { 102 this(name, null, (URI) null); 103 } 104 105 /** 106 * Constructor taking a name and a reference to an external context. 107 * 108 * @param name The context name. 109 * @param externalContext The external context. 110 */ 111 public LoggerContext(final String name, final Object externalContext) { 112 this(name, externalContext, (URI) null); 113 } 114 115 /** 116 * Constructor taking a name, external context and a configuration URI. 117 * 118 * @param name The context name. 119 * @param externalContext The external context. 120 * @param configLocn The location of the configuration as a URI. 121 */ 122 public LoggerContext(final String name, final Object externalContext, final URI configLocn) { 123 this.contextName = name; 124 this.externalContext = externalContext; 125 this.configLocation = configLocn; 126 } 127 128 /** 129 * Constructor taking a name external context and a configuration location String. The location must be resolvable 130 * to a File. 131 * 132 * @param name The configuration location. 133 * @param externalContext The external context. 134 * @param configLocn The configuration location. 135 */ 136 public LoggerContext(final String name, final Object externalContext, final String configLocn) { 137 this.contextName = name; 138 this.externalContext = externalContext; 139 if (configLocn != null) { 140 URI uri; 141 try { 142 uri = new File(configLocn).toURI(); 143 } catch (final Exception ex) { 144 uri = null; 145 } 146 configLocation = uri; 147 } else { 148 configLocation = null; 149 } 150 } 151 152 /** 153 * Returns the current LoggerContext. 154 * <p> 155 * Avoids the type cast for: 156 * </p> 157 * 158 * <pre> 159 * (LoggerContext) LogManager.getContext(); 160 * </pre> 161 * 162 * <p> 163 * WARNING - The LoggerContext returned by this method may not be the LoggerContext used to create a Logger for the 164 * calling class. 165 * </p> 166 * 167 * @return The current LoggerContext. 168 * @see LogManager#getContext() 169 */ 170 public static LoggerContext getContext() { 171 return (LoggerContext) LogManager.getContext(); 172 } 173 174 /** 175 * Returns a LoggerContext. 176 * <p> 177 * Avoids the type cast for: 178 * </p> 179 * 180 * <pre> 181 * (LoggerContext) LogManager.getContext(currentContext); 182 * </pre> 183 * 184 * @param currentContext if false the LoggerContext appropriate for the caller of this method is returned. For 185 * example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be 186 * returned and if the caller is a class in the container's classpath then a different LoggerContext may 187 * be returned. If true then only a single LoggerContext will be returned. 188 * @return a LoggerContext. 189 * @see LogManager#getContext(boolean) 190 */ 191 public static LoggerContext getContext(final boolean currentContext) { 192 return (LoggerContext) LogManager.getContext(currentContext); 193 } 194 195 /** 196 * Returns a LoggerContext. 197 * <p> 198 * Avoids the type cast for: 199 * </p> 200 * 201 * <pre> 202 * (LoggerContext) LogManager.getContext(loader, currentContext, configLocation); 203 * </pre> 204 * 205 * @param loader The ClassLoader for the context. If null the context will attempt to determine the appropriate 206 * ClassLoader. 207 * @param currentContext if false the LoggerContext appropriate for the caller of this method is returned. For 208 * example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be 209 * returned and if the caller is a class in the container's classpath then a different LoggerContext may 210 * be returned. If true then only a single LoggerContext will be returned. 211 * @param configLocation The URI for the configuration to use. 212 * @return a LoggerContext. 213 * @see LogManager#getContext(ClassLoader, boolean, URI) 214 */ 215 public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext, 216 final URI configLocation) { 217 return (LoggerContext) LogManager.getContext(loader, currentContext, configLocation); 218 } 219 220 @Override 221 public void start() { 222 LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this); 223 if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) { 224 LOGGER.debug("Stack trace to locate invoker", 225 new Exception("Not a real error, showing stack trace to locate invoker")); 226 } 227 if (configLock.tryLock()) { 228 try { 229 if (this.isInitialized() || this.isStopped()) { 230 this.setStarting(); 231 reconfigure(); 232 if (this.configuration.isShutdownHookEnabled()) { 233 setUpShutdownHook(); 234 } 235 this.setStarted(); 236 } 237 } finally { 238 configLock.unlock(); 239 } 240 } 241 LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this); 242 } 243 244 /** 245 * Starts with a specific configuration. 246 * 247 * @param config The new Configuration. 248 */ 249 public void start(final Configuration config) { 250 LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config); 251 if (configLock.tryLock()) { 252 try { 253 if (this.isInitialized() || this.isStopped()) { 254 if (this.configuration.isShutdownHookEnabled()) { 255 setUpShutdownHook(); 256 } 257 this.setStarted(); 258 } 259 } finally { 260 configLock.unlock(); 261 } 262 } 263 setConfiguration(config); 264 LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config); 265 } 266 267 private void setUpShutdownHook() { 268 if (shutdownCallback == null) { 269 final LoggerContextFactory factory = LogManager.getFactory(); 270 if (factory instanceof ShutdownCallbackRegistry) { 271 LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Shutdown hook enabled. Registering a new one."); 272 try { 273 final long shutdownTimeoutMillis = this.configuration.getShutdownTimeoutMillis(); 274 this.shutdownCallback = ((ShutdownCallbackRegistry) factory).addShutdownCallback(new Runnable() { 275 @Override 276 public void run() { 277 @SuppressWarnings("resource") 278 final LoggerContext context = LoggerContext.this; 279 LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Stopping LoggerContext[name={}, {}]", 280 context.getName(), context); 281 context.stop(shutdownTimeoutMillis, TimeUnit.MILLISECONDS); 282 } 283 284 @Override 285 public String toString() { 286 return "Shutdown callback for LoggerContext[name=" + LoggerContext.this.getName() + ']'; 287 } 288 }); 289 } catch (final IllegalStateException e) { 290 throw new IllegalStateException( 291 "Unable to register Log4j shutdown hook because JVM is shutting down.", e); 292 } catch (final SecurityException e) { 293 LOGGER.error(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook due to security restrictions", 294 e); 295 } 296 } 297 } 298 } 299 300 @Override 301 public void close() { 302 stop(); 303 } 304 305 @Override 306 public void terminate() { 307 stop(); 308 } 309 310 /** 311 * Blocks until all Log4j tasks have completed execution after a shutdown request and all appenders have shut down, 312 * or the timeout occurs, or the current thread is interrupted, whichever happens first. 313 * <p> 314 * Not all appenders will honor this, it is a hint and not an absolute guarantee that the this method not block longer. 315 * Setting timeout too low increase the risk of losing outstanding log events not yet written to the final 316 * destination. 317 * <p> 318 * Log4j can start threads to perform certain actions like file rollovers, calling this method with a positive timeout will 319 * block until the rollover thread is done. 320 * 321 * @param timeout the maximum time to wait, or 0 which mean that each apppender uses its default timeout, and don't wait for background 322 tasks 323 * @param timeUnit 324 * the time unit of the timeout argument 325 * @return {@code true} if the logger context terminated and {@code false} if the timeout elapsed before 326 * termination. 327 * @since 2.7 328 */ 329 @Override 330 public boolean stop(final long timeout, final TimeUnit timeUnit) { 331 LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this); 332 configLock.lock(); 333 try { 334 if (this.isStopped()) { 335 return true; 336 } 337 338 this.setStopping(); 339 try { 340 Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500 341 } catch (final LinkageError | Exception e) { 342 // LOG4J2-1506 Hello Android, GAE 343 LOGGER.error("Unable to unregister MBeans", e); 344 } 345 if (shutdownCallback != null) { 346 shutdownCallback.cancel(); 347 shutdownCallback = null; 348 } 349 final Configuration prev = configuration; 350 configuration = NULL_CONFIGURATION; 351 updateLoggers(); 352 if (prev instanceof LifeCycle2) { 353 ((LifeCycle2) prev).stop(timeout, timeUnit); 354 } else { 355 prev.stop(); 356 } 357 externalContext = null; 358 LogManager.getFactory().removeContext(this); 359 } finally { 360 configLock.unlock(); 361 this.setStopped(); 362 } 363 LOGGER.debug("Stopped LoggerContext[name={}, {}] with status {}", getName(), this, true); 364 return true; 365 } 366 367 /** 368 * Gets the name. 369 * 370 * @return the name. 371 */ 372 public String getName() { 373 return contextName; 374 } 375 376 /** 377 * Gets the root logger. 378 * 379 * @return the root logger. 380 */ 381 public Logger getRootLogger() { 382 return getLogger(LogManager.ROOT_LOGGER_NAME); 383 } 384 385 /** 386 * Sets the name. 387 * 388 * @param name the new LoggerContext name 389 * @throws NullPointerException if the specified name is {@code null} 390 */ 391 public void setName(final String name) { 392 contextName = Objects.requireNonNull(name); 393 } 394 395 /** 396 * Sets the external context. 397 * 398 * @param context The external context. 399 */ 400 public void setExternalContext(final Object context) { 401 this.externalContext = context; 402 } 403 404 /** 405 * Returns the external context. 406 * 407 * @return The external context. 408 */ 409 @Override 410 public Object getExternalContext() { 411 return this.externalContext; 412 } 413 414 /** 415 * Gets a Logger from the Context. 416 * 417 * @param name The name of the Logger to return. 418 * @return The Logger. 419 */ 420 @Override 421 public Logger getLogger(final String name) { 422 return getLogger(name, null); 423 } 424 425 /** 426 * Gets a collection of the current loggers. 427 * <p> 428 * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this 429 * collection at your own risk. 430 * </p> 431 * 432 * @return a collection of the current loggers. 433 */ 434 public Collection<Logger> getLoggers() { 435 return loggerRegistry.getLoggers(); 436 } 437 438 /** 439 * Obtains a Logger from the Context. 440 * 441 * @param name The name of the Logger to return. 442 * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change the 443 * logger but will log a warning if mismatched. 444 * @return The Logger. 445 */ 446 @Override 447 public Logger getLogger(final String name, final MessageFactory messageFactory) { 448 // Note: This is the only method where we add entries to the 'loggerRegistry' ivar. 449 Logger logger = loggerRegistry.getLogger(name, messageFactory); 450 if (logger != null) { 451 AbstractLogger.checkMessageFactory(logger, messageFactory); 452 return logger; 453 } 454 455 logger = newInstance(this, name, messageFactory); 456 loggerRegistry.putIfAbsent(name, messageFactory, logger); 457 return loggerRegistry.getLogger(name, messageFactory); 458 } 459 460 /** 461 * Determines if the specified Logger exists. 462 * 463 * @param name The Logger name to search for. 464 * @return True if the Logger exists, false otherwise. 465 */ 466 @Override 467 public boolean hasLogger(final String name) { 468 return loggerRegistry.hasLogger(name); 469 } 470 471 /** 472 * Determines if the specified Logger exists. 473 * 474 * @param name The Logger name to search for. 475 * @return True if the Logger exists, false otherwise. 476 */ 477 @Override 478 public boolean hasLogger(final String name, final MessageFactory messageFactory) { 479 return loggerRegistry.hasLogger(name, messageFactory); 480 } 481 482 /** 483 * Determines if the specified Logger exists. 484 * 485 * @param name The Logger name to search for. 486 * @return True if the Logger exists, false otherwise. 487 */ 488 @Override 489 public boolean hasLogger(final String name, final Class<? extends MessageFactory> messageFactoryClass) { 490 return loggerRegistry.hasLogger(name, messageFactoryClass); 491 } 492 493 /** 494 * Returns the current Configuration. The Configuration will be replaced when a reconfigure occurs. 495 * 496 * @return The current Configuration, never {@code null}, but may be 497 * {@link org.apache.logging.log4j.core.config.NullConfiguration}. 498 */ 499 public Configuration getConfiguration() { 500 return configuration; 501 } 502 503 /** 504 * Adds a Filter to the Configuration. Filters that are added through the API will be lost when a reconfigure 505 * occurs. 506 * 507 * @param filter The Filter to add. 508 */ 509 public void addFilter(final Filter filter) { 510 configuration.addFilter(filter); 511 } 512 513 /** 514 * Removes a Filter from the current Configuration. 515 * 516 * @param filter The Filter to remove. 517 */ 518 public void removeFilter(final Filter filter) { 519 configuration.removeFilter(filter); 520 } 521 522 /** 523 * Sets the Configuration to be used. 524 * 525 * @param config The new Configuration. 526 * @return The previous Configuration. 527 */ 528 public Configuration setConfiguration(final Configuration config) { 529 if (config == null) { 530 LOGGER.error("No configuration found for context '{}'.", contextName); 531 // No change, return the current configuration. 532 return this.configuration; 533 } 534 configLock.lock(); 535 try { 536 final Configuration prev = this.configuration; 537 config.addListener(this); 538 539 final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES); 540 541 try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException 542 map.putIfAbsent("hostName", NetUtils.getLocalHostname()); 543 } catch (final Exception ex) { 544 LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString()); 545 map.putIfAbsent("hostName", "unknown"); 546 } 547 map.putIfAbsent("contextName", contextName); 548 config.start(); 549 this.configuration = config; 550 updateLoggers(); 551 if (prev != null) { 552 prev.removeListener(this); 553 prev.stop(); 554 } 555 556 firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config)); 557 558 try { 559 Server.reregisterMBeansAfterReconfigure(); 560 } catch (final LinkageError | Exception e) { 561 // LOG4J2-716: Android has no java.lang.management 562 LOGGER.error("Could not reconfigure JMX", e); 563 } 564 // AsyncLoggers update their nanoClock when the configuration changes 565 Log4jLogEvent.setNanoClock(configuration.getNanoClock()); 566 567 return prev; 568 } finally { 569 configLock.unlock(); 570 } 571 } 572 573 private void firePropertyChangeEvent(final PropertyChangeEvent event) { 574 for (final PropertyChangeListener listener : propertyChangeListeners) { 575 listener.propertyChange(event); 576 } 577 } 578 579 public void addPropertyChangeListener(final PropertyChangeListener listener) { 580 propertyChangeListeners.add(Objects.requireNonNull(listener, "listener")); 581 } 582 583 public void removePropertyChangeListener(final PropertyChangeListener listener) { 584 propertyChangeListeners.remove(listener); 585 } 586 587 /** 588 * Returns the initial configuration location or {@code null}. The returned value may not be the location of the 589 * current configuration. Use {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() 590 * getConfigurationSource()}.{@link ConfigurationSource#getLocation() getLocation()} to get the actual source of the 591 * current configuration. 592 * 593 * @return the initial configuration location or {@code null} 594 */ 595 public URI getConfigLocation() { 596 return configLocation; 597 } 598 599 /** 600 * Sets the configLocation to the specified value and reconfigures this context. 601 * 602 * @param configLocation the location of the new configuration 603 */ 604 public void setConfigLocation(final URI configLocation) { 605 this.configLocation = configLocation; 606 reconfigure(configLocation); 607 } 608 609 /** 610 * Reconfigures the context. 611 */ 612 private void reconfigure(final URI configURI) { 613 final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null; 614 LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}", 615 contextName, configURI, this, cl); 616 final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl); 617 if (instance == null) { 618 LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl); 619 } else { 620 setConfiguration(instance); 621 /* 622 * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) { 623 * old.stop(); } 624 */ 625 final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource()); 626 LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}", 627 contextName, location, this, cl); 628 } 629 } 630 631 /** 632 * Reconfigures the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new 633 * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old 634 * LoggerConfig, along with old Appenders and Filters. 635 */ 636 public void reconfigure() { 637 reconfigure(configLocation); 638 } 639 640 /** 641 * Causes all Loggers to be updated against the current Configuration. 642 */ 643 public void updateLoggers() { 644 updateLoggers(this.configuration); 645 } 646 647 /** 648 * Causes all Logger to be updated against the specified Configuration. 649 * 650 * @param config The Configuration. 651 */ 652 public void updateLoggers(final Configuration config) { 653 final Configuration old = this.configuration; 654 for (final Logger logger : loggerRegistry.getLoggers()) { 655 logger.updateConfiguration(config); 656 } 657 firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, old, config)); 658 } 659 660 /** 661 * Causes a reconfiguration to take place when the underlying configuration file changes. 662 * 663 * @param reconfigurable The Configuration that can be reconfigured. 664 */ 665 @Override 666 public synchronized void onChange(final Reconfigurable reconfigurable) { 667 final long startMillis = System.currentTimeMillis(); 668 LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this); 669 initApiModule(); 670 final Configuration newConfig = reconfigurable.reconfigure(); 671 if (newConfig != null) { 672 setConfiguration(newConfig); 673 LOGGER.debug("Reconfiguration completed for {} ({}) in {} milliseconds.", contextName, this, 674 System.currentTimeMillis() - startMillis); 675 } else { 676 LOGGER.debug("Reconfiguration failed for {} ({}) in {} milliseconds.", contextName, this, 677 System.currentTimeMillis() - startMillis); 678 } 679 } 680 681 private void initApiModule() { 682 ThreadContextMapFactory.init(); // Or make public and call ThreadContext.init() which calls ThreadContextMapFactory.init(). 683 } 684 685 // LOG4J2-151: changed visibility from private to protected 686 protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) { 687 return new Logger(ctx, name, messageFactory); 688 } 689 690}