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