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.concurrent.ConcurrentHashMap; 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; 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.jmx.Server; 039import org.apache.logging.log4j.core.util.Assert; 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; 046 047import 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 */ 056public 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 volatile 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 * Starts 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 * Sets 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 * Obtains 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 * Obtains 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 * Determines 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 * Adds 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 * Sets the Configuration to be used. 343 * @param config The new Configuration. 344 * @return The previous Configuration. 345 */ 346 private Configuration setConfiguration(final Configuration config) { 347 Assert.requireNonNull(config, "No Configuration was provided"); 348 configLock.lock(); 349 try { 350 final Configuration prev = this.config; 351 config.addListener(this); 352 final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES); 353 354 try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException 355 map.putIfAbsent("hostName", NetUtils.getLocalHostname()); 356 } catch (final Exception ex) { 357 LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString()); 358 map.putIfAbsent("hostName", "unknown"); 359 } 360 map.putIfAbsent("contextName", name); 361 config.start(); 362 this.config = config; 363 updateLoggers(); 364 if (prev != null) { 365 prev.removeListener(this); 366 prev.stop(); 367 } 368 369 firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config)); 370 371 try { 372 Server.reregisterMBeansAfterReconfigure(); 373 } catch (final Throwable t) { 374 // LOG4J2-716: Android has no java.lang.management 375 LOGGER.error("Could not reconfigure JMX", t); 376 } 377 return prev; 378 } finally { 379 configLock.unlock(); 380 } 381 } 382 383 private void firePropertyChangeEvent(final PropertyChangeEvent event) { 384 for (final PropertyChangeListener listener : propertyChangeListeners) { 385 listener.propertyChange(event); 386 } 387 } 388 389 public void addPropertyChangeListener(final PropertyChangeListener listener) { 390 propertyChangeListeners.add(Assert.requireNonNull(listener, "listener")); 391 } 392 393 public void removePropertyChangeListener(final PropertyChangeListener listener) { 394 propertyChangeListeners.remove(listener); 395 } 396 397 /** 398 * Returns the initial configuration location or {@code null}. The returned value may not be the location of the 399 * current configuration. Use 400 * {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() getConfigurationSource()}.{@link 401 * ConfigurationSource#getLocation() getLocation()} to get the actual source of the current configuration. 402 * @return the initial configuration location or {@code null} 403 */ 404 public URI getConfigLocation() { 405 return configLocation; 406 } 407 408 /** 409 * Sets the configLocation to the specified value and reconfigures this context. 410 * @param configLocation the location of the new configuration 411 */ 412 public void setConfigLocation(final URI configLocation) { 413 this.configLocation = configLocation; 414 415 reconfigure(configLocation); 416 } 417 418 /** 419 * Reconfigure the context. 420 */ 421 private void reconfigure(final URI configURI) { 422 final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null; 423 LOGGER.debug("Reconfiguration started for context[name={}] at {} ({}) with optional ClassLoader: {}", name, 424 configURI, this, cl); 425 final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configURI, cl); 426 setConfiguration(instance); 427 /* 428 * instance.start(); Configuration old = setConfiguration(instance); 429 * updateLoggers(); if (old != null) { old.stop(); } 430 */ 431 432 LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({}) with optional ClassLoader: {}", name, 433 configURI, this, cl); 434 } 435 436 /** 437 * Reconfigure the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new 438 * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old 439 * LoggerConfig, along with old Appenders and Filters. 440 */ 441 public void reconfigure() { 442 reconfigure(configLocation); 443 } 444 445 /** 446 * Causes all Loggers to be updated against the current Configuration. 447 */ 448 public void updateLoggers() { 449 updateLoggers(this.config); 450 } 451 452 /** 453 * Causes all Logger to be updated against the specified Configuration. 454 * @param config The Configuration. 455 */ 456 public void updateLoggers(final Configuration config) { 457 for (final Logger logger : loggers.values()) { 458 logger.updateConfiguration(config); 459 } 460 } 461 462 /** 463 * Causes a reconfiguration to take place when the underlying configuration 464 * file changes. 465 * 466 * @param reconfigurable The Configuration that can be reconfigured. 467 */ 468 @Override 469 public synchronized void onChange(final Reconfigurable reconfigurable) { 470 LOGGER.debug("Reconfiguration started for context {} ({})", name, this); 471 final Configuration newConfig = reconfigurable.reconfigure(); 472 if (newConfig != null) { 473 setConfiguration(newConfig); 474 LOGGER.debug("Reconfiguration completed for {} ({})", name, this); 475 } else { 476 LOGGER.debug("Reconfiguration failed for {} ({})", name, this); 477 } 478 } 479 480 // LOG4J2-151: changed visibility from private to protected 481 protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) { 482 return new Logger(ctx, name, messageFactory); 483 } 484 485}