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