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