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