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