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.lang.ref.Reference;
023    import java.lang.ref.SoftReference;
024    import java.net.URI;
025    import java.util.Collection;
026    import java.util.concurrent.ConcurrentHashMap;
027    import java.util.concurrent.ConcurrentMap;
028    import java.util.concurrent.CopyOnWriteArrayList;
029    import java.util.concurrent.locks.Lock;
030    import java.util.concurrent.locks.ReentrantLock;
031    
032    import org.apache.logging.log4j.LogManager;
033    import org.apache.logging.log4j.Marker;
034    import org.apache.logging.log4j.MarkerManager;
035    import org.apache.logging.log4j.core.config.Configuration;
036    import org.apache.logging.log4j.core.config.ConfigurationFactory;
037    import org.apache.logging.log4j.core.config.ConfigurationListener;
038    import org.apache.logging.log4j.core.config.ConfigurationSource;
039    import org.apache.logging.log4j.core.config.DefaultConfiguration;
040    import org.apache.logging.log4j.core.config.NullConfiguration;
041    import org.apache.logging.log4j.core.config.Reconfigurable;
042    import org.apache.logging.log4j.core.jmx.Server;
043    import org.apache.logging.log4j.core.util.Assert;
044    import org.apache.logging.log4j.core.util.NetUtils;
045    import org.apache.logging.log4j.message.MessageFactory;
046    import org.apache.logging.log4j.spi.AbstractLogger;
047    import 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     */
056    public 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            map.putIfAbsent("hostName", NetUtils.getLocalHostname());
357            map.putIfAbsent("contextName", name);
358            config.start();
359            this.config = config;
360            updateLoggers();
361            if (prev != null) {
362                prev.removeListener(this);
363                prev.stop();
364            }
365    
366            firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
367    
368            try {
369                Server.reregisterMBeansAfterReconfigure();
370            } catch (final Exception ex) {
371                LOGGER.error("Could not reconfigure JMX", ex);
372            }
373            return prev;
374        }
375    
376        private void firePropertyChangeEvent(final PropertyChangeEvent event) {
377            for (final PropertyChangeListener listener : propertyChangeListeners) {
378                listener.propertyChange(event);
379            }
380        }
381    
382        public void addPropertyChangeListener(final PropertyChangeListener listener) {
383            propertyChangeListeners.add(Assert.requireNonNull(listener, "listener"));
384        }
385    
386        public void removePropertyChangeListener(final PropertyChangeListener listener) {
387            propertyChangeListeners.remove(listener);
388        }
389    
390        /**
391         * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
392         * current configuration. Use 
393         * {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() getConfigurationSource()}.{@link 
394         * ConfigurationSource#getLocation() getLocation()} to get the actual source of the current configuration.
395         * @return the initial configuration location or {@code null}
396         */
397        public synchronized URI getConfigLocation() {
398            return configLocation;
399        }
400    
401        /**
402         * Sets the configLocation to the specified value and reconfigures this context.
403         * @param configLocation the location of the new configuration
404         */
405        public synchronized void setConfigLocation(final URI configLocation) {
406            this.configLocation = configLocation;
407            reconfigure();
408        }
409    
410        /**
411         * Reconfigure the context.
412         */
413        public synchronized void reconfigure() {
414            LOGGER.debug("Reconfiguration started for context[name={}] at {} ({})", name, configLocation, this);
415            final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
416            setConfiguration(instance);
417            /*
418             * instance.start(); Configuration old = setConfiguration(instance);
419             * updateLoggers(); if (old != null) { old.stop(); }
420             */
421    
422            LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({})", name, configLocation, this);
423        }
424    
425        /**
426         * Cause all Loggers to be updated against the current Configuration.
427         */
428        public void updateLoggers() {
429            updateLoggers(this.config);
430        }
431    
432        /**
433         * Cause all Logger to be updated against the specified Configuration.
434         * @param config The Configuration.
435         */
436        public void updateLoggers(final Configuration config) {
437            for (final Logger logger : loggers.values()) {
438                logger.updateConfiguration(config);
439            }
440        }
441    
442        /**
443         * Cause a reconfiguration to take place when the underlying configuration
444         * file changes.
445         *
446         * @param reconfigurable The Configuration that can be reconfigured.
447         */
448        @Override
449        public synchronized void onChange(final Reconfigurable reconfigurable) {
450            LOGGER.debug("Reconfiguration started for context {} ({})", name, this);
451            final Configuration config = reconfigurable.reconfigure();
452            if (config != null) {
453                setConfiguration(config);
454                LOGGER.debug("Reconfiguration completed for {} ({})", name, this);
455            } else {
456                LOGGER.debug("Reconfiguration failed for {} ({})", name, this);
457            }
458        }
459    
460        // LOG4J2-151: changed visibility from private to protected
461        protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
462            return new Logger(ctx, name, messageFactory);
463        }
464    
465        private static class ShutdownThread implements Runnable {
466    
467            private final LoggerContext context;
468    
469            public ShutdownThread(final LoggerContext context) {
470                this.context = context;
471            }
472    
473            @Override
474            public void run() {
475                LOGGER.debug("ShutdownThread stopping LoggerContext[name={}, {}]...", context.getName(), context);
476                context.stop();
477                LOGGER.debug("ShutdownThread stopped LoggerContext[name={}, {}].", context.getName(), context);
478            }
479        }
480    
481    }