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