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