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.async.AsyncLogger;
033import org.apache.logging.log4j.core.config.Configuration;
034import org.apache.logging.log4j.core.config.ConfigurationFactory;
035import org.apache.logging.log4j.core.config.ConfigurationListener;
036import org.apache.logging.log4j.core.config.ConfigurationSource; // SUPPRESS CHECKSTYLE
037import org.apache.logging.log4j.core.config.DefaultConfiguration;
038import org.apache.logging.log4j.core.config.NullConfiguration;
039import org.apache.logging.log4j.core.config.Reconfigurable;
040import org.apache.logging.log4j.core.impl.Log4jLogEvent;
041import org.apache.logging.log4j.core.jmx.Server;
042import org.apache.logging.log4j.core.util.Cancellable;
043import org.apache.logging.log4j.core.util.NanoClockFactory;
044import org.apache.logging.log4j.core.util.NetUtils;
045import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
046import org.apache.logging.log4j.message.MessageFactory;
047import org.apache.logging.log4j.spi.AbstractLogger;
048import org.apache.logging.log4j.spi.LoggerContextFactory;
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 final 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.fatal(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     * Sets the external context.
324     * 
325     * @param context The external context.
326     */
327    public void setExternalContext(final Object context) {
328        this.externalContext = context;
329    }
330
331    /**
332     * Returns the external context.
333     * 
334     * @return The external context.
335     */
336    @Override
337    public Object getExternalContext() {
338        return this.externalContext;
339    }
340
341    /**
342     * Obtains a Logger from the Context.
343     * 
344     * @param name The name of the Logger to return.
345     * @return The Logger.
346     */
347    @Override
348    public Logger getLogger(final String name) {
349        return getLogger(name, null);
350    }
351
352    /**
353     * Gets a collection of the current loggers.
354     * <p>
355     * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this
356     * collection at your own risk.
357     * </p>
358     *
359     * @return a collection of the current loggers.
360     */
361    public Collection<Logger> getLoggers() {
362        return loggers.values();
363    }
364
365    /**
366     * Obtains a Logger from the Context.
367     * 
368     * @param name The name of the Logger to return.
369     * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change the
370     *            logger but will log a warning if mismatched.
371     * @return The Logger.
372     */
373    @Override
374    public Logger getLogger(final String name, final MessageFactory messageFactory) {
375        Logger logger = loggers.get(name);
376        if (logger != null) {
377            AbstractLogger.checkMessageFactory(logger, messageFactory);
378            return logger;
379        }
380
381        logger = newInstance(this, name, messageFactory);
382        final Logger prev = loggers.putIfAbsent(name, logger);
383        return prev == null ? logger : prev;
384    }
385
386    /**
387     * Determines if the specified Logger exists.
388     * 
389     * @param name The Logger name to search for.
390     * @return True if the Logger exists, false otherwise.
391     */
392    @Override
393    public boolean hasLogger(final String name) {
394        return loggers.containsKey(name);
395    }
396
397    /**
398     * Returns the current Configuration. The Configuration will be replaced when a reconfigure occurs.
399     *
400     * @return The Configuration.
401     */
402    public Configuration getConfiguration() {
403        return configuration;
404    }
405
406    /**
407     * Adds a Filter to the Configuration. Filters that are added through the API will be lost when a reconfigure
408     * occurs.
409     * 
410     * @param filter The Filter to add.
411     */
412    public void addFilter(final Filter filter) {
413        configuration.addFilter(filter);
414    }
415
416    /**
417     * Removes a Filter from the current Configuration.
418     * 
419     * @param filter The Filter to remove.
420     */
421    public void removeFilter(final Filter filter) {
422        configuration.removeFilter(filter);
423    }
424
425    /**
426     * Sets the Configuration to be used.
427     * 
428     * @param config The new Configuration.
429     * @return The previous Configuration.
430     */
431    private Configuration setConfiguration(final Configuration config) {
432        Objects.requireNonNull(config, "No Configuration was provided");
433        configLock.lock();
434        try {
435            final Configuration prev = this.configuration;
436            config.addListener(this);
437            final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
438
439            try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
440                map.putIfAbsent("hostName", NetUtils.getLocalHostname());
441            } catch (final Exception ex) {
442                LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
443                map.putIfAbsent("hostName", "unknown");
444            }
445            map.putIfAbsent("contextName", contextName);
446            config.start();
447            this.configuration = config;
448            updateLoggers();
449            if (prev != null) {
450                prev.removeListener(this);
451                prev.stop();
452            }
453
454            firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
455
456            try {
457                Server.reregisterMBeansAfterReconfigure();
458            } catch (final Throwable t) {
459                // LOG4J2-716: Android has no java.lang.management
460                LOGGER.error("Could not reconfigure JMX", t);
461            }
462            Log4jLogEvent.setNanoClock(NanoClockFactory.createNanoClock());
463            try {
464                AsyncLogger.setNanoClock(NanoClockFactory.createNanoClock());
465            } catch (Throwable ignored) {
466                // LMAX Disruptor jar may not be in the classpath. Ignore this.
467                LOGGER.debug("Could not set AsyncLogger NanoClock. Ignoring: " + ignored.toString());
468            }
469            return prev;
470        } finally {
471            configLock.unlock();
472        }
473    }
474
475    private void firePropertyChangeEvent(final PropertyChangeEvent event) {
476        for (final PropertyChangeListener listener : propertyChangeListeners) {
477            listener.propertyChange(event);
478        }
479    }
480
481    public void addPropertyChangeListener(final PropertyChangeListener listener) {
482        propertyChangeListeners.add(Objects.requireNonNull(listener, "listener"));
483    }
484
485    public void removePropertyChangeListener(final PropertyChangeListener listener) {
486        propertyChangeListeners.remove(listener);
487    }
488
489    /**
490     * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
491     * current configuration. Use {@link #getConfiguration()}.{@link Configuration#getConfigurationSource()
492     * getConfigurationSource()}.{@link ConfigurationSource#getLocation() getLocation()} to get the actual source of the
493     * current configuration.
494     * 
495     * @return the initial configuration location or {@code null}
496     */
497    public URI getConfigLocation() {
498        return configLocation;
499    }
500
501    /**
502     * Sets the configLocation to the specified value and reconfigures this context.
503     * 
504     * @param configLocation the location of the new configuration
505     */
506    public void setConfigLocation(final URI configLocation) {
507        this.configLocation = configLocation;
508
509        reconfigure(configLocation);
510    }
511
512    /**
513     * Reconfigure the context.
514     */
515    private void reconfigure(final URI configURI) {
516        final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
517        LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
518                contextName, configURI, this, cl);
519        final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(contextName, configURI, cl);
520        setConfiguration(instance);
521        /*
522         * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) {
523         * old.stop(); }
524         */
525
526        LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
527                contextName, configURI, this, cl);
528    }
529
530    /**
531     * Reconfigure the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new
532     * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old
533     * LoggerConfig, along with old Appenders and Filters.
534     */
535    public void reconfigure() {
536        reconfigure(configLocation);
537    }
538
539    /**
540     * Causes all Loggers to be updated against the current Configuration.
541     */
542    public void updateLoggers() {
543        updateLoggers(this.configuration);
544    }
545
546    /**
547     * Causes all Logger to be updated against the specified Configuration.
548     * 
549     * @param config The Configuration.
550     */
551    public void updateLoggers(final Configuration config) {
552        for (final Logger logger : loggers.values()) {
553            logger.updateConfiguration(config);
554        }
555    }
556
557    /**
558     * Causes a reconfiguration to take place when the underlying configuration file changes.
559     *
560     * @param reconfigurable The Configuration that can be reconfigured.
561     */
562    @Override
563    public synchronized void onChange(final Reconfigurable reconfigurable) {
564        LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this);
565        final Configuration newConfig = reconfigurable.reconfigure();
566        if (newConfig != null) {
567            setConfiguration(newConfig);
568            LOGGER.debug("Reconfiguration completed for {} ({})", contextName, this);
569        } else {
570            LOGGER.debug("Reconfiguration failed for {} ({})", contextName, this);
571        }
572    }
573
574    // LOG4J2-151: changed visibility from private to protected
575    protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
576        return new Logger(ctx, name, messageFactory);
577    }
578
579}