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.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.List;
029import java.util.Objects;
030import java.util.concurrent.ConcurrentMap;
031import java.util.concurrent.CopyOnWriteArrayList;
032import java.util.concurrent.TimeUnit;
033import java.util.concurrent.locks.Lock;
034import java.util.concurrent.locks.ReentrantLock;
035
036import org.apache.logging.log4j.LogManager;
037import org.apache.logging.log4j.core.config.Configuration;
038import org.apache.logging.log4j.core.config.ConfigurationFactory;
039import org.apache.logging.log4j.core.config.ConfigurationListener;
040import org.apache.logging.log4j.core.config.ConfigurationSource;
041import org.apache.logging.log4j.core.config.DefaultConfiguration;
042import org.apache.logging.log4j.core.config.NullConfiguration;
043import org.apache.logging.log4j.core.config.Reconfigurable;
044import org.apache.logging.log4j.core.impl.Log4jLogEvent;
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 = null;
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 Object externalContext;
097    private String contextName;
098    private volatile URI configLocation;
099    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}