    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 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    }