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; // SUPPRESS CHECKSTYLE 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 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 final 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.fatal(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 * Sets the external context. 324 * 325 * @param context The external context. 326 */ 327 public void setExternalContext(final Object context) { 328 this.externalContext = context; 329 } 330 331 /** 332 * Returns the external context. 333 * 334 * @return The external context. 335 */ 336 @Override 337 public Object getExternalContext() { 338 return this.externalContext; 339 } 340 341 /** 342 * Obtains a Logger from the Context. 343 * 344 * @param name The name of the Logger to return. 345 * @return The Logger. 346 */ 347 @Override 348 public Logger getLogger(final String name) { 349 return getLogger(name, null); 350 } 351 352 /** 353 * Gets a collection of the current loggers. 354 * <p> 355 * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this 356 * collection at your own risk. 357 * </p> 358 * 359 * @return a collection of the current loggers. 360 */ 361 public Collection<Logger> getLoggers() { 362 return loggers.values(); 363 } 364 365 /** 366 * Obtains a Logger from the Context. 367 * 368 * @param name The name of the Logger to return. 369 * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change the 370 * logger but will log a warning if mismatched. 371 * @return The Logger. 372 */ 373 @Override 374 public Logger getLogger(final String name, final MessageFactory messageFactory) { 375 Logger logger = loggers.get(name); 376 if (logger != null) { 377 AbstractLogger.checkMessageFactory(logger, messageFactory); 378 return logger; 379 } 380 381 logger = newInstance(this, name, messageFactory); 382 final Logger prev = loggers.putIfAbsent(name, logger); 383 return prev == null ? logger : prev; 384 } 385 386 /** 387 * Determines if the specified Logger exists. 388 * 389 * @param name The Logger name to search for. 390 * @return True if the Logger exists, false otherwise. 391 */ 392 @Override 393 public boolean hasLogger(final String name) { 394 return loggers.containsKey(name); 395 } 396 397 /** 398 * Returns the current Configuration. The Configuration will be replaced when a reconfigure occurs. 399 * 400 * @return The Configuration. 401 */ 402 public Configuration getConfiguration() { 403 return configuration; 404 } 405 406 /** 407 * Adds a Filter to the Configuration. Filters that are added through the API will be lost when a reconfigure 408 * occurs. 409 * 410 * @param filter The Filter to add. 411 */ 412 public void addFilter(final Filter filter) { 413 configuration.addFilter(filter); 414 } 415 416 /** 417 * Removes a Filter from the current Configuration. 418 * 419 * @param filter The Filter to remove. 420 */ 421 public void removeFilter(final Filter filter) { 422 configuration.removeFilter(filter); 423 } 424 425 /** 426 * Sets the Configuration to be used. 427 * 428 * @param config The new Configuration. 429 * @return The previous Configuration. 430 */ 431 private Configuration setConfiguration(final Configuration config) { 432 Objects.requireNonNull(config, "No Configuration was provided"); 433 configLock.lock(); 434 try { 435 final Configuration prev = this.configuration; 436 config.addListener(this); 437 final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES); 438 439 try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException 440 map.putIfAbsent("hostName", NetUtils.getLocalHostname()); 441 } catch (final Exception ex) { 442 LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString()); 443 map.putIfAbsent("hostName", "unknown"); 444 } 445 map.putIfAbsent("contextName", contextName); 446 config.start(); 447 this.configuration = config; 448 updateLoggers(); 449 if (prev != null) { 450 prev.removeListener(this); 451 prev.stop(); 452 } 453 454 firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config)); 455 456 try { 457 Server.reregisterMBeansAfterReconfigure(); 458 } catch (final Throwable t) { 459 // LOG4J2-716: Android has no java.lang.management 460 LOGGER.error("Could not reconfigure JMX", t); 461 } 462 Log4jLogEvent.setNanoClock(NanoClockFactory.createNanoClock()); 463 try { 464 AsyncLogger.setNanoClock(NanoClockFactory.createNanoClock()); 465 } catch (Throwable ignored) { 466 // LMAX Disruptor jar may not be in the classpath. Ignore this. 467 LOGGER.debug("Could not set AsyncLogger NanoClock. Ignoring: " + ignored.toString()); 468 } 469 return prev; 470 } finally { 471 configLock.unlock(); 472 } 473 } 474 475 private void firePropertyChangeEvent(final PropertyChangeEvent event) { 476 for (final PropertyChangeListener listener : propertyChangeListeners) { 477 listener.propertyChange(event); 478 } 479 } 480 481 public void addPropertyChangeListener(final PropertyChangeListener listener) { 482 propertyChangeListeners.add(Objects.requireNonNull(listener, "listener")); 483 } 484 485 public void removePropertyChangeListener(final PropertyChangeListener listener) { 486 propertyChangeListeners.remove(listener); 487 } 488 489 /** 490 * Returns the initial configuration location or {@code null}. The returned value may not be the location of the 491 * current configuration. Use {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() 492 * getConfigurationSource()}.{@link ConfigurationSource#getLocation() getLocation()} to get the actual source of the 493 * current configuration. 494 * 495 * @return the initial configuration location or {@code null} 496 */ 497 public URI getConfigLocation() { 498 return configLocation; 499 } 500 501 /** 502 * Sets the configLocation to the specified value and reconfigures this context. 503 * 504 * @param configLocation the location of the new configuration 505 */ 506 public void setConfigLocation(final URI configLocation) { 507 this.configLocation = configLocation; 508 509 reconfigure(configLocation); 510 } 511 512 /** 513 * Reconfigure the context. 514 */ 515 private void reconfigure(final URI configURI) { 516 final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null; 517 LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}", 518 contextName, configURI, this, cl); 519 final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(contextName, configURI, cl); 520 setConfiguration(instance); 521 /* 522 * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) { 523 * old.stop(); } 524 */ 525 526 LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}", 527 contextName, configURI, this, cl); 528 } 529 530 /** 531 * Reconfigure the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new 532 * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old 533 * LoggerConfig, along with old Appenders and Filters. 534 */ 535 public void reconfigure() { 536 reconfigure(configLocation); 537 } 538 539 /** 540 * Causes all Loggers to be updated against the current Configuration. 541 */ 542 public void updateLoggers() { 543 updateLoggers(this.configuration); 544 } 545 546 /** 547 * Causes all Logger to be updated against the specified Configuration. 548 * 549 * @param config The Configuration. 550 */ 551 public void updateLoggers(final Configuration config) { 552 for (final Logger logger : loggers.values()) { 553 logger.updateConfiguration(config); 554 } 555 } 556 557 /** 558 * Causes a reconfiguration to take place when the underlying configuration file changes. 559 * 560 * @param reconfigurable The Configuration that can be reconfigured. 561 */ 562 @Override 563 public synchronized void onChange(final Reconfigurable reconfigurable) { 564 LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this); 565 final Configuration newConfig = reconfigurable.reconfigure(); 566 if (newConfig != null) { 567 setConfiguration(newConfig); 568 LOGGER.debug("Reconfiguration completed for {} ({})", contextName, this); 569 } else { 570 LOGGER.debug("Reconfiguration failed for {} ({})", contextName, this); 571 } 572 } 573 574 // LOG4J2-151: changed visibility from private to protected 575 protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) { 576 return new Logger(ctx, name, messageFactory); 577 } 578 579}