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