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