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}