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.lang.ref.Reference;
023import java.lang.ref.SoftReference;
024import java.net.URI;
025import java.util.Collection;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.concurrent.ConcurrentMap;
028import java.util.concurrent.CopyOnWriteArrayList;
029import java.util.concurrent.locks.Lock;
030import java.util.concurrent.locks.ReentrantLock;
031
032import org.apache.logging.log4j.LogManager;
033import org.apache.logging.log4j.Marker;
034import org.apache.logging.log4j.MarkerManager;
035import org.apache.logging.log4j.core.config.Configuration;
036import org.apache.logging.log4j.core.config.ConfigurationFactory;
037import org.apache.logging.log4j.core.config.ConfigurationListener;
038import org.apache.logging.log4j.core.config.ConfigurationSource;
039import org.apache.logging.log4j.core.config.DefaultConfiguration;
040import org.apache.logging.log4j.core.config.NullConfiguration;
041import org.apache.logging.log4j.core.config.Reconfigurable;
042import org.apache.logging.log4j.core.jmx.Server;
043import org.apache.logging.log4j.core.util.Assert;
044import org.apache.logging.log4j.core.util.NetUtils;
045import org.apache.logging.log4j.message.MessageFactory;
046import org.apache.logging.log4j.spi.AbstractLogger;
047import org.apache.logging.log4j.util.PropertiesUtil;
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 boolean SHUTDOWN_HOOK_ENABLED =
059        PropertiesUtil.getProperties().getBooleanProperty("log4j.shutdownHookEnabled", true);
060
061    public static final String PROPERTY_CONFIG = "config";
062    private static final Marker SHUTDOWN_HOOK = MarkerManager.getMarker("SHUTDOWN HOOK");
063    private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
064
065    private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<String, Logger>();
066    private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>();
067
068    /**
069     * The Configuration is volatile to guarantee that initialization of the
070     * Configuration has completed before the reference is updated.
071     */
072    private volatile Configuration config = new DefaultConfiguration();
073    private Object externalContext;
074    private final String name;
075    private URI configLocation;
076
077    /**
078     * Once a shutdown hook thread executes, we can't remove it from the runtime (throws an exception). Therefore,
079     * it's really pointless to keep a strongly accessible reference to said thread. Thus, please use a
080     * SoftReference here to prevent possible cyclic memory references.
081     */
082    private Reference<Thread> shutdownThread;
083
084    private final Lock configLock = new ReentrantLock();
085
086    /**
087     * Constructor taking only a name.
088     * @param name The context name.
089     */
090    public LoggerContext(final String name) {
091        this(name, null, (URI) null);
092    }
093
094    /**
095     * Constructor taking a name and a reference to an external context.
096     * @param name The context name.
097     * @param externalContext The external context.
098     */
099    public LoggerContext(final String name, final Object externalContext) {
100        this(name, externalContext, (URI) null);
101    }
102
103    /**
104     * Constructor taking a name, external context and a configuration URI.
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.name = name;
111        this.externalContext = externalContext;
112        this.configLocation = configLocn;
113    }
114
115    /**
116     * Constructor taking a name external context and a configuration location
117     * String. The location must be resolvable 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.name = 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    @Override
140    public void start() {
141        LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
142        if (configLock.tryLock()) {
143            try {
144                if (this.isInitialized() || this.isStopped()) {
145                    this.setStarting();
146                    reconfigure();
147                    setUpShutdownHook();
148                    this.setStarted();
149                }
150            } finally {
151                configLock.unlock();
152            }
153        }
154        LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
155    }
156
157    /**
158     * Start with a specific configuration.
159     * @param config The new Configuration.
160     */
161    public void start(final Configuration config) {
162        LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config);
163        if (configLock.tryLock()) {
164            try {
165                if (this.isInitialized() || this.isStopped()) {
166                    setUpShutdownHook();
167                    this.setStarted();
168                }
169            } finally {
170                configLock.unlock();
171            }
172        }
173        setConfiguration(config);
174        LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config);
175    }
176
177    private void setUpShutdownHook() {
178        if (config.isShutdownHookEnabled() && SHUTDOWN_HOOK_ENABLED) {
179            LOGGER.debug(SHUTDOWN_HOOK, "Shutdown hook enabled. Registering a new one.");
180            shutdownThread = new SoftReference<Thread>(
181                    new Thread(new ShutdownThread(this), "log4j-shutdown")
182            );
183            addShutdownHook();
184        }
185    }
186
187    private void addShutdownHook() {
188        final Thread hook = getShutdownThread();
189        if (hook != null) {
190            try {
191                Runtime.getRuntime().addShutdownHook(hook);
192            } catch (final IllegalStateException ise) {
193                LOGGER.warn(SHUTDOWN_HOOK, "Unable to register shutdown hook due to JVM state");
194            } catch (final SecurityException se) {
195                LOGGER.warn(SHUTDOWN_HOOK, "Unable to register shutdown hook due to security restrictions");
196            }
197        }
198    }
199
200    private Thread getShutdownThread() {
201        return shutdownThread == null ? null : shutdownThread.get();
202    }
203
204    @Override
205    public void stop() {
206        LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this);
207        configLock.lock();
208        try {
209            if (this.isStopped()) {
210                return;
211            }
212            this.setStopping();
213            tearDownShutdownHook();
214            final Configuration prev = config;
215            config = NULL_CONFIGURATION;
216            updateLoggers();
217            prev.stop();
218            externalContext = null;
219            LogManager.getFactory().removeContext(this);
220            this.setStopped();
221        } finally {
222            configLock.unlock();
223
224            // in finally: unregister MBeans even if an exception occurred while stopping
225            Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500
226        }
227        LOGGER.debug("Stopped LoggerContext[name={}, {}]...", getName(), this);
228    }
229
230    private void tearDownShutdownHook() {
231        if (shutdownThread != null) {
232            LOGGER.debug(SHUTDOWN_HOOK, "Enqueue shutdown hook for garbage collection.");
233            shutdownThread.enqueue();
234        }
235    }
236
237    /**
238     * Gets the name.
239     *
240     * @return the name.
241     */
242    public String getName() {
243        return name;
244    }
245
246    /**
247     * Set the external context.
248     * @param context The external context.
249     */
250    public void setExternalContext(final Object context) {
251        this.externalContext = context;
252    }
253
254    /**
255     * Returns the external context.
256     * @return The external context.
257     */
258    @Override
259    public Object getExternalContext() {
260        return this.externalContext;
261    }
262
263    /**
264     * Obtain a Logger from the Context.
265     * @param name The name of the Logger to return.
266     * @return The Logger.
267     */
268    @Override
269    public Logger getLogger(final String name) {
270        return getLogger(name, null);
271    }
272
273    /**
274     * Gets a collection of the current loggers.
275     * <p>
276     * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this collection at your own
277     * risk.
278     * </p>
279     *
280     * @return a collection of the current loggers.
281     */
282    public Collection<Logger> getLoggers() {
283        return loggers.values();
284    }
285
286    /**
287     * Obtain a Logger from the Context.
288     * @param name The name of the Logger to return.
289     * @param messageFactory The message factory is used only when creating a
290     *            logger, subsequent use does not change the logger but will log
291     *            a warning if mismatched.
292     * @return The Logger.
293     */
294    @Override
295    public Logger getLogger(final String name, final MessageFactory messageFactory) {
296        Logger logger = loggers.get(name);
297        if (logger != null) {
298            AbstractLogger.checkMessageFactory(logger, messageFactory);
299            return logger;
300        }
301
302        logger = newInstance(this, name, messageFactory);
303        final Logger prev = loggers.putIfAbsent(name, logger);
304        return prev == null ? logger : prev;
305    }
306
307    /**
308     * Determine if the specified Logger exists.
309     * @param name The Logger name to search for.
310     * @return True if the Logger exists, false otherwise.
311     */
312    @Override
313    public boolean hasLogger(final String name) {
314        return loggers.containsKey(name);
315    }
316
317    /**
318     * Returns the current Configuration. The Configuration will be replaced
319     * when a reconfigure occurs.
320     *
321     * @return The Configuration.
322     */
323    public Configuration getConfiguration() {
324        return config;
325    }
326
327    /**
328     * Add a Filter to the Configuration. Filters that are added through the API will be lost
329     * when a reconfigure occurs.
330     * @param filter The Filter to add.
331     */
332    public void addFilter(final Filter filter) {
333        config.addFilter(filter);
334    }
335
336    /**
337     * Removes a Filter from the current Configuration.
338     * @param filter The Filter to remove.
339     */
340    public void removeFilter(final Filter filter) {
341        config.removeFilter(filter);
342    }
343
344    /**
345     * Set the Configuration to be used.
346     * @param config The new Configuration.
347     * @return The previous Configuration.
348     */
349    private synchronized Configuration setConfiguration(final Configuration config) {
350        if (config == null) {
351            throw new NullPointerException("No Configuration was provided");
352        }
353        final Configuration prev = this.config;
354        config.addListener(this);
355        final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
356        
357        try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
358            map.putIfAbsent("hostName", NetUtils.getLocalHostname());
359        } catch (final Exception ex) {
360            LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
361            map.putIfAbsent("hostName", "unknown");
362        }
363        map.putIfAbsent("contextName", name);
364        config.start();
365        this.config = config;
366        updateLoggers();
367        if (prev != null) {
368            prev.removeListener(this);
369            prev.stop();
370        }
371
372        firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
373
374        try {
375            Server.reregisterMBeansAfterReconfigure();
376        } catch (final Throwable t) { // LOG4J2-716: Android has no java.lang.management
377            LOGGER.error("Could not reconfigure JMX", t);
378        }
379        return prev;
380    }
381
382    private void firePropertyChangeEvent(final PropertyChangeEvent event) {
383        for (final PropertyChangeListener listener : propertyChangeListeners) {
384            listener.propertyChange(event);
385        }
386    }
387
388    public void addPropertyChangeListener(final PropertyChangeListener listener) {
389        propertyChangeListeners.add(Assert.requireNonNull(listener, "listener"));
390    }
391
392    public void removePropertyChangeListener(final PropertyChangeListener listener) {
393        propertyChangeListeners.remove(listener);
394    }
395
396    /**
397     * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
398     * current configuration. Use 
399     * {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() getConfigurationSource()}.{@link 
400     * ConfigurationSource#getLocation() getLocation()} to get the actual source of the current configuration.
401     * @return the initial configuration location or {@code null}
402     */
403    public synchronized URI getConfigLocation() {
404        return configLocation;
405    }
406
407    /**
408     * Sets the configLocation to the specified value and reconfigures this context.
409     * @param configLocation the location of the new configuration
410     */
411    public synchronized void setConfigLocation(final URI configLocation) {
412        this.configLocation = configLocation;
413        reconfigure();
414    }
415
416    /**
417     * Reconfigure the context.
418     */
419    public synchronized void reconfigure() {
420        LOGGER.debug("Reconfiguration started for context[name={}] at {} ({})", name, configLocation, this);
421        final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
422        setConfiguration(instance);
423        /*
424         * instance.start(); Configuration old = setConfiguration(instance);
425         * updateLoggers(); if (old != null) { old.stop(); }
426         */
427
428        LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({})", name, configLocation, this);
429    }
430
431    /**
432     * Cause all Loggers to be updated against the current Configuration.
433     */
434    public void updateLoggers() {
435        updateLoggers(this.config);
436    }
437
438    /**
439     * Cause all Logger to be updated against the specified Configuration.
440     * @param config The Configuration.
441     */
442    public void updateLoggers(final Configuration config) {
443        for (final Logger logger : loggers.values()) {
444            logger.updateConfiguration(config);
445        }
446    }
447
448    /**
449     * Cause a reconfiguration to take place when the underlying configuration
450     * file changes.
451     *
452     * @param reconfigurable The Configuration that can be reconfigured.
453     */
454    @Override
455    public synchronized void onChange(final Reconfigurable reconfigurable) {
456        LOGGER.debug("Reconfiguration started for context {} ({})", name, this);
457        final Configuration config = reconfigurable.reconfigure();
458        if (config != null) {
459            setConfiguration(config);
460            LOGGER.debug("Reconfiguration completed for {} ({})", name, this);
461        } else {
462            LOGGER.debug("Reconfiguration failed for {} ({})", name, this);
463        }
464    }
465
466    // LOG4J2-151: changed visibility from private to protected
467    protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
468        return new Logger(ctx, name, messageFactory);
469    }
470
471    private static class ShutdownThread implements Runnable {
472
473        private final LoggerContext context;
474
475        public ShutdownThread(final LoggerContext context) {
476            this.context = context;
477        }
478
479        @Override
480        public void run() {
481            LOGGER.debug("ShutdownThread stopping LoggerContext[name={}, {}]...", context.getName(), context);
482            context.stop();
483            LOGGER.debug("ShutdownThread stopped LoggerContext[name={}, {}].", context.getName(), context);
484        }
485    }
486
487}