View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.core;
    18
    19  import java.beans.PropertyChangeEvent;
    20  import java.beans.PropertyChangeListener;
    21  import java.io.File;
    22  import java.net.URI;
    23  import java.util.Collection;
    24  import java.util.concurrent.ConcurrentHashMap;
    25  import java.util.concurrent.ConcurrentMap;
    26  import java.util.concurrent.CopyOnWriteArrayList;
    27  import java.util.concurrent.locks.Lock;
    28  import java.util.concurrent.locks.ReentrantLock;
    29
    30  import org.apache.logging.log4j.LogManager;
    31  import org.apache.logging.log4j.core.config.Configuration;
    32  import org.apache.logging.log4j.core.config.ConfigurationFactory;
    33  import org.apache.logging.log4j.core.config.ConfigurationListener;
    34  import org.apache.logging.log4j.core.config.ConfigurationSource;
    35  import org.apache.logging.log4j.core.config.DefaultConfiguration;
    36  import org.apache.logging.log4j.core.config.NullConfiguration;
    37  import org.apache.logging.log4j.core.config.Reconfigurable;
    38  import org.apache.logging.log4j.core.jmx.Server;
    39  import org.apache.logging.log4j.core.util.Assert;
    40  import org.apache.logging.log4j.core.util.Cancellable;
    41  import org.apache.logging.log4j.core.util.NetUtils;
    42  import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
    43  import org.apache.logging.log4j.message.MessageFactory;
    44  import org.apache.logging.log4j.spi.AbstractLogger;
    45  import org.apache.logging.log4j.spi.LoggerContextFactory;
    46
    47  import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER;
    48
    49  /**
    50   * The LoggerContext is the anchor for the logging system. It maintains a list
    51   * of all the loggers requested by applications and a reference to the
    52   * Configuration. The Configuration will contain the configured loggers,
    53   * appenders, filters, etc and will be atomically updated whenever a reconfigure
    54   * occurs.
    55   */
    56  public class LoggerContext extends AbstractLifeCycle implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener {
    57
    58      private static final long serialVersionUID = 1L;
    59
    60      public static final String PROPERTY_CONFIG = "config";
    61      private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
    62
    63      private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<String, Logger>();
    64      private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>();
    65
    66      /**
    67       * The Configuration is volatile to guarantee that initialization of the
    68       * Configuration has completed before the reference is updated.
    69       */
    70      private volatile Configuration config = new DefaultConfiguration();
    71      private Object externalContext;
    72      private final String name;
    73      private volatile URI configLocation;
    74      private Cancellable shutdownCallback;
    75
    76      private final Lock configLock = new ReentrantLock();
    77
    78      /**
    79       * Constructor taking only a name.
    80       * @param name The context name.
    81       */
    82      public LoggerContext(final String name) {
    83          this(name, null, (URI) null);
    84      }
    85
    86      /**
    87       * Constructor taking a name and a reference to an external context.
    88       * @param name The context name.
    89       * @param externalContext The external context.
    90       */
    91      public LoggerContext(final String name, final Object externalContext) {
    92          this(name, externalContext, (URI) null);
    93      }
    94
    95      /**
    96       * Constructor taking a name, external context and a configuration URI.
    97       * @param name The context name.
    98       * @param externalContext The external context.
    99       * @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 }