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