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.Collection;
024    import java.util.concurrent.ConcurrentHashMap;
025    import java.util.concurrent.ConcurrentMap;
026    import java.util.concurrent.CopyOnWriteArrayList;
027    import java.util.concurrent.locks.Lock;
028    import java.util.concurrent.locks.ReentrantLock;
029    
030    import org.apache.logging.log4j.LogManager;
031    import org.apache.logging.log4j.core.config.Configuration;
032    import org.apache.logging.log4j.core.config.ConfigurationFactory;
033    import org.apache.logging.log4j.core.config.ConfigurationListener;
034    import org.apache.logging.log4j.core.config.ConfigurationSource;
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.jmx.Server;
039    import org.apache.logging.log4j.core.util.Assert;
040    import org.apache.logging.log4j.core.util.Cancellable;
041    import org.apache.logging.log4j.core.util.NetUtils;
042    import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
043    import org.apache.logging.log4j.message.MessageFactory;
044    import org.apache.logging.log4j.spi.AbstractLogger;
045    import org.apache.logging.log4j.spi.LoggerContextFactory;
046    
047    import 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     */
056    public 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 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         * Start 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 (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         * Set 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         * Obtain 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         * Obtain 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         * Determine 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         * Add 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         * Set the Configuration to be used.
343         * @param config The new Configuration.
344         * @return The previous Configuration.
345         */
346        private synchronized Configuration setConfiguration(final Configuration config) {
347            Assert.requireNonNull(config, "No Configuration was provided");
348            final Configuration prev = this.config;
349            config.addListener(this);
350            final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
351    
352            try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
353                map.putIfAbsent("hostName", NetUtils.getLocalHostname());
354            } catch (final Exception ex) {
355                LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
356                map.putIfAbsent("hostName", "unknown");
357            }
358            map.putIfAbsent("contextName", name);
359            config.start();
360            this.config = config;
361            updateLoggers();
362            if (prev != null) {
363                prev.removeListener(this);
364                prev.stop();
365            }
366    
367            firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
368    
369            try {
370                Server.reregisterMBeansAfterReconfigure();
371            } catch (final Throwable t) {
372                // LOG4J2-716: Android has no java.lang.management
373                LOGGER.error("Could not reconfigure JMX", t);
374            }
375            return prev;
376        }
377    
378        private void firePropertyChangeEvent(final PropertyChangeEvent event) {
379            for (final PropertyChangeListener listener : propertyChangeListeners) {
380                listener.propertyChange(event);
381            }
382        }
383    
384        public void addPropertyChangeListener(final PropertyChangeListener listener) {
385            propertyChangeListeners.add(Assert.requireNonNull(listener, "listener"));
386        }
387    
388        public void removePropertyChangeListener(final PropertyChangeListener listener) {
389            propertyChangeListeners.remove(listener);
390        }
391    
392        /**
393         * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
394         * current configuration. Use
395         * {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() getConfigurationSource()}.{@link
396         * ConfigurationSource#getLocation() getLocation()} to get the actual source of the current configuration.
397         * @return the initial configuration location or {@code null}
398         */
399        public synchronized URI getConfigLocation() {
400            return configLocation;
401        }
402    
403        /**
404         * Sets the configLocation to the specified value and reconfigures this context.
405         * @param configLocation the location of the new configuration
406         */
407        public synchronized void setConfigLocation(final URI configLocation) {
408            this.configLocation = configLocation;
409            reconfigure();
410        }
411    
412        /**
413         * Reconfigure the context.
414         */
415        public synchronized void reconfigure() {
416            final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
417            LOGGER.debug("Reconfiguration started for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
418                configLocation, this, cl);
419            final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation, cl);
420            setConfiguration(instance);
421            /*
422             * instance.start(); Configuration old = setConfiguration(instance);
423             * updateLoggers(); if (old != null) { old.stop(); }
424             */
425    
426            LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
427                configLocation, this, cl);
428        }
429    
430        /**
431         * Cause all Loggers to be updated against the current Configuration.
432         */
433        public void updateLoggers() {
434            updateLoggers(this.config);
435        }
436    
437        /**
438         * Cause all Logger to be updated against the specified Configuration.
439         * @param config The Configuration.
440         */
441        public void updateLoggers(final Configuration config) {
442            for (final Logger logger : loggers.values()) {
443                logger.updateConfiguration(config);
444            }
445        }
446    
447        /**
448         * Cause a reconfiguration to take place when the underlying configuration
449         * file changes.
450         *
451         * @param reconfigurable The Configuration that can be reconfigured.
452         */
453        @Override
454        public synchronized void onChange(final Reconfigurable reconfigurable) {
455            LOGGER.debug("Reconfiguration started for context {} ({})", name, this);
456            final Configuration config = reconfigurable.reconfigure();
457            if (config != null) {
458                setConfiguration(config);
459                LOGGER.debug("Reconfiguration completed for {} ({})", name, this);
460            } else {
461                LOGGER.debug("Reconfiguration failed for {} ({})", name, this);
462            }
463        }
464    
465        // LOG4J2-151: changed visibility from private to protected
466        protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
467            return new Logger(ctx, name, messageFactory);
468        }
469    
470    }