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