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 */ 017 package org.apache.logging.log4j.core; 018 019 import java.beans.PropertyChangeEvent; 020 import java.beans.PropertyChangeListener; 021 import java.io.File; 022 import java.lang.ref.Reference; 023 import java.lang.ref.SoftReference; 024 import java.net.URI; 025 import java.util.Collection; 026 import java.util.concurrent.ConcurrentHashMap; 027 import java.util.concurrent.ConcurrentMap; 028 import java.util.concurrent.CopyOnWriteArrayList; 029 import java.util.concurrent.locks.Lock; 030 import java.util.concurrent.locks.ReentrantLock; 031 032 import org.apache.logging.log4j.LogManager; 033 import org.apache.logging.log4j.Marker; 034 import org.apache.logging.log4j.MarkerManager; 035 import org.apache.logging.log4j.core.config.Configuration; 036 import org.apache.logging.log4j.core.config.ConfigurationFactory; 037 import org.apache.logging.log4j.core.config.ConfigurationListener; 038 import org.apache.logging.log4j.core.config.ConfigurationSource; 039 import org.apache.logging.log4j.core.config.DefaultConfiguration; 040 import org.apache.logging.log4j.core.config.NullConfiguration; 041 import org.apache.logging.log4j.core.config.Reconfigurable; 042 import org.apache.logging.log4j.core.jmx.Server; 043 import org.apache.logging.log4j.core.util.Assert; 044 import org.apache.logging.log4j.core.util.NetUtils; 045 import org.apache.logging.log4j.message.MessageFactory; 046 import org.apache.logging.log4j.spi.AbstractLogger; 047 import org.apache.logging.log4j.util.PropertiesUtil; 048 049 /** 050 * The LoggerContext is the anchor for the logging system. It maintains a list 051 * of all the loggers requested by applications and a reference to the 052 * Configuration. The Configuration will contain the configured loggers, 053 * appenders, filters, etc and will be atomically updated whenever a reconfigure 054 * occurs. 055 */ 056 public class LoggerContext extends AbstractLifeCycle implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener { 057 058 private static final boolean SHUTDOWN_HOOK_ENABLED = 059 PropertiesUtil.getProperties().getBooleanProperty("log4j.shutdownHookEnabled", true); 060 061 public static final String PROPERTY_CONFIG = "config"; 062 private static final Marker SHUTDOWN_HOOK = MarkerManager.getMarker("SHUTDOWN HOOK"); 063 private static final Configuration NULL_CONFIGURATION = new NullConfiguration(); 064 065 private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<String, Logger>(); 066 private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>(); 067 068 /** 069 * The Configuration is volatile to guarantee that initialization of the 070 * Configuration has completed before the reference is updated. 071 */ 072 private volatile Configuration config = new DefaultConfiguration(); 073 private Object externalContext; 074 private final String name; 075 private URI configLocation; 076 077 /** 078 * Once a shutdown hook thread executes, we can't remove it from the runtime (throws an exception). Therefore, 079 * it's really pointless to keep a strongly accessible reference to said thread. Thus, please use a 080 * SoftReference here to prevent possible cyclic memory references. 081 */ 082 private Reference<Thread> shutdownThread; 083 084 private final Lock configLock = new ReentrantLock(); 085 086 /** 087 * Constructor taking only a name. 088 * @param name The context name. 089 */ 090 public LoggerContext(final String name) { 091 this(name, null, (URI) null); 092 } 093 094 /** 095 * Constructor taking a name and a reference to an external context. 096 * @param name The context name. 097 * @param externalContext The external context. 098 */ 099 public LoggerContext(final String name, final Object externalContext) { 100 this(name, externalContext, (URI) null); 101 } 102 103 /** 104 * Constructor taking a name, external context and a configuration URI. 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.name = name; 111 this.externalContext = externalContext; 112 this.configLocation = configLocn; 113 } 114 115 /** 116 * Constructor taking a name external context and a configuration location 117 * String. The location must be resolvable 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.name = 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 @Override 140 public void start() { 141 LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this); 142 if (configLock.tryLock()) { 143 try { 144 if (this.isInitialized() || this.isStopped()) { 145 this.setStarting(); 146 reconfigure(); 147 setUpShutdownHook(); 148 this.setStarted(); 149 } 150 } finally { 151 configLock.unlock(); 152 } 153 } 154 LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this); 155 } 156 157 /** 158 * Start with a specific configuration. 159 * @param config The new Configuration. 160 */ 161 public void start(final Configuration config) { 162 LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config); 163 if (configLock.tryLock()) { 164 try { 165 if (this.isInitialized() || this.isStopped()) { 166 setUpShutdownHook(); 167 this.setStarted(); 168 } 169 } finally { 170 configLock.unlock(); 171 } 172 } 173 setConfiguration(config); 174 LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config); 175 } 176 177 private void setUpShutdownHook() { 178 if (config.isShutdownHookEnabled() && SHUTDOWN_HOOK_ENABLED) { 179 LOGGER.debug(SHUTDOWN_HOOK, "Shutdown hook enabled. Registering a new one."); 180 shutdownThread = new SoftReference<Thread>( 181 new Thread(new ShutdownThread(this), "log4j-shutdown") 182 ); 183 addShutdownHook(); 184 } 185 } 186 187 private void addShutdownHook() { 188 final Thread hook = getShutdownThread(); 189 if (hook != null) { 190 try { 191 Runtime.getRuntime().addShutdownHook(hook); 192 } catch (final IllegalStateException ise) { 193 LOGGER.warn(SHUTDOWN_HOOK, "Unable to register shutdown hook due to JVM state"); 194 } catch (final SecurityException se) { 195 LOGGER.warn(SHUTDOWN_HOOK, "Unable to register shutdown hook due to security restrictions"); 196 } 197 } 198 } 199 200 private Thread getShutdownThread() { 201 return shutdownThread == null ? null : shutdownThread.get(); 202 } 203 204 @Override 205 public void stop() { 206 LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this); 207 configLock.lock(); 208 try { 209 if (this.isStopped()) { 210 return; 211 } 212 this.setStopping(); 213 tearDownShutdownHook(); 214 final Configuration prev = config; 215 config = NULL_CONFIGURATION; 216 updateLoggers(); 217 prev.stop(); 218 externalContext = null; 219 LogManager.getFactory().removeContext(this); 220 this.setStopped(); 221 } finally { 222 configLock.unlock(); 223 224 // in finally: unregister MBeans even if an exception occurred while stopping 225 Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500 226 } 227 LOGGER.debug("Stopped LoggerContext[name={}, {}]...", getName(), this); 228 } 229 230 private void tearDownShutdownHook() { 231 if (shutdownThread != null) { 232 LOGGER.debug(SHUTDOWN_HOOK, "Enqueue shutdown hook for garbage collection."); 233 shutdownThread.enqueue(); 234 } 235 } 236 237 /** 238 * Gets the name. 239 * 240 * @return the name. 241 */ 242 public String getName() { 243 return name; 244 } 245 246 /** 247 * Set the external context. 248 * @param context The external context. 249 */ 250 public void setExternalContext(final Object context) { 251 this.externalContext = context; 252 } 253 254 /** 255 * Returns the external context. 256 * @return The external context. 257 */ 258 @Override 259 public Object getExternalContext() { 260 return this.externalContext; 261 } 262 263 /** 264 * Obtain a Logger from the Context. 265 * @param name The name of the Logger to return. 266 * @return The Logger. 267 */ 268 @Override 269 public Logger getLogger(final String name) { 270 return getLogger(name, null); 271 } 272 273 /** 274 * Gets a collection of the current loggers. 275 * <p> 276 * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this collection at your own 277 * risk. 278 * </p> 279 * 280 * @return a collection of the current loggers. 281 */ 282 public Collection<Logger> getLoggers() { 283 return loggers.values(); 284 } 285 286 /** 287 * Obtain a Logger from the Context. 288 * @param name The name of the Logger to return. 289 * @param messageFactory The message factory is used only when creating a 290 * logger, subsequent use does not change the logger but will log 291 * a warning if mismatched. 292 * @return The Logger. 293 */ 294 @Override 295 public Logger getLogger(final String name, final MessageFactory messageFactory) { 296 Logger logger = loggers.get(name); 297 if (logger != null) { 298 AbstractLogger.checkMessageFactory(logger, messageFactory); 299 return logger; 300 } 301 302 logger = newInstance(this, name, messageFactory); 303 final Logger prev = loggers.putIfAbsent(name, logger); 304 return prev == null ? logger : prev; 305 } 306 307 /** 308 * Determine if the specified Logger exists. 309 * @param name The Logger name to search for. 310 * @return True if the Logger exists, false otherwise. 311 */ 312 @Override 313 public boolean hasLogger(final String name) { 314 return loggers.containsKey(name); 315 } 316 317 /** 318 * Returns the current Configuration. The Configuration will be replaced 319 * when a reconfigure occurs. 320 * 321 * @return The Configuration. 322 */ 323 public Configuration getConfiguration() { 324 return config; 325 } 326 327 /** 328 * Add a Filter to the Configuration. Filters that are added through the API will be lost 329 * when a reconfigure occurs. 330 * @param filter The Filter to add. 331 */ 332 public void addFilter(final Filter filter) { 333 config.addFilter(filter); 334 } 335 336 /** 337 * Removes a Filter from the current Configuration. 338 * @param filter The Filter to remove. 339 */ 340 public void removeFilter(final Filter filter) { 341 config.removeFilter(filter); 342 } 343 344 /** 345 * Set the Configuration to be used. 346 * @param config The new Configuration. 347 * @return The previous Configuration. 348 */ 349 private synchronized Configuration setConfiguration(final Configuration config) { 350 if (config == null) { 351 throw new NullPointerException("No Configuration was provided"); 352 } 353 final Configuration prev = this.config; 354 config.addListener(this); 355 final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES); 356 map.putIfAbsent("hostName", NetUtils.getLocalHostname()); 357 map.putIfAbsent("contextName", name); 358 config.start(); 359 this.config = config; 360 updateLoggers(); 361 if (prev != null) { 362 prev.removeListener(this); 363 prev.stop(); 364 } 365 366 firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config)); 367 368 try { 369 Server.reregisterMBeansAfterReconfigure(); 370 } catch (final Exception ex) { 371 LOGGER.error("Could not reconfigure JMX", ex); 372 } 373 return prev; 374 } 375 376 private void firePropertyChangeEvent(final PropertyChangeEvent event) { 377 for (final PropertyChangeListener listener : propertyChangeListeners) { 378 listener.propertyChange(event); 379 } 380 } 381 382 public void addPropertyChangeListener(final PropertyChangeListener listener) { 383 propertyChangeListeners.add(Assert.requireNonNull(listener, "listener")); 384 } 385 386 public void removePropertyChangeListener(final PropertyChangeListener listener) { 387 propertyChangeListeners.remove(listener); 388 } 389 390 /** 391 * Returns the initial configuration location or {@code null}. The returned value may not be the location of the 392 * current configuration. Use 393 * {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() getConfigurationSource()}.{@link 394 * ConfigurationSource#getLocation() getLocation()} to get the actual source of the current configuration. 395 * @return the initial configuration location or {@code null} 396 */ 397 public synchronized URI getConfigLocation() { 398 return configLocation; 399 } 400 401 /** 402 * Sets the configLocation to the specified value and reconfigures this context. 403 * @param configLocation the location of the new configuration 404 */ 405 public synchronized void setConfigLocation(final URI configLocation) { 406 this.configLocation = configLocation; 407 reconfigure(); 408 } 409 410 /** 411 * Reconfigure the context. 412 */ 413 public synchronized void reconfigure() { 414 LOGGER.debug("Reconfiguration started for context[name={}] at {} ({})", name, configLocation, this); 415 final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation); 416 setConfiguration(instance); 417 /* 418 * instance.start(); Configuration old = setConfiguration(instance); 419 * updateLoggers(); if (old != null) { old.stop(); } 420 */ 421 422 LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({})", name, configLocation, this); 423 } 424 425 /** 426 * Cause all Loggers to be updated against the current Configuration. 427 */ 428 public void updateLoggers() { 429 updateLoggers(this.config); 430 } 431 432 /** 433 * Cause all Logger to be updated against the specified Configuration. 434 * @param config The Configuration. 435 */ 436 public void updateLoggers(final Configuration config) { 437 for (final Logger logger : loggers.values()) { 438 logger.updateConfiguration(config); 439 } 440 } 441 442 /** 443 * Cause a reconfiguration to take place when the underlying configuration 444 * file changes. 445 * 446 * @param reconfigurable The Configuration that can be reconfigured. 447 */ 448 @Override 449 public synchronized void onChange(final Reconfigurable reconfigurable) { 450 LOGGER.debug("Reconfiguration started for context {} ({})", name, this); 451 final Configuration config = reconfigurable.reconfigure(); 452 if (config != null) { 453 setConfiguration(config); 454 LOGGER.debug("Reconfiguration completed for {} ({})", name, this); 455 } else { 456 LOGGER.debug("Reconfiguration failed for {} ({})", name, this); 457 } 458 } 459 460 // LOG4J2-151: changed visibility from private to protected 461 protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) { 462 return new Logger(ctx, name, messageFactory); 463 } 464 465 private static class ShutdownThread implements Runnable { 466 467 private final LoggerContext context; 468 469 public ShutdownThread(final LoggerContext context) { 470 this.context = context; 471 } 472 473 @Override 474 public void run() { 475 LOGGER.debug("ShutdownThread stopping LoggerContext[name={}, {}]...", context.getName(), context); 476 context.stop(); 477 LOGGER.debug("ShutdownThread stopped LoggerContext[name={}, {}].", context.getName(), context); 478 } 479 } 480 481 }