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}