View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core;
18  
19  import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER;
20  
21  import java.beans.PropertyChangeEvent;
22  import java.beans.PropertyChangeListener;
23  import java.io.File;
24  import java.net.URI;
25  import java.util.Collection;
26  import java.util.List;
27  import java.util.Objects;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.concurrent.ConcurrentMap;
30  import java.util.concurrent.CopyOnWriteArrayList;
31  import java.util.concurrent.TimeUnit;
32  import java.util.concurrent.locks.Lock;
33  import java.util.concurrent.locks.ReentrantLock;
34  
35  import org.apache.logging.log4j.LogManager;
36  import org.apache.logging.log4j.core.config.Configuration;
37  import org.apache.logging.log4j.core.config.ConfigurationFactory;
38  import org.apache.logging.log4j.core.config.ConfigurationListener;
39  import org.apache.logging.log4j.core.config.ConfigurationSource;
40  import org.apache.logging.log4j.core.config.DefaultConfiguration;
41  import org.apache.logging.log4j.core.config.NullConfiguration;
42  import org.apache.logging.log4j.core.config.Reconfigurable;
43  import org.apache.logging.log4j.core.impl.Log4jLogEvent;
44  import org.apache.logging.log4j.core.impl.ThreadContextDataInjector;
45  import org.apache.logging.log4j.core.jmx.Server;
46  import org.apache.logging.log4j.core.util.Cancellable;
47  import org.apache.logging.log4j.core.util.ExecutorServices;
48  import org.apache.logging.log4j.core.util.Loader;
49  import org.apache.logging.log4j.core.util.NetUtils;
50  import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
51  import org.apache.logging.log4j.message.MessageFactory;
52  import org.apache.logging.log4j.spi.AbstractLogger;
53  import org.apache.logging.log4j.spi.LoggerContextFactory;
54  import org.apache.logging.log4j.spi.LoggerContextShutdownAware;
55  import org.apache.logging.log4j.spi.LoggerContextShutdownEnabled;
56  import org.apache.logging.log4j.spi.LoggerRegistry;
57  import org.apache.logging.log4j.spi.Terminable;
58  import org.apache.logging.log4j.spi.ThreadContextMapFactory;
59  import org.apache.logging.log4j.util.PropertiesUtil;
60  
61  
62  /**
63   * The LoggerContext is the anchor for the logging system. It maintains a list of all the loggers requested by
64   * applications and a reference to the Configuration. The Configuration will contain the configured loggers, appenders,
65   * filters, etc and will be atomically updated whenever a reconfigure occurs.
66   */
67  public class LoggerContext extends AbstractLifeCycle
68          implements org.apache.logging.log4j.spi.LoggerContext, AutoCloseable, Terminable, ConfigurationListener,
69          LoggerContextShutdownEnabled {
70  
71      static {
72          try {
73              // LOG4J2-1642 preload ExecutorServices as it is used in shutdown hook
74              Loader.loadClass(ExecutorServices.class.getName());
75          } catch (final Exception e) {
76              LOGGER.error("Failed to preload ExecutorServices class.", e);
77          }
78      }
79  
80      /**
81       * Property name of the property change event fired if the configuration is changed.
82       */
83      public static final String PROPERTY_CONFIG = "config";
84  
85      private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
86  
87      private final LoggerRegistry<Logger> loggerRegistry = new LoggerRegistry<>();
88      private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<>();
89      private volatile List<LoggerContextShutdownAware> listeners;
90  
91      /**
92       * The Configuration is volatile to guarantee that initialization of the Configuration has completed before the
93       * reference is updated.
94       */
95      private volatile Configuration configuration = new DefaultConfiguration();
96      private static final String EXTERNAL_CONTEXT_KEY = "__EXTERNAL_CONTEXT_KEY__";
97      private ConcurrentMap<String, Object> externalMap = new ConcurrentHashMap<>();
98      private String contextName;
99      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 }