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