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