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.Collection;
024import java.util.Objects;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.ConcurrentMap;
027import java.util.concurrent.CopyOnWriteArrayList;
028import java.util.concurrent.locks.Lock;
029import java.util.concurrent.locks.ReentrantLock;
030
031import org.apache.logging.log4j.LogManager;
032import org.apache.logging.log4j.core.config.Configuration;
033import org.apache.logging.log4j.core.config.ConfigurationFactory;
034import org.apache.logging.log4j.core.config.ConfigurationListener;
035import org.apache.logging.log4j.core.config.ConfigurationSource; // SUPPRESS CHECKSTYLE
036import org.apache.logging.log4j.core.config.DefaultConfiguration;
037import org.apache.logging.log4j.core.config.NullConfiguration;
038import org.apache.logging.log4j.core.config.Reconfigurable;
039import org.apache.logging.log4j.core.impl.Log4jLogEvent;
040import org.apache.logging.log4j.core.jmx.Server;
041import org.apache.logging.log4j.core.util.Cancellable;
042import org.apache.logging.log4j.core.util.NanoClockFactory;
043import org.apache.logging.log4j.core.util.NetUtils;
044import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
045import org.apache.logging.log4j.message.MessageFactory;
046import org.apache.logging.log4j.spi.AbstractLogger;
047import org.apache.logging.log4j.spi.LoggerContextFactory;
048import org.apache.logging.log4j.spi.LoggerContextKey;
049
050import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.*;
051
052/**
053 * The LoggerContext is the anchor for the logging system. It maintains a list of all the loggers requested by
054 * applications and a reference to the Configuration. The Configuration will contain the configured loggers, appenders,
055 * filters, etc and will be atomically updated whenever a reconfigure occurs.
056 */
057public class LoggerContext extends AbstractLifeCycle implements org.apache.logging.log4j.spi.LoggerContext,
058        ConfigurationListener {
059
060    /**
061     * Property name of the property change event fired if the configuration is changed.
062     */
063    public static final String PROPERTY_CONFIG = "config";
064
065    private static final long serialVersionUID = 1L;
066    private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
067
068    private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<>();
069    private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<>();
070
071    /**
072     * The Configuration is volatile to guarantee that initialization of the Configuration has completed before the
073     * reference is updated.
074     */
075    private volatile Configuration configuration = new DefaultConfiguration();
076    private Object externalContext;
077    private String contextName;
078    private volatile URI configLocation;
079    private Cancellable shutdownCallback;
080
081    private final Lock configLock = new ReentrantLock();
082
083    /**
084     * Constructor taking only a name.
085     * 
086     * @param name The context name.
087     */
088    public LoggerContext(final String name) {
089        this(name, null, (URI) null);
090    }
091
092    /**
093     * Constructor taking a name and a reference to an external context.
094     * 
095     * @param name The context name.
096     * @param externalContext The external context.
097     */
098    public LoggerContext(final String name, final Object externalContext) {
099        this(name, externalContext, (URI) null);
100    }
101
102    /**
103     * Constructor taking a name, external context and a configuration URI.
104     * 
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.contextName = name;
111        this.externalContext = externalContext;
112        this.configLocation = configLocn;
113    }
114
115    /**
116     * Constructor taking a name external context and a configuration location String. The location must be resolvable
117     * 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.contextName = 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    /**
140     * Returns the current LoggerContext.
141     * <p>
142     * Avoids the type cast for:
143     * </p>
144     *
145     * <pre>
146     * (LoggerContext) LogManager.getContext();
147     * </pre>
148     *
149     * <p>
150     * WARNING - The LoggerContext returned by this method may not be the LoggerContext used to create a Logger for the
151     * calling class.
152     * </p>
153     * 
154     * @return The current LoggerContext.
155     * @see LogManager#getContext()
156     */
157    public static LoggerContext getContext() {
158        return (LoggerContext) LogManager.getContext();
159    }
160
161    /**
162     * Returns a LoggerContext.
163     * <p>
164     * Avoids the type cast for:
165     * </p>
166     *
167     * <pre>
168     * (LoggerContext) LogManager.getContext(currentContext);
169     * </pre>
170     *
171     * @param currentContext if false the LoggerContext appropriate for the caller of this method is returned. For
172     *            example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be
173     *            returned and if the caller is a class in the container's classpath then a different LoggerContext may
174     *            be returned. If true then only a single LoggerContext will be returned.
175     * @return a LoggerContext.
176     * @see LogManager#getContext(boolean)
177     */
178    public static LoggerContext getContext(final boolean currentContext) {
179        return (LoggerContext) LogManager.getContext(currentContext);
180    }
181
182    /**
183     * Returns a LoggerContext.
184     * <p>
185     * Avoids the type cast for:
186     * </p>
187     *
188     * <pre>
189     * (LoggerContext) LogManager.getContext(loader, currentContext, configLocation);
190     * </pre>
191     *
192     * @param loader The ClassLoader for the context. If null the context will attempt to determine the appropriate
193     *            ClassLoader.
194     * @param currentContext if false the LoggerContext appropriate for the caller of this method is returned. For
195     *            example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be
196     *            returned and if the caller is a class in the container's classpath then a different LoggerContext may
197     *            be returned. If true then only a single LoggerContext will be returned.
198     * @param configLocation The URI for the configuration to use.
199     * @return a LoggerContext.
200     * @see LogManager#getContext(ClassLoader, boolean, URI)
201     */
202    public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext,
203            final URI configLocation) {
204        return (LoggerContext) LogManager.getContext(loader, currentContext, configLocation);
205    }
206
207    @Override
208    public void start() {
209        LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
210        if (configLock.tryLock()) {
211            try {
212                if (this.isInitialized() || this.isStopped()) {
213                    this.setStarting();
214                    reconfigure();
215                    if (this.configuration.isShutdownHookEnabled()) {
216                        setUpShutdownHook();
217                    }
218                    this.setStarted();
219                }
220            } finally {
221                configLock.unlock();
222            }
223        }
224        LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
225    }
226
227    /**
228     * Starts with a specific configuration.
229     * 
230     * @param config The new Configuration.
231     */
232    public void start(final Configuration config) {
233        LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config);
234        if (configLock.tryLock()) {
235            try {
236                if (this.isInitialized() || this.isStopped()) {
237                    if (this.configuration.isShutdownHookEnabled()) {
238                        setUpShutdownHook();
239                    }
240                    this.setStarted();
241                }
242            } finally {
243                configLock.unlock();
244            }
245        }
246        setConfiguration(config);
247        LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config);
248    }
249
250    private void setUpShutdownHook() {
251        if (shutdownCallback == null) {
252            final LoggerContextFactory factory = LogManager.getFactory();
253            if (factory instanceof ShutdownCallbackRegistry) {
254                LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Shutdown hook enabled. Registering a new one.");
255                try {
256                    this.shutdownCallback = ((ShutdownCallbackRegistry) factory).addShutdownCallback(new Runnable() {
257                        @Override
258                        public void run() {
259                            final LoggerContext context = LoggerContext.this;
260                            LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Stopping LoggerContext[name={}, {}]",
261                                    context.getName(), context);
262                            context.stop();
263                        }
264
265                        @Override
266                        public String toString() {
267                            return "Shutdown callback for LoggerContext[name=" + LoggerContext.this.getName() + ']';
268                        }
269                    });
270                } catch (final IllegalStateException e) {
271                    LOGGER.error(SHUTDOWN_HOOK_MARKER,
272                            "Unable to register shutdown hook because JVM is shutting down.", e);
273                } catch (final SecurityException e) {
274                    LOGGER.error(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook due to security restrictions",
275                            e);
276                }
277            }
278        }
279    }
280
281    @Override
282    public void stop() {
283        LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this);
284        configLock.lock();
285        try {
286            if (this.isStopped()) {
287                return;
288            }
289
290            this.setStopping();
291            try {
292                Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500
293            } catch (final Exception ex) {
294                LOGGER.error("Unable to unregister MBeans", ex);
295            }
296            if (shutdownCallback != null) {
297                shutdownCallback.cancel();
298                shutdownCallback = null;
299            }
300            final Configuration prev = configuration;
301            configuration = NULL_CONFIGURATION;
302            updateLoggers();
303            prev.stop();
304            externalContext = null;
305            LogManager.getFactory().removeContext(this);
306            this.setStopped();
307        } finally {
308            configLock.unlock();
309        }
310        LOGGER.debug("Stopped LoggerContext[name={}, {}]...", getName(), this);
311    }
312
313    /**
314     * Gets the name.
315     *
316     * @return the name.
317     */
318    public String getName() {
319        return contextName;
320    }
321    
322    /**
323     * Gets the root logger.
324     * 
325     * @return the root logger.
326     */
327    public Logger getRootLogger() {
328        return getLogger(LogManager.ROOT_LOGGER_NAME);
329    }
330    
331    /**
332     * Sets the name.
333     * 
334     * @param name the new LoggerContext name
335     * @throws NullPointerException if the specified name is {@code null}
336     */
337    public void setName(final String name) {
338        contextName = Objects.requireNonNull(name);
339    }
340
341    /**
342     * Sets the external context.
343     * 
344     * @param context The external context.
345     */
346    public void setExternalContext(final Object context) {
347        this.externalContext = context;
348    }
349
350    /**
351     * Returns the external context.
352     * 
353     * @return The external context.
354     */
355    @Override
356    public Object getExternalContext() {
357        return this.externalContext;
358    }
359
360    /**
361     * Gets a Logger from the Context.
362     * 
363     * @param name The name of the Logger to return.
364     * @return The Logger.
365     */
366    @Override
367    public Logger getLogger(final String name) {
368        return getLogger(name, null);
369    }
370
371    /**
372     * Gets a collection of the current loggers.
373     * <p>
374     * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this
375     * collection at your own risk.
376     * </p>
377     *
378     * @return a collection of the current loggers.
379     */
380    public Collection<Logger> getLoggers() {
381        return loggers.values();
382    }
383
384    /**
385     * Obtains a Logger from the Context.
386     * 
387     * @param name The name of the Logger to return.
388     * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change the
389     *            logger but will log a warning if mismatched.
390     * @return The Logger.
391     */
392    @Override
393    public Logger getLogger(final String name, final MessageFactory messageFactory) {
394        // Note: This is the only method where we add entries to the 'loggers' ivar. 
395        // The loggers map key is the logger name plus the messageFactory FQCN.
396        String key = LoggerContextKey.create(name, messageFactory);
397        Logger logger = loggers.get(key);
398        if (logger != null) {
399            AbstractLogger.checkMessageFactory(logger, messageFactory);
400            return logger;
401        }
402
403        logger = newInstance(this, name, messageFactory);
404        // If messageFactory was null then we need to pull it out of the logger now
405        key = LoggerContextKey.create(name, logger.getMessageFactory());
406        final Logger prev = loggers.putIfAbsent(key, logger);
407        return prev == null ? logger : prev;
408    }
409
410    /**
411     * Determines if the specified Logger exists.
412     * 
413     * @param name The Logger name to search for.
414     * @return True if the Logger exists, false otherwise.
415     */
416    @Override
417    public boolean hasLogger(final String name) {
418        return loggers.containsKey(LoggerContextKey.create(name));
419    }
420
421    /**
422     * Determines if the specified Logger exists.
423     * 
424     * @param name The Logger name to search for.
425     * @return True if the Logger exists, false otherwise.
426     */
427    @Override
428    public boolean hasLogger(final String name, MessageFactory messageFactory) {
429        return loggers.containsKey(LoggerContextKey.create(name, messageFactory));
430    }
431
432    /**
433     * Determines if the specified Logger exists.
434     * 
435     * @param name The Logger name to search for.
436     * @return True if the Logger exists, false otherwise.
437     */
438    @Override
439    public boolean hasLogger(final String name, Class<? extends MessageFactory> messageFactoryClass) {
440        return loggers.containsKey(LoggerContextKey.create(name, messageFactoryClass));
441    }
442
443    /**
444     * Returns the current Configuration. The Configuration will be replaced when a reconfigure occurs.
445     *
446     * @return The Configuration.
447     */
448    public Configuration getConfiguration() {
449        return configuration;
450    }
451
452    /**
453     * Adds a Filter to the Configuration. Filters that are added through the API will be lost when a reconfigure
454     * occurs.
455     * 
456     * @param filter The Filter to add.
457     */
458    public void addFilter(final Filter filter) {
459        configuration.addFilter(filter);
460    }
461
462    /**
463     * Removes a Filter from the current Configuration.
464     * 
465     * @param filter The Filter to remove.
466     */
467    public void removeFilter(final Filter filter) {
468        configuration.removeFilter(filter);
469    }
470
471    /**
472     * Sets the Configuration to be used.
473     * 
474     * @param config The new Configuration.
475     * @return The previous Configuration.
476     */
477    private Configuration setConfiguration(final Configuration config) {
478        Objects.requireNonNull(config, "No Configuration was provided");
479        configLock.lock();
480        try {
481            final Configuration prev = this.configuration;
482            config.addListener(this);
483            final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
484
485            try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
486                map.putIfAbsent("hostName", NetUtils.getLocalHostname());
487            } catch (final Exception ex) {
488                LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
489                map.putIfAbsent("hostName", "unknown");
490            }
491            map.putIfAbsent("contextName", contextName);
492            config.start();
493            this.configuration = config;
494            updateLoggers();
495            if (prev != null) {
496                prev.removeListener(this);
497                prev.stop();
498            }
499
500            firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
501
502            try {
503                Server.reregisterMBeansAfterReconfigure();
504            } catch (final Throwable t) {
505                // LOG4J2-716: Android has no java.lang.management
506                LOGGER.error("Could not reconfigure JMX", t);
507            }
508            // AsyncLoggers update their nanoClock when the configuration changes
509            Log4jLogEvent.setNanoClock(NanoClockFactory.createNanoClock());
510            
511            return prev;
512        } finally {
513            configLock.unlock();
514        }
515    }
516
517    private void firePropertyChangeEvent(final PropertyChangeEvent event) {
518        for (final PropertyChangeListener listener : propertyChangeListeners) {
519            listener.propertyChange(event);
520        }
521    }
522
523    public void addPropertyChangeListener(final PropertyChangeListener listener) {
524        propertyChangeListeners.add(Objects.requireNonNull(listener, "listener"));
525    }
526
527    public void removePropertyChangeListener(final PropertyChangeListener listener) {
528        propertyChangeListeners.remove(listener);
529    }
530
531    /**
532     * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
533     * current configuration. Use {@link #getConfiguration()}.{@link Configuration#getConfigurationSource()
534     * getConfigurationSource()}.{@link ConfigurationSource#getLocation() getLocation()} to get the actual source of the
535     * current configuration.
536     * 
537     * @return the initial configuration location or {@code null}
538     */
539    public URI getConfigLocation() {
540        return configLocation;
541    }
542
543    /**
544     * Sets the configLocation to the specified value and reconfigures this context.
545     * 
546     * @param configLocation the location of the new configuration
547     */
548    public void setConfigLocation(final URI configLocation) {
549        this.configLocation = configLocation;
550
551        reconfigure(configLocation);
552    }
553
554    /**
555     * Reconfigures the context.
556     */
557    private void reconfigure(final URI configURI) {
558        final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
559        LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
560                contextName, configURI, this, cl);
561        final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(contextName, configURI, cl);
562        setConfiguration(instance);
563        /*
564         * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) {
565         * old.stop(); }
566         */
567        final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource());
568        LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
569                contextName, location, this, cl);
570    }
571
572    /**
573     * Reconfigures the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new
574     * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old
575     * LoggerConfig, along with old Appenders and Filters.
576     */
577    public void reconfigure() {
578        reconfigure(configLocation);
579    }
580
581    /**
582     * Causes all Loggers to be updated against the current Configuration.
583     */
584    public void updateLoggers() {
585        updateLoggers(this.configuration);
586    }
587
588    /**
589     * Causes all Logger to be updated against the specified Configuration.
590     * 
591     * @param config The Configuration.
592     */
593    public void updateLoggers(final Configuration config) {
594        for (final Logger logger : loggers.values()) {
595            logger.updateConfiguration(config);
596        }
597    }
598
599    /**
600     * Causes a reconfiguration to take place when the underlying configuration file changes.
601     *
602     * @param reconfigurable The Configuration that can be reconfigured.
603     */
604    @Override
605    public synchronized void onChange(final Reconfigurable reconfigurable) {
606        LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this);
607        final Configuration newConfig = reconfigurable.reconfigure();
608        if (newConfig != null) {
609            setConfiguration(newConfig);
610            LOGGER.debug("Reconfiguration completed for {} ({})", contextName, this);
611        } else {
612            LOGGER.debug("Reconfiguration failed for {} ({})", contextName, this);
613        }
614    }
615
616    // LOG4J2-151: changed visibility from private to protected
617    protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
618        return new Logger(ctx, name, messageFactory);
619    }
620
621}