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.concurrent.ConcurrentHashMap;
024import java.util.concurrent.ConcurrentMap;
025import java.util.concurrent.CopyOnWriteArrayList;
026import java.util.concurrent.locks.Lock;
027import java.util.concurrent.locks.ReentrantLock;
028
029import org.apache.logging.log4j.LogManager;
030import org.apache.logging.log4j.core.config.Configuration;
031import org.apache.logging.log4j.core.config.ConfigurationFactory;
032import org.apache.logging.log4j.core.config.ConfigurationListener;
033import org.apache.logging.log4j.core.config.DefaultConfiguration;
034import org.apache.logging.log4j.core.config.NullConfiguration;
035import org.apache.logging.log4j.core.config.Reconfigurable;
036import org.apache.logging.log4j.core.helpers.Assert;
037import org.apache.logging.log4j.core.helpers.NetUtils;
038import org.apache.logging.log4j.core.jmx.Server;
039import org.apache.logging.log4j.message.MessageFactory;
040import org.apache.logging.log4j.spi.AbstractLogger;
041import org.apache.logging.log4j.status.StatusLogger;
042
043/**
044 * The LoggerContext is the anchor for the logging system. It maintains a list
045 * of all the loggers requested by applications and a reference to the
046 * Configuration. The Configuration will contain the configured loggers,
047 * appenders, filters, etc and will be atomically updated whenever a reconfigure
048 * occurs.
049 */
050public class LoggerContext implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener, LifeCycle {
051
052    public static final String PROPERTY_CONFIG = "config";
053    private static final StatusLogger LOGGER = StatusLogger.getLogger();
054    private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
055
056    private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<String, Logger>();
057    private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>();
058
059    /**
060     * The Configuration is volatile to guarantee that initialization of the
061     * Configuration has completed before the reference is updated.
062     */
063    private volatile Configuration config = new DefaultConfiguration();
064    private Object externalContext;
065    private final String name;
066    private URI configLocation;
067
068    private ShutdownThread shutdownThread = null;
069
070    /**
071     * Status of the LoggerContext.
072     */
073    public enum Status {
074        /** Initialized but not yet started. */
075        INITIALIZED,
076        /** In the process of starting. */
077        STARTING,
078        /** Is active. */
079        STARTED,
080        /** Shutdown is in progress. */
081        STOPPING,
082        /** Has shutdown. */
083        STOPPED
084    }
085
086    private volatile Status status = Status.INITIALIZED;
087
088    private final Lock configLock = new ReentrantLock();
089
090    /**
091     * Constructor taking only a name.
092     * @param name The context name.
093     */
094    public LoggerContext(final String name) {
095        this(name, null, (URI) null);
096    }
097
098    /**
099     * Constructor taking a name and a reference to an external context.
100     * @param name The context name.
101     * @param externalContext The external context.
102     */
103    public LoggerContext(final String name, final Object externalContext) {
104        this(name, externalContext, (URI) null);
105    }
106
107    /**
108     * Constructor taking a name, external context and a configuration URI.
109     * @param name The context name.
110     * @param externalContext The external context.
111     * @param configLocn The location of the configuration as a URI.
112     */
113    public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
114        this.name = name;
115        this.externalContext = externalContext;
116        this.configLocation = configLocn;
117    }
118
119    /**
120     * Constructor taking a name external context and a configuration location
121     * String. The location must be resolvable to a File.
122     *
123     * @param name The configuration location.
124     * @param externalContext The external context.
125     * @param configLocn The configuration location.
126     */
127    public LoggerContext(final String name, final Object externalContext, final String configLocn) {
128        this.name = name;
129        this.externalContext = externalContext;
130        if (configLocn != null) {
131            URI uri;
132            try {
133                uri = new File(configLocn).toURI();
134            } catch (final Exception ex) {
135                uri = null;
136            }
137            configLocation = uri;
138        } else {
139            configLocation = null;
140        }
141    }
142
143    @Override
144    public void start() {
145        if (configLock.tryLock()) {
146            try {
147                if (status == Status.INITIALIZED || status == Status.STOPPED) {
148                    status = Status.STARTING;
149                    reconfigure();
150                    if (config.isShutdownHookEnabled()) {
151                        shutdownThread = new ShutdownThread(this);
152                        try {
153                            Runtime.getRuntime().addShutdownHook(shutdownThread);
154                        } catch (final IllegalStateException ise) {
155                            LOGGER.warn("Unable to register shutdown hook due to JVM state");
156                            shutdownThread = null;
157                        } catch (final SecurityException se) {
158                            LOGGER.warn("Unable to register shutdown hook due to security restrictions");
159                            shutdownThread = null;
160                        }
161                    }
162                    status = Status.STARTED;
163                }
164            } finally {
165                configLock.unlock();
166            }
167        }
168    }
169
170    /**
171     * Start with a specific configuration.
172     * @param config The new Configuration.
173     */
174    public void start(final Configuration config) {
175        if (configLock.tryLock()) {
176            try {
177                if ((status == Status.INITIALIZED || status == Status.STOPPED) && config.isShutdownHookEnabled() ) {
178                    shutdownThread = new ShutdownThread(this);
179                    try {
180                        Runtime.getRuntime().addShutdownHook(shutdownThread);
181                    } catch (final IllegalStateException ise) {
182                        LOGGER.warn("Unable to register shutdown hook due to JVM state");
183                        shutdownThread = null;
184                    } catch (final SecurityException se) {
185                        LOGGER.warn("Unable to register shutdown hook due to security restrictions");
186                        shutdownThread = null;
187                    }
188                    status = Status.STARTED;
189                }
190            } finally {
191                configLock.unlock();
192            }
193        }
194        setConfiguration(config);
195    }
196
197    @Override
198    public void stop() {
199        configLock.lock();
200        try {
201            if (status == Status.STOPPED) {
202                return;
203            }
204            status = Status.STOPPING;
205            if (shutdownThread != null) {
206                Runtime.getRuntime().removeShutdownHook(shutdownThread);
207                shutdownThread = null;
208            }
209            final Configuration prev = config;
210            config = NULL_CONFIGURATION;
211            updateLoggers();
212            prev.stop();
213            externalContext = null;
214            LogManager.getFactory().removeContext(this);
215            status = Status.STOPPED;
216        } finally {
217            configLock.unlock();
218            
219            // in finally: unregister MBeans even if an exception occurred while stopping 
220            Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500
221        }
222    }
223
224    /**
225     * Gets the name.
226     *
227     * @return the name.
228     */
229    public String getName() {
230        return name;
231    }
232
233    public Status getStatus() {
234        return status;
235    }
236
237    @Override
238    public boolean isStarted() {
239        return status == Status.STARTED;
240    }
241
242    /**
243     * Set the external context.
244     * @param context The external context.
245     */
246    public void setExternalContext(final Object context) {
247        this.externalContext = context;
248    }
249
250    /**
251     * Returns the external context.
252     * @return The external context.
253     */
254    @Override
255    public Object getExternalContext() {
256        return this.externalContext;
257    }
258
259    /**
260     * Obtain a Logger from the Context.
261     * @param name The name of the Logger to return.
262     * @return The Logger.
263     */
264    @Override
265    public Logger getLogger(final String name) {
266        return getLogger(name, null);
267    }
268
269    /**
270     * Obtain a Logger from the Context.
271     * @param name The name of the Logger to return.
272     * @param messageFactory The message factory is used only when creating a
273     *            logger, subsequent use does not change the logger but will log
274     *            a warning if mismatched.
275     * @return The Logger.
276     */
277    @Override
278    public Logger getLogger(final String name, final MessageFactory messageFactory) {
279        Logger logger = loggers.get(name);
280        if (logger != null) {
281            AbstractLogger.checkMessageFactory(logger, messageFactory);
282            return logger;
283        }
284
285        logger = newInstance(this, name, messageFactory);
286        final Logger prev = loggers.putIfAbsent(name, logger);
287        return prev == null ? logger : prev;
288    }
289
290    /**
291     * Determine if the specified Logger exists.
292     * @param name The Logger name to search for.
293     * @return True if the Logger exists, false otherwise.
294     */
295    @Override
296    public boolean hasLogger(final String name) {
297        return loggers.containsKey(name);
298    }
299
300    /**
301     * Returns the current Configuration. The Configuration will be replaced
302     * when a reconfigure occurs.
303     *
304     * @return The Configuration.
305     */
306    public Configuration getConfiguration() {
307        return config;
308    }
309
310    /**
311     * Add a Filter to the Configuration. Filters that are added through the API will be lost
312     * when a reconfigure occurs.
313     * @param filter The Filter to add.
314     */
315    public void addFilter(final Filter filter) {
316        config.addFilter(filter);
317    }
318
319    /**
320     * Removes a Filter from the current Configuration.
321     * @param filter The Filter to remove.
322     */
323    public void removeFilter(final Filter filter) {
324        config.removeFilter(filter);
325    }
326
327    /**
328     * Set the Configuration to be used.
329     * @param config The new Configuration.
330     * @return The previous Configuration.
331     */
332    private synchronized Configuration setConfiguration(final Configuration config) {
333        if (config == null) {
334            throw new NullPointerException("No Configuration was provided");
335        }
336        final Configuration prev = this.config;
337        config.addListener(this);
338        final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
339        map.putIfAbsent("hostName", NetUtils.getLocalHostname());
340        map.putIfAbsent("contextName", name);
341        config.start();
342        this.config = config;
343        updateLoggers();
344        if (prev != null) {
345            prev.removeListener(this);
346            prev.stop();
347        }
348
349        // notify listeners
350        final PropertyChangeEvent evt = new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config);
351        for (final PropertyChangeListener listener : propertyChangeListeners) {
352            listener.propertyChange(evt);
353        }
354
355        try {
356            Server.reregisterMBeansAfterReconfigure();
357        } catch (final Exception ex) {
358            LOGGER.error("Could not reconfigure JMX", ex);
359        }
360        return prev;
361    }
362
363    public void addPropertyChangeListener(final PropertyChangeListener listener) {
364        propertyChangeListeners.add(Assert.isNotNull(listener, "listener"));
365    }
366
367    public void removePropertyChangeListener(final PropertyChangeListener listener) {
368        propertyChangeListeners.remove(listener);
369    }
370
371    public synchronized URI getConfigLocation() {
372        return configLocation;
373    }
374
375    public synchronized void setConfigLocation(final URI configLocation) {
376        this.configLocation = configLocation;
377        reconfigure();
378    }
379
380    /**
381     * Reconfigure the context.
382     */
383    public synchronized void reconfigure() {
384        LOGGER.debug("Reconfiguration started for context " + name);
385        final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
386        setConfiguration(instance);
387        /*
388         * instance.start(); Configuration old = setConfiguration(instance);
389         * updateLoggers(); if (old != null) { old.stop(); }
390         */
391
392        LOGGER.debug("Reconfiguration completed");
393    }
394
395    /**
396     * Cause all Loggers to be updated against the current Configuration.
397     */
398    public void updateLoggers() {
399        updateLoggers(this.config);
400    }
401
402    /**
403     * Cause all Logger to be updated against the specified Configuration.
404     * @param config The Configuration.
405     */
406    public void updateLoggers(final Configuration config) {
407        for (final Logger logger : loggers.values()) {
408            logger.updateConfiguration(config);
409        }
410    }
411
412    /**
413     * Cause a reconfiguration to take place when the underlying configuration
414     * file changes.
415     *
416     * @param reconfigurable The Configuration that can be reconfigured.
417     */
418    @Override
419    public synchronized void onChange(final Reconfigurable reconfigurable) {
420        LOGGER.debug("Reconfiguration started for context " + name);
421        final Configuration config = reconfigurable.reconfigure();
422        if (config != null) {
423            setConfiguration(config);
424            LOGGER.debug("Reconfiguration completed");
425        } else {
426            LOGGER.debug("Reconfiguration failed");
427        }
428    }
429
430    // LOG4J2-151: changed visibility from private to protected
431    protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
432        return new Logger(ctx, name, messageFactory);
433    }
434
435    private class ShutdownThread extends Thread {
436
437        private final LoggerContext context;
438
439        public ShutdownThread(final LoggerContext context) {
440            this.context = context;
441        }
442
443        @Override
444        public void run() {
445            context.shutdownThread = null;
446            context.stop();
447        }
448    }
449
450}