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.HashMap;
024    import java.util.Map;
025    import java.util.concurrent.ConcurrentHashMap;
026    import java.util.concurrent.ConcurrentMap;
027    import java.util.concurrent.CopyOnWriteArrayList;
028    import java.util.concurrent.locks.Lock;
029    import java.util.concurrent.locks.ReentrantLock;
030    
031    import org.apache.logging.log4j.LogManager;
032    import org.apache.logging.log4j.core.config.Configuration;
033    import org.apache.logging.log4j.core.config.ConfigurationFactory;
034    import org.apache.logging.log4j.core.config.ConfigurationListener;
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.helpers.Assert;
039    import org.apache.logging.log4j.core.helpers.NetUtils;
040    import org.apache.logging.log4j.message.MessageFactory;
041    import org.apache.logging.log4j.spi.AbstractLogger;
042    import org.apache.logging.log4j.status.StatusLogger;
043    
044    /**
045     * The LoggerContext is the anchor for the logging system. It maintains a list
046     * of all the loggers requested by applications and a reference to the
047     * Configuration. The Configuration will contain the configured loggers,
048     * appenders, filters, etc and will be atomically updated whenever a reconfigure
049     * occurs.
050     */
051    public class LoggerContext implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener, LifeCycle {
052    
053        public static final String PROPERTY_CONFIG = "config";
054        private static final StatusLogger LOGGER = StatusLogger.getLogger();
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                        shutdownThread = new ShutdownThread(this);
151                        try {
152                            Runtime.getRuntime().addShutdownHook(shutdownThread);
153                        } catch (SecurityException se) {
154                            LOGGER.warn("Unable to register shutdown hook due to security restrictions");
155                            shutdownThread = null;
156                        }
157                        status = Status.STARTED;
158                    }
159                } finally {
160                    configLock.unlock();
161                }
162            }
163        }
164    
165        /**
166         * Start with a specific configuration.
167         * @param config The new Configuration.
168         */
169        public void start(final Configuration config) {
170            if (configLock.tryLock()) {
171                try {
172                    if (status == Status.INITIALIZED || status == Status.STOPPED) {
173                        shutdownThread = new ShutdownThread(this);
174                        try {
175                            Runtime.getRuntime().addShutdownHook(shutdownThread);
176                        } catch (SecurityException se) {
177                            LOGGER.warn("Unable to register shutdown hook due to security restrictions");
178                            shutdownThread = null;
179                        }
180                        status = Status.STARTED;
181                    }
182                } finally {
183                    configLock.unlock();
184                }
185            }
186            setConfiguration(config);
187        }
188    
189        @Override
190        public void stop() {
191            configLock.lock();
192            try {
193                if (status == Status.STOPPED) {
194                    return;
195                }
196                status = Status.STOPPING;
197                if (shutdownThread != null) {
198                    Runtime.getRuntime().removeShutdownHook(shutdownThread);
199                    shutdownThread = null;
200                }
201                Configuration prev = config;
202                config = new NullConfiguration();
203                updateLoggers();
204                prev.stop();
205                externalContext = null;
206                LogManager.getFactory().removeContext(this);
207                status = Status.STOPPED;
208            } finally {
209                configLock.unlock();
210            }
211        }
212    
213        /**
214         * Gets the name.
215         *
216         * @return the name.
217         */
218        public String getName() {
219            return name;
220        }
221    
222        public Status getStatus() {
223            return status;
224        }
225    
226        @Override
227        public boolean isStarted() {
228            return status == Status.STARTED;
229        }
230    
231        /**
232         * Set the external context.
233         * @param context The external context.
234         */
235        public void setExternalContext(final Object context) {
236            this.externalContext = context;
237        }
238    
239        /**
240         * Returns the external context.
241         * @return The external context.
242         */
243        @Override
244        public Object getExternalContext() {
245            return this.externalContext;
246        }
247    
248        /**
249         * Obtain a Logger from the Context.
250         * @param name The name of the Logger to return.
251         * @return The Logger.
252         */
253        @Override
254        public Logger getLogger(final String name) {
255            return getLogger(name, null);
256        }
257    
258        /**
259         * Obtain a Logger from the Context.
260         * @param name The name of the Logger to return.
261         * @param messageFactory The message factory is used only when creating a
262         *            logger, subsequent use does not change the logger but will log
263         *            a warning if mismatched.
264         * @return The Logger.
265         */
266        @Override
267        public Logger getLogger(final String name, final MessageFactory messageFactory) {
268            Logger logger = loggers.get(name);
269            if (logger != null) {
270                AbstractLogger.checkMessageFactory(logger, messageFactory);
271                return logger;
272            }
273    
274            logger = newInstance(this, name, messageFactory);
275            final Logger prev = loggers.putIfAbsent(name, logger);
276            return prev == null ? logger : prev;
277        }
278    
279        /**
280         * Determine if the specified Logger exists.
281         * @param name The Logger name to search for.
282         * @return True if the Logger exists, false otherwise.
283         */
284        @Override
285        public boolean hasLogger(final String name) {
286            return loggers.containsKey(name);
287        }
288    
289        /**
290         * Returns the current Configuration. The Configuration will be replaced
291         * when a reconfigure occurs.
292         *
293         * @return The Configuration.
294         */
295        public Configuration getConfiguration() {
296            return config;
297        }
298    
299        /**
300         * Add a Filter to the Configuration. Filters that are added through the API will be lost
301         * when a reconfigure occurs.
302         * @param filter The Filter to add.
303         */
304        public void addFilter(final Filter filter) {
305            config.addFilter(filter);
306        }
307    
308        /**
309         * Removes a Filter from the current Configuration.
310         * @param filter The Filter to remove.
311         */
312        public void removeFilter(final Filter filter) {
313            config.removeFilter(filter);
314        }
315    
316        /**
317         * Set the Configuration to be used.
318         * @param config The new Configuration.
319         * @return The previous Configuration.
320         */
321        private synchronized Configuration setConfiguration(final Configuration config) {
322            if (config == null) {
323                throw new NullPointerException("No Configuration was provided");
324            }
325            final Configuration prev = this.config;
326            config.addListener(this);
327            final Map<String, String> map = new HashMap<String, String>();
328            map.put("hostName", NetUtils.getLocalHostname());
329            map.put("contextName", name);
330            config.addComponent(Configuration.CONTEXT_PROPERTIES, map);
331            config.start();
332            this.config = config;
333            updateLoggers();
334            if (prev != null) {
335                prev.removeListener(this);
336                prev.stop();
337            }
338    
339            // notify listeners
340            PropertyChangeEvent evt = new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config);
341            for (PropertyChangeListener listener : propertyChangeListeners) {
342                listener.propertyChange(evt);
343            }
344            return prev;
345        }
346    
347        public void addPropertyChangeListener(PropertyChangeListener listener) {
348            propertyChangeListeners.add(Assert.isNotNull(listener, "listener"));
349        }
350    
351        public void removePropertyChangeListener(PropertyChangeListener listener) {
352            propertyChangeListeners.remove(listener);
353        }
354    
355        public synchronized URI getConfigLocation() {
356            return configLocation;
357        }
358    
359        public synchronized void setConfigLocation(URI configLocation) {
360            this.configLocation = configLocation;
361            reconfigure();
362        }
363    
364        /**
365         * Reconfigure the context.
366         */
367        public synchronized void reconfigure() {
368            LOGGER.debug("Reconfiguration started for context " + name);
369            final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
370            setConfiguration(instance);
371            /*
372             * instance.start(); Configuration old = setConfiguration(instance);
373             * updateLoggers(); if (old != null) { old.stop(); }
374             */
375            LOGGER.debug("Reconfiguration completed");
376        }
377    
378        /**
379         * Cause all Loggers to be updated against the current Configuration.
380         */
381        public void updateLoggers() {
382            updateLoggers(this.config);
383        }
384    
385        /**
386         * Cause all Logger to be updated against the specified Configuration.
387         * @param config The Configuration.
388         */
389        public void updateLoggers(final Configuration config) {
390            for (final Logger logger : loggers.values()) {
391                logger.updateConfiguration(config);
392            }
393        }
394    
395        /**
396         * Cause a reconfiguration to take place when the underlying configuration
397         * file changes.
398         *
399         * @param reconfigurable The Configuration that can be reconfigured.
400         */
401        @Override
402        public synchronized void onChange(final Reconfigurable reconfigurable) {
403            LOGGER.debug("Reconfiguration started for context " + name);
404            final Configuration config = reconfigurable.reconfigure();
405            if (config != null) {
406                setConfiguration(config);
407                LOGGER.debug("Reconfiguration completed");
408            } else {
409                LOGGER.debug("Reconfiguration failed");
410            }
411        }
412    
413        // LOG4J2-151: changed visibility from private to protected
414        protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
415            return new Logger(ctx, name, messageFactory);
416        }
417    
418        private class ShutdownThread extends Thread {
419    
420            private final LoggerContext context;
421    
422            public ShutdownThread(LoggerContext context) {
423                this.context = context;
424            }
425    
426            @Override
427            public void run() {
428                context.shutdownThread = null;
429                context.stop();
430            }
431        }
432    
433    }