This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 724281cec4 Implement `MessageFactory`-namespaced logger registry 
(#2936)
724281cec4 is described below

commit 724281cec45300690b42ef863790e5ce1a3d0e57
Author: Volkan Yazıcı <[email protected]>
AuthorDate: Mon Sep 16 13:15:33 2024 +0200

    Implement `MessageFactory`-namespaced logger registry (#2936)
    
    Co-authored-by: Piotr P. Karwasz <[email protected]>
---
 .../log4j/message/LocalizedMessageFactory.java     |  22 ++
 .../apache/logging/log4j/message/package-info.java |   2 +-
 .../logging/log4j/simple/SimpleLoggerContext.java  |  33 +--
 .../apache/logging/log4j/simple/package-info.java  |   2 +-
 .../apache/logging/log4j/spi/AbstractLogger.java   |   4 +
 .../apache/logging/log4j/spi/LoggerContext.java    |   7 +-
 .../apache/logging/log4j/spi/LoggerRegistry.java   | 294 ++++++++++++++++-----
 .../org/apache/logging/log4j/spi/package-info.java |   2 +-
 .../java/org/apache/logging/log4j/core/Logger.java |   2 +-
 .../apache/logging/log4j/core/LoggerContext.java   |  58 ++--
 .../logging/log4j/core/async/package-info.java     |   2 +-
 .../apache/logging/log4j/core/package-info.java    |   2 +-
 log4j-taglib/pom.xml                               |  14 +-
 .../log4j/taglib/Log4jTaglibLoggerContext.java     |  92 ++++---
 .../apache/logging/log4j/taglib/package-info.java  |   2 +-
 .../logging/log4j/tojul/JULLoggerContext.java      |  33 ++-
 .../apache/logging/log4j/tojul/package-info.java   |   2 +-
 .../apache/logging/slf4j/SLF4JLoggerContext.java   |  34 ++-
 .../org/apache/logging/slf4j/package-info.java     |   2 +-
 pom.xml                                            |  17 ++
 ...eprecate_AbstractLogger_checkMessageFactory.xml |   8 +
 ...ke_LoggerRegistry_MessageFactory_namespaced.xml |   9 +
 22 files changed, 463 insertions(+), 180 deletions(-)

diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java
index d8360dff0e..10d75fc907 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java
@@ -16,7 +16,9 @@
  */
 package org.apache.logging.log4j.message;
 
+import java.util.Objects;
 import java.util.ResourceBundle;
+import org.jspecify.annotations.Nullable;
 
 /**
  * Creates {@link FormattedMessage} instances for {@link MessageFactory2} 
methods (and {@link MessageFactory} by
@@ -33,8 +35,11 @@ import java.util.ResourceBundle;
 public class LocalizedMessageFactory extends AbstractMessageFactory {
     private static final long serialVersionUID = -1996295808703146741L;
 
+    @Nullable
     // FIXME: cannot use ResourceBundle name for serialization until Java 8
     private final transient ResourceBundle resourceBundle;
+
+    @Nullable
     private final String baseName;
 
     public LocalizedMessageFactory(final ResourceBundle resourceBundle) {
@@ -92,4 +97,21 @@ public class LocalizedMessageFactory extends 
AbstractMessageFactory {
         }
         return new LocalizedMessage(resourceBundle, key, params);
     }
+
+    @Override
+    public boolean equals(final Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (object == null || getClass() != object.getClass()) {
+            return false;
+        }
+        final LocalizedMessageFactory that = (LocalizedMessageFactory) object;
+        return Objects.equals(resourceBundle, that.resourceBundle) && 
Objects.equals(baseName, that.baseName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(resourceBundle, baseName);
+    }
 }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/package-info.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/package-info.java
index 816466b467..e2346e8055 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/package-info.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/package-info.java
@@ -19,7 +19,7 @@
  * Public Message Types used for Log4j 2. Users may implement their own 
Messages.
  */
 @Export
-@Version("2.24.0")
+@Version("2.25.0")
 package org.apache.logging.log4j.message;
 
 import org.osgi.annotation.bundle.Export;
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java
index d1aaf8db0f..e4052e87a4 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java
@@ -20,12 +20,13 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import java.io.PrintStream;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.message.ParameterizedMessageFactory;
 import org.apache.logging.log4j.simple.internal.SimpleProvider;
-import org.apache.logging.log4j.spi.AbstractLogger;
 import org.apache.logging.log4j.spi.ExtendedLogger;
 import org.apache.logging.log4j.spi.LoggerContext;
 import org.apache.logging.log4j.spi.LoggerRegistry;
 import org.apache.logging.log4j.util.PropertiesUtil;
+import org.jspecify.annotations.Nullable;
 
 /**
  * A simple {@link LoggerContext} implementation.
@@ -41,6 +42,8 @@ public class SimpleLoggerContext implements LoggerContext {
     /** All system properties used by <code>SimpleLog</code> start with this */
     protected static final String SYSTEM_PREFIX = 
"org.apache.logging.log4j.simplelog.";
 
+    private static final MessageFactory DEFAULT_MESSAGE_FACTORY = 
ParameterizedMessageFactory.INSTANCE;
+
     private final PropertiesUtil props;
 
     /** Include the instance name in the log message? */
@@ -96,14 +99,14 @@ public class SimpleLoggerContext implements LoggerContext {
     }
 
     @Override
-    public ExtendedLogger getLogger(final String name, final MessageFactory 
messageFactory) {
-        // Note: This is the only method where we add entries to the 
'loggerRegistry' ivar.
-        final ExtendedLogger extendedLogger = loggerRegistry.getLogger(name, 
messageFactory);
-        if (extendedLogger != null) {
-            AbstractLogger.checkMessageFactory(extendedLogger, messageFactory);
-            return extendedLogger;
-        }
-        final SimpleLogger simpleLogger = new SimpleLogger(
+    public ExtendedLogger getLogger(final String name, @Nullable final 
MessageFactory messageFactory) {
+        final MessageFactory effectiveMessageFactory =
+                messageFactory != null ? messageFactory : 
DEFAULT_MESSAGE_FACTORY;
+        return loggerRegistry.computeIfAbsent(name, effectiveMessageFactory, 
this::createLogger);
+    }
+
+    private ExtendedLogger createLogger(final String name, @Nullable final 
MessageFactory messageFactory) {
+        return new SimpleLogger(
                 name,
                 defaultLevel,
                 showLogName,
@@ -114,8 +117,6 @@ public class SimpleLoggerContext implements LoggerContext {
                 messageFactory,
                 props,
                 stream);
-        loggerRegistry.putIfAbsent(name, messageFactory, simpleLogger);
-        return loggerRegistry.getLogger(name, messageFactory);
     }
 
     /**
@@ -131,16 +132,18 @@ public class SimpleLoggerContext implements LoggerContext 
{
 
     @Override
     public boolean hasLogger(final String name) {
-        return false;
+        return loggerRegistry.hasLogger(name, DEFAULT_MESSAGE_FACTORY);
     }
 
     @Override
     public boolean hasLogger(final String name, final Class<? extends 
MessageFactory> messageFactoryClass) {
-        return false;
+        return loggerRegistry.hasLogger(name, messageFactoryClass);
     }
 
     @Override
-    public boolean hasLogger(final String name, final MessageFactory 
messageFactory) {
-        return false;
+    public boolean hasLogger(final String name, @Nullable final MessageFactory 
messageFactory) {
+        final MessageFactory effectiveMessageFactory =
+                messageFactory != null ? messageFactory : 
DEFAULT_MESSAGE_FACTORY;
+        return loggerRegistry.hasLogger(name, effectiveMessageFactory);
     }
 }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java
index 1481df9462..6e77cad0a6 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java
@@ -20,7 +20,7 @@
  * Providers are able to be loaded at runtime.
  */
 @Export
-@Version("2.24.0")
+@Version("2.25.0")
 package org.apache.logging.log4j.simple;
 
 import org.osgi.annotation.bundle.Export;
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
index ecc499ac58..06d99f3f15 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
@@ -166,7 +166,11 @@ public abstract class AbstractLogger implements 
ExtendedLogger, LocationAwareLog
      *
      * @param logger The logger to check
      * @param messageFactory The message factory to check.
+     * @deprecated As of version {@code 2.25.0}, planned to be removed!
+     * Instead, in {@link LoggerContext#getLogger(String, MessageFactory)} 
implementations, namespace loggers with message factories.
+     * If your implementation uses {@link LoggerRegistry}, you are already 
covered.
      */
+    @Deprecated
     public static void checkMessageFactory(final ExtendedLogger logger, final 
MessageFactory messageFactory) {
         final String name = logger.getName();
         final MessageFactory loggerMessageFactory = logger.getMessageFactory();
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java
index a14af967bf..27874bded0 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.spi;
 
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.message.MessageFactory;
+import org.jspecify.annotations.Nullable;
 
 /**
  * Anchor point for logging implementations.
@@ -54,7 +55,7 @@ public interface LoggerContext {
      * @return The logger.
      * @since 2.14.0
      */
-    default ExtendedLogger getLogger(Class<?> cls, MessageFactory 
messageFactory) {
+    default ExtendedLogger getLogger(Class<?> cls, @Nullable MessageFactory 
messageFactory) {
         final String canonicalName = cls.getCanonicalName();
         return getLogger(canonicalName != null ? canonicalName : 
cls.getName(), messageFactory);
     }
@@ -73,7 +74,7 @@ public interface LoggerContext {
      *                       the logger but will log a warning if mismatched.
      * @return The logger with the specified name.
      */
-    ExtendedLogger getLogger(String name, MessageFactory messageFactory);
+    ExtendedLogger getLogger(String name, @Nullable MessageFactory 
messageFactory);
 
     /**
      * Gets the LoggerRegistry.
@@ -118,7 +119,7 @@ public interface LoggerContext {
      * @return true if the Logger exists, false otherwise.
      * @since 2.5
      */
-    boolean hasLogger(String name, MessageFactory messageFactory);
+    boolean hasLogger(String name, @Nullable MessageFactory messageFactory);
 
     /**
      * Associates an object into the LoggerContext by name for later use.
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerRegistry.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerRegistry.java
index e71c0a933d..323055fb40 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerRegistry.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerRegistry.java
@@ -16,28 +16,50 @@
  */
 package org.apache.logging.log4j.spi;
 
+import static java.util.Objects.requireNonNull;
+
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.BiFunction;
 import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.message.ParameterizedMessageFactory;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
 
 /**
- * Convenience class to be used by {@code LoggerContext} implementations.
+ * Convenience class to be used as an {@link ExtendedLogger} registry by 
{@code LoggerContext} implementations.
  */
+@NullMarked
 public class LoggerRegistry<T extends ExtendedLogger> {
-    private static final String DEFAULT_FACTORY_KEY = 
AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS.getName();
-    private final MapFactory<T> factory;
-    private final Map<String, Map<String, T>> map;
+
+    private final Map<String, Map<MessageFactory, WeakReference<T>>> 
loggerRefByMessageFactoryByName = new HashMap<>();
+
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+    private final Lock readLock = lock.readLock();
+
+    private final Lock writeLock = lock.writeLock();
 
     /**
-     * Interface to control the data structure used by the registry to store 
the Loggers.
+     * Data structure contract for the internal storage of admitted loggers.
+     *
      * @param <T> subtype of {@code ExtendedLogger}
+     * @deprecated As of version {@code 2.25.0}, planned to be removed!
      */
+    @Deprecated
     public interface MapFactory<T extends ExtendedLogger> {
+
         Map<String, T> createInnerMap();
 
         Map<String, Map<String, T>> createOuterMap();
@@ -46,10 +68,14 @@ public class LoggerRegistry<T extends ExtendedLogger> {
     }
 
     /**
-     * Generates ConcurrentHashMaps for use by the registry to store the 
Loggers.
+     * {@link MapFactory} implementation using {@link ConcurrentHashMap}.
+     *
      * @param <T> subtype of {@code ExtendedLogger}
+     * @deprecated As of version {@code 2.25.0}, planned to be removed!
      */
+    @Deprecated
     public static class ConcurrentMapFactory<T extends ExtendedLogger> 
implements MapFactory<T> {
+
         @Override
         public Map<String, T> createInnerMap() {
             return new ConcurrentHashMap<>();
@@ -62,15 +88,19 @@ public class LoggerRegistry<T extends ExtendedLogger> {
 
         @Override
         public void putIfAbsent(final Map<String, T> innerMap, final String 
name, final T logger) {
-            ((ConcurrentMap<String, T>) innerMap).putIfAbsent(name, logger);
+            innerMap.putIfAbsent(name, logger);
         }
     }
 
     /**
-     * Generates WeakHashMaps for use by the registry to store the Loggers.
+     * {@link MapFactory} implementation using {@link WeakHashMap}.
+     *
      * @param <T> subtype of {@code ExtendedLogger}
+     * @deprecated As of version {@code 2.25.0}, planned to be removed!
      */
+    @Deprecated
     public static class WeakMapFactory<T extends ExtendedLogger> implements 
MapFactory<T> {
+
         @Override
         public Map<String, T> createInnerMap() {
             return new WeakHashMap<>();
@@ -87,43 +117,68 @@ public class LoggerRegistry<T extends ExtendedLogger> {
         }
     }
 
-    public LoggerRegistry() {
-        this(new ConcurrentMapFactory<T>());
-    }
+    public LoggerRegistry() {}
 
-    public LoggerRegistry(final MapFactory<T> factory) {
-        this.factory = Objects.requireNonNull(factory, "factory");
-        this.map = factory.createOuterMap();
-    }
-
-    private static String factoryClassKey(final Class<? extends 
MessageFactory> messageFactoryClass) {
-        return messageFactoryClass == null ? DEFAULT_FACTORY_KEY : 
messageFactoryClass.getName();
-    }
-
-    private static String factoryKey(final MessageFactory messageFactory) {
-        return messageFactory == null
-                ? DEFAULT_FACTORY_KEY
-                : messageFactory.getClass().getName();
+    /**
+     * Constructs an instance <b>ignoring</b> the given the map factory.
+     *
+     * @param mapFactory a map factory
+     * @deprecated As of version {@code 2.25.0}, planned to be removed!
+     */
+    @Deprecated
+    public LoggerRegistry(@Nullable final MapFactory<T> mapFactory) {
+        this();
     }
 
     /**
-     * Returns an ExtendedLogger.
-     * @param name The name of the Logger to return.
-     * @return The logger with the specified name.
+     * Returns the logger associated with the given name.
+     * <p>
+     * There can be made no assumptions on the message factory of the returned 
logger.
+     * Callers are strongly advised to switch to {@link #getLogger(String, 
MessageFactory)} and <b>provide a message factory parameter!</b>
+     * </p>
+     *
+     * @param name a logger name
+     * @return the logger associated with the name
+     * @deprecated As of version {@code 2.25.0}, planned to be removed!
+     * Use {@link #getLogger(String, MessageFactory)} instead.
      */
+    @Deprecated
     public T getLogger(final String name) {
-        return getOrCreateInnerMap(DEFAULT_FACTORY_KEY).get(name);
+        requireNonNull(name, "name");
+        return getLogger(name, null);
     }
 
     /**
-     * Returns an ExtendedLogger.
-     * @param name The name of the Logger to return.
-     * @param messageFactory The message factory is used only when creating a 
logger, subsequent use does not change
-     *                       the logger but will log a warning if mismatched.
-     * @return The logger with the specified name.
+     * Returns the logger associated with the given name and message factory.
+     * <p>
+     * In the absence of a message factory, there can be made no assumptions 
on the message factory of the returned logger.
+     * This lenient behaviour is only kept for backward compatibility.
+     * Callers are strongly advised to <b>provide a message factory parameter 
to the method!</b>
+     * </p>
+     *
+     * @param name a logger name
+     * @param messageFactory a message factory
+     * @return the logger associated with the given name and message factory
      */
-    public T getLogger(final String name, final MessageFactory messageFactory) 
{
-        return getOrCreateInnerMap(factoryKey(messageFactory)).get(name);
+    public T getLogger(final String name, @Nullable final MessageFactory 
messageFactory) {
+        requireNonNull(name, "name");
+        readLock.lock();
+        try {
+            final Map<MessageFactory, WeakReference<T>> 
loggerRefByMessageFactory =
+                    loggerRefByMessageFactoryByName.get(name);
+            if (loggerRefByMessageFactory == null) {
+                return null;
+            }
+            final MessageFactory effectiveMessageFactory =
+                    messageFactory != null ? messageFactory : 
ParameterizedMessageFactory.INSTANCE;
+            final WeakReference<T> loggerRef = 
loggerRefByMessageFactory.get(effectiveMessageFactory);
+            if (loggerRef == null) {
+                return null;
+            }
+            return loggerRef.get();
+        } finally {
+            readLock.unlock();
+        }
     }
 
     public Collection<T> getLoggers() {
@@ -131,53 +186,164 @@ public class LoggerRegistry<T extends ExtendedLogger> {
     }
 
     public Collection<T> getLoggers(final Collection<T> destination) {
-        for (final Map<String, T> inner : map.values()) {
-            destination.addAll(inner.values());
+        requireNonNull(destination, "destination");
+        readLock.lock();
+        try {
+            loggerRefByMessageFactoryByName.values().stream()
+                    .flatMap(loggerRefByMessageFactory ->
+                            
loggerRefByMessageFactory.values().stream().map(WeakReference::get))
+                    .filter(Objects::nonNull)
+                    .forEach(destination::add);
+        } finally {
+            readLock.unlock();
         }
         return destination;
     }
 
-    private Map<String, T> getOrCreateInnerMap(final String factoryName) {
-        Map<String, T> inner = map.get(factoryName);
-        if (inner == null) {
-            inner = factory.createInnerMap();
-            map.put(factoryName, inner);
-        }
-        return inner;
-    }
-
     /**
-     * Detects if a Logger with the specified name exists.
-     * @param name The Logger name to search for.
-     * @return true if the Logger exists, false otherwise.
+     * Checks if a logger associated with the given name exists.
+     * <p>
+     * There can be made no assumptions on the message factory of the found 
logger.
+     * Callers are strongly advised to switch to {@link #hasLogger(String, 
MessageFactory)} and <b>provide a message factory parameter!</b>
+     * </p>
+     *
+     * @param name a logger name
+     * @return {@code true}, if the logger exists; {@code false} otherwise.
+     * @deprecated As of version {@code 2.25.0}, planned to be removed!
+     * Use {@link #hasLogger(String, MessageFactory)} instead.
      */
+    @Deprecated
     public boolean hasLogger(final String name) {
-        return getOrCreateInnerMap(DEFAULT_FACTORY_KEY).containsKey(name);
+        requireNonNull(name, "name");
+        final T logger = getLogger(name);
+        return logger != null;
     }
 
     /**
-     * Detects if a Logger with the specified name and MessageFactory exists.
-     * @param name The Logger name to search for.
-     * @param messageFactory The message factory to search for.
-     * @return true if the Logger exists, false otherwise.
+     * Checks if a logger associated with the given name and message factory 
exists.
+     * <p>
+     * In the absence of a message factory, there can be made no assumptions 
on the message factory of the found logger.
+     * This lenient behaviour is only kept for backward compatibility.
+     * Callers are strongly advised to <b>provide a message factory parameter 
to the method!</b>
+     * </p>
+     *
+     * @param name a logger name
+     * @param messageFactory a message factory
+     * @return {@code true}, if the logger exists; {@code false} otherwise.
      * @since 2.5
      */
-    public boolean hasLogger(final String name, final MessageFactory 
messageFactory) {
-        return 
getOrCreateInnerMap(factoryKey(messageFactory)).containsKey(name);
+    public boolean hasLogger(final String name, @Nullable final MessageFactory 
messageFactory) {
+        requireNonNull(name, "name");
+        final T logger = getLogger(name, messageFactory);
+        return logger != null;
     }
 
     /**
-     * Detects if a Logger with the specified name and MessageFactory type 
exists.
-     * @param name The Logger name to search for.
-     * @param messageFactoryClass The message factory class to search for.
-     * @return true if the Logger exists, false otherwise.
+     * Checks if a logger associated with the given name and message factory 
type exists.
+     *
+     * @param name a logger name
+     * @param messageFactoryClass a message factory class
+     * @return {@code true}, if the logger exists; {@code false} otherwise.
      * @since 2.5
      */
     public boolean hasLogger(final String name, final Class<? extends 
MessageFactory> messageFactoryClass) {
-        return 
getOrCreateInnerMap(factoryClassKey(messageFactoryClass)).containsKey(name);
+        requireNonNull(name, "name");
+        requireNonNull(messageFactoryClass, "messageFactoryClass");
+        readLock.lock();
+        try {
+            return loggerRefByMessageFactoryByName.getOrDefault(name, 
Collections.emptyMap()).keySet().stream()
+                    .anyMatch(messageFactory -> 
messageFactoryClass.equals(messageFactory.getClass()));
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    /**
+     * Registers the provided logger using the given name – <b>message factory 
parameter is ignored</b> and the one from the logger will be used instead.
+     *
+     * @param name a logger name
+     * @param messageFactory ignored – kept for backward compatibility
+     * @param logger a logger instance
+     * @deprecated As of version {@code 2.25.0}, planned to be removed!
+     * Use {@link #computeIfAbsent(String, MessageFactory, BiFunction)} 
instead.
+     */
+    @Deprecated
+    public void putIfAbsent(final String name, @Nullable final MessageFactory 
messageFactory, final T logger) {
+
+        // Check arguments
+        requireNonNull(name, "name");
+        requireNonNull(logger, "logger");
+
+        // Insert the logger
+        writeLock.lock();
+        try {
+            final Map<MessageFactory, WeakReference<T>> 
loggerRefByMessageFactory =
+                    loggerRefByMessageFactoryByName.computeIfAbsent(name, 
this::createLoggerRefByMessageFactoryMap);
+            final MessageFactory loggerMessageFactory = 
logger.getMessageFactory();
+            final WeakReference<T> loggerRef = 
loggerRefByMessageFactory.get(loggerMessageFactory);
+            if (loggerRef == null || loggerRef.get() == null) {
+                loggerRefByMessageFactory.put(loggerMessageFactory, new 
WeakReference<>(logger));
+            }
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    public T computeIfAbsent(
+            final String name,
+            final MessageFactory messageFactory,
+            final BiFunction<String, MessageFactory, T> loggerSupplier) {
+
+        // Check arguments
+        requireNonNull(name, "name");
+        requireNonNull(messageFactory, "messageFactory");
+        requireNonNull(loggerSupplier, "loggerSupplier");
+
+        // Read lock fast path: See if logger already exists
+        T logger = getLogger(name, messageFactory);
+        if (logger != null) {
+            return logger;
+        }
+
+        // Write lock slow path: Insert the logger
+        writeLock.lock();
+        try {
+
+            // See if the logger is created by another thread in the meantime
+            final Map<MessageFactory, WeakReference<T>> 
loggerRefByMessageFactory =
+                    loggerRefByMessageFactoryByName.computeIfAbsent(name, 
this::createLoggerRefByMessageFactoryMap);
+            final WeakReference<T> loggerRef;
+            if ((loggerRef = loggerRefByMessageFactory.get(messageFactory)) != 
null
+                    && (logger = loggerRef.get()) != null) {
+                return logger;
+            }
+
+            // Create the logger
+            logger = loggerSupplier.apply(name, messageFactory);
+
+            // Report message factory mismatches, if there is any
+            final MessageFactory loggerMessageFactory = 
logger.getMessageFactory();
+            if (!loggerMessageFactory.equals(messageFactory)) {
+                StatusLogger.getLogger()
+                        .error(
+                                "Newly registered logger with name `{}` and 
message factory `{}`, is requested to be associated with a different message 
factory: `{}`.\n"
+                                        + "Effectively the message factory of 
the logger will be used and the other one will be ignored.\n"
+                                        + "This generally hints a problem at 
the logger context implementation.\n"
+                                        + "Please report this using the Log4j 
project issue tracker.",
+                                name,
+                                loggerMessageFactory,
+                                messageFactory);
+            }
+
+            // Insert the logger
+            loggerRefByMessageFactory.put(loggerMessageFactory, new 
WeakReference<>(logger));
+            return logger;
+        } finally {
+            writeLock.unlock();
+        }
     }
 
-    public void putIfAbsent(final String name, final MessageFactory 
messageFactory, final T logger) {
-        factory.putIfAbsent(getOrCreateInnerMap(factoryKey(messageFactory)), 
name, logger);
+    private Map<MessageFactory, WeakReference<T>> 
createLoggerRefByMessageFactoryMap(final String ignored) {
+        return new WeakHashMap<>();
     }
 }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java
index 8fff7b8097..3b6b5c2585 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java
@@ -19,7 +19,7 @@
  * API classes.
  */
 @Export
-@Version("2.24.0")
+@Version("2.25.0")
 package org.apache.logging.log4j.spi;
 
 import org.osgi.annotation.bundle.Export;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java
index 777d627115..169c39162d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java
@@ -111,7 +111,7 @@ public class Logger extends AbstractLogger implements 
Supplier<LoggerConfig> {
         this.privateConfig = new PrivateConfig(context.getConfiguration(), 
this);
     }
 
-    private static MessageFactory getEffectiveMessageFactory(final 
MessageFactory messageFactory) {
+    static MessageFactory getEffectiveMessageFactory(final MessageFactory 
messageFactory) {
         return createInstanceFromFactoryProperty(
                 MessageFactory.class,
                 messageFactory,
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
index 07cd2d1f59..1e6c3a47fe 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
@@ -48,14 +48,15 @@ import org.apache.logging.log4j.core.util.ExecutorServices;
 import org.apache.logging.log4j.core.util.NetUtils;
 import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
 import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.spi.AbstractLogger;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
 import org.apache.logging.log4j.spi.LoggerContextShutdownAware;
 import org.apache.logging.log4j.spi.LoggerContextShutdownEnabled;
 import org.apache.logging.log4j.spi.LoggerRegistry;
 import org.apache.logging.log4j.spi.Terminable;
 import org.apache.logging.log4j.spi.ThreadContextMapFactory;
+import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.PropertiesUtil;
+import org.jspecify.annotations.Nullable;
 
 /**
  * The LoggerContext is the anchor for the logging system. It maintains a list 
of all the loggers requested by
@@ -76,6 +77,13 @@ public class LoggerContext extends AbstractLifeCycle
 
     private static final Configuration NULL_CONFIGURATION = new 
NullConfiguration();
 
+    /**
+     * The default message factory to use while creating loggers, if none is 
provided.
+     *
+     * @see <a 
href="https://github.com/apache/logging-log4j2/pull/2936";>#2936</a> for the 
discussion on why we leak the message factory of the default logger and 
hardcode it here.
+     */
+    private static final MessageFactory DEFAULT_MESSAGE_FACTORY = 
Logger.getEffectiveMessageFactory(null);
+
     private final LoggerRegistry<Logger> loggerRegistry = new 
LoggerRegistry<>();
     private final CopyOnWriteArrayList<PropertyChangeListener> 
propertyChangeListeners = new CopyOnWriteArrayList<>();
     private volatile List<LoggerContextShutdownAware> listeners;
@@ -515,25 +523,17 @@ public class LoggerContext extends AbstractLifeCycle
     }
 
     /**
-     * Obtains a Logger from the Context.
+     * Obtains a logger from the context.
      *
-     * @param name The name of the Logger to return.
-     * @param messageFactory The message factory is used only when creating a 
logger, subsequent use does not change the
-     *            logger but will log a warning if mismatched.
-     * @return The Logger.
+     * @param name a logger name
+     * @param messageFactory a message factory to associate the logger with
+     * @return a logger matching the given name and message factory
      */
     @Override
-    public Logger getLogger(final String name, final MessageFactory 
messageFactory) {
-        // Note: This is the only method where we add entries to the 
'loggerRegistry' ivar.
-        Logger logger = loggerRegistry.getLogger(name, messageFactory);
-        if (logger != null) {
-            AbstractLogger.checkMessageFactory(logger, messageFactory);
-            return logger;
-        }
-
-        logger = newInstance(this, name, messageFactory);
-        loggerRegistry.putIfAbsent(name, messageFactory, logger);
-        return loggerRegistry.getLogger(name, messageFactory);
+    public Logger getLogger(final String name, @Nullable final MessageFactory 
messageFactory) {
+        final MessageFactory effectiveMessageFactory =
+                messageFactory != null ? messageFactory : 
DEFAULT_MESSAGE_FACTORY;
+        return loggerRegistry.computeIfAbsent(name, effectiveMessageFactory, 
this::newInstance);
     }
 
     /**
@@ -554,7 +554,7 @@ public class LoggerContext extends AbstractLifeCycle
      */
     @Override
     public boolean hasLogger(final String name) {
-        return loggerRegistry.hasLogger(name);
+        return loggerRegistry.hasLogger(name, DEFAULT_MESSAGE_FACTORY);
     }
 
     /**
@@ -564,8 +564,10 @@ public class LoggerContext extends AbstractLifeCycle
      * @return True if the Logger exists, false otherwise.
      */
     @Override
-    public boolean hasLogger(final String name, final MessageFactory 
messageFactory) {
-        return loggerRegistry.hasLogger(name, messageFactory);
+    public boolean hasLogger(final String name, @Nullable final MessageFactory 
messageFactory) {
+        final MessageFactory effectiveMessageFactory =
+                messageFactory != null ? messageFactory : 
DEFAULT_MESSAGE_FACTORY;
+        return loggerRegistry.hasLogger(name, effectiveMessageFactory);
     }
 
     /**
@@ -812,6 +814,22 @@ public class LoggerContext extends AbstractLifeCycle
                 .init(); // Or make public and call ThreadContext.init() which 
calls ThreadContextMapFactory.init().
     }
 
+    private Logger newInstance(final String name, final MessageFactory 
messageFactory) {
+        final Logger logger = newInstance(this, name, messageFactory);
+        final MessageFactory loggerMessageFactory = logger.getMessageFactory();
+        if (!loggerMessageFactory.equals(messageFactory)) {
+            StatusLogger.getLogger()
+                    .error(
+                            "Newly created logger with name `{}` and message 
factory `{}` was actually requested to be created with a different message 
factory: `{}`.\n"
+                                    + "This generally hints a problem.\n"
+                                    + "Please report this using the Log4j 
project issue tracker.",
+                            name,
+                            loggerMessageFactory,
+                            messageFactory);
+        }
+        return logger;
+    }
+
     // LOG4J2-151: changed visibility from private to protected
     protected Logger newInstance(final LoggerContext ctx, final String name, 
final MessageFactory messageFactory) {
         return new Logger(ctx, name, messageFactory);
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java
index 92bb0d5449..abf19fcd6f 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java
@@ -18,7 +18,7 @@
  * Provides Asynchronous Logger classes and interfaces for low-latency logging.
  */
 @Export
-@Version("2.24.0")
+@Version("2.25.0")
 package org.apache.logging.log4j.core.async;
 
 import org.osgi.annotation.bundle.Export;
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java
index 266256b463..171f9c7e78 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java
@@ -18,7 +18,7 @@
  * Implementation of Log4j 2.
  */
 @Export
-@Version("2.24.0")
+@Version("2.25.0")
 package org.apache.logging.log4j.core;
 
 import org.osgi.annotation.bundle.Export;
diff --git a/log4j-taglib/pom.xml b/log4j-taglib/pom.xml
index f34353488a..3855d8f441 100644
--- a/log4j-taglib/pom.xml
+++ b/log4j-taglib/pom.xml
@@ -28,14 +28,19 @@
   <name>Apache Log4j Tag Library</name>
   <description>The Apache Log4j Tag Library for Web Applications</description>
   <properties>
-
     <!--
       ~ OSGi and JPMS options
       -->
+    <bnd-extra-package-options>
+      <!-- Annotations only -->
+      org.jspecify.*;resolution:=optional
+    </bnd-extra-package-options>
     <bnd-extra-module-options>
       <!-- Filebased module names: MUST be static -->
       
javax.servlet.api;substitute="javax.servlet-api";static=true;transitive=false,
-      
javax.servlet.jsp.api;substitute="javax.servlet.jsp-api";static=true;transitive=false
+      
javax.servlet.jsp.api;substitute="javax.servlet.jsp-api";static=true;transitive=false,
+      <!-- Remove `transitive` for optional dependencies -->
+      org.jspecify;transitive=false
     </bnd-extra-module-options>
     <Fragment-Host>org.apache.logging.log4j.core</Fragment-Host>
   </properties>
@@ -59,6 +64,11 @@
       <artifactId>log4j-web</artifactId>
       <optional>true</optional>
     </dependency>
+    <dependency>
+      <groupId>org.jspecify</groupId>
+      <artifactId>jspecify</artifactId>
+      <scope>provided</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.logging.log4j</groupId>
       <artifactId>log4j-core</artifactId>
diff --git 
a/log4j-taglib/src/main/java/org/apache/logging/log4j/taglib/Log4jTaglibLoggerContext.java
 
b/log4j-taglib/src/main/java/org/apache/logging/log4j/taglib/Log4jTaglibLoggerContext.java
index 3c76f39b21..8203f9c736 100644
--- 
a/log4j-taglib/src/main/java/org/apache/logging/log4j/taglib/Log4jTaglibLoggerContext.java
+++ 
b/log4j-taglib/src/main/java/org/apache/logging/log4j/taglib/Log4jTaglibLoggerContext.java
@@ -16,14 +16,20 @@
  */
 package org.apache.logging.log4j.taglib;
 
+import java.util.Map;
 import java.util.WeakHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 import javax.servlet.ServletContext;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.spi.AbstractLogger;
+import org.apache.logging.log4j.message.ParameterizedMessageFactory;
 import org.apache.logging.log4j.spi.ExtendedLogger;
 import org.apache.logging.log4j.spi.LoggerContext;
 import org.apache.logging.log4j.spi.LoggerRegistry;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
 
 /**
  * This bridge between the tag library and the Log4j API ensures that 
instances of {@link Log4jTaglibLogger} are
@@ -31,13 +37,21 @@ import org.apache.logging.log4j.spi.LoggerRegistry;
  *
  * @since 2.0
  */
+@NullMarked
 final class Log4jTaglibLoggerContext implements LoggerContext {
-    // These were change to WeakHashMaps to avoid ClassLoader (memory) leak, 
something that's particularly
-    // important in Servlet containers.
-    private static final WeakHashMap<ServletContext, Log4jTaglibLoggerContext> 
CONTEXTS = new WeakHashMap<>();
 
-    private final LoggerRegistry<Log4jTaglibLogger> loggerRegistry =
-            new LoggerRegistry<>(new 
LoggerRegistry.WeakMapFactory<Log4jTaglibLogger>());
+    private static final ReadWriteLock LOCK = new ReentrantReadWriteLock();
+
+    private static final Lock READ_LOCK = LOCK.readLock();
+
+    private static final Lock WRITE_LOCK = LOCK.writeLock();
+
+    private static final Map<ServletContext, Log4jTaglibLoggerContext> 
LOGGER_CONTEXT_BY_SERVLET_CONTEXT =
+            new WeakHashMap<>();
+
+    private static final MessageFactory DEFAULT_MESSAGE_FACTORY = 
ParameterizedMessageFactory.INSTANCE;
+
+    private final LoggerRegistry<Log4jTaglibLogger> loggerRegistry = new 
LoggerRegistry<>();
 
     private final ServletContext servletContext;
 
@@ -47,36 +61,25 @@ final class Log4jTaglibLoggerContext implements 
LoggerContext {
 
     @Override
     public Object getExternalContext() {
-        return this.servletContext;
+        return servletContext;
     }
 
     @Override
     public Log4jTaglibLogger getLogger(final String name) {
-        return this.getLogger(name, null);
+        return getLogger(name, null);
     }
 
     @Override
-    public Log4jTaglibLogger getLogger(final String name, final MessageFactory 
messageFactory) {
-        // Note: This is the only method where we add entries to the 
'loggerRegistry' ivar.
-        Log4jTaglibLogger logger = this.loggerRegistry.getLogger(name, 
messageFactory);
-        if (logger != null) {
-            AbstractLogger.checkMessageFactory(logger, messageFactory);
-            return logger;
-        }
-
-        synchronized (this.loggerRegistry) {
-            logger = this.loggerRegistry.getLogger(name, messageFactory);
-            if (logger == null) {
-                final LoggerContext context = LogManager.getContext(false);
-                final ExtendedLogger original =
-                        messageFactory == null ? context.getLogger(name) : 
context.getLogger(name, messageFactory);
-                // wrap a logger from an underlying implementation
-                logger = new Log4jTaglibLogger(original, name, 
original.getMessageFactory());
-                this.loggerRegistry.putIfAbsent(name, messageFactory, logger);
-            }
-        }
+    public Log4jTaglibLogger getLogger(final String name, @Nullable final 
MessageFactory messageFactory) {
+        final MessageFactory effectiveMessageFactory =
+                messageFactory != null ? messageFactory : 
DEFAULT_MESSAGE_FACTORY;
+        return loggerRegistry.computeIfAbsent(name, effectiveMessageFactory, 
this::createLogger);
+    }
 
-        return logger;
+    private Log4jTaglibLogger createLogger(final String name, @Nullable final 
MessageFactory messageFactory) {
+        final LoggerContext loggerContext = LogManager.getContext(false);
+        final ExtendedLogger delegateLogger = loggerContext.getLogger(name, 
messageFactory);
+        return new Log4jTaglibLogger(delegateLogger, name, 
delegateLogger.getMessageFactory());
     }
 
     @Override
@@ -85,8 +88,10 @@ final class Log4jTaglibLoggerContext implements 
LoggerContext {
     }
 
     @Override
-    public boolean hasLogger(final String name, final MessageFactory 
messageFactory) {
-        return loggerRegistry.hasLogger(name, messageFactory);
+    public boolean hasLogger(final String name, @Nullable final MessageFactory 
messageFactory) {
+        final MessageFactory effectiveMessageFactory =
+                messageFactory != null ? messageFactory : 
DEFAULT_MESSAGE_FACTORY;
+        return loggerRegistry.hasLogger(name, effectiveMessageFactory);
     }
 
     @Override
@@ -94,20 +99,25 @@ final class Log4jTaglibLoggerContext implements 
LoggerContext {
         return loggerRegistry.hasLogger(name, messageFactoryClass);
     }
 
-    static synchronized Log4jTaglibLoggerContext getInstance(final 
ServletContext servletContext) {
-        Log4jTaglibLoggerContext loggerContext = CONTEXTS.get(servletContext);
-        if (loggerContext != null) {
-            return loggerContext;
-        }
+    static Log4jTaglibLoggerContext getInstance(final ServletContext 
servletContext) {
 
-        synchronized (CONTEXTS) {
-            loggerContext = CONTEXTS.get(servletContext);
-            if (loggerContext == null) {
-                loggerContext = new Log4jTaglibLoggerContext(servletContext);
-                CONTEXTS.put(servletContext, loggerContext);
+        // Get the associated logger context, if exists
+        READ_LOCK.lock();
+        try {
+            final Log4jTaglibLoggerContext loggerContext = 
LOGGER_CONTEXT_BY_SERVLET_CONTEXT.get(servletContext);
+            if (loggerContext != null) {
+                return loggerContext;
             }
+        } finally {
+            READ_LOCK.unlock();
         }
 
-        return loggerContext;
+        // Create the logger context
+        WRITE_LOCK.lock();
+        try {
+            return 
LOGGER_CONTEXT_BY_SERVLET_CONTEXT.computeIfAbsent(servletContext, 
Log4jTaglibLoggerContext::new);
+        } finally {
+            WRITE_LOCK.unlock();
+        }
     }
 }
diff --git 
a/log4j-taglib/src/main/java/org/apache/logging/log4j/taglib/package-info.java 
b/log4j-taglib/src/main/java/org/apache/logging/log4j/taglib/package-info.java
index 3614d93117..9f8d273e7d 100644
--- 
a/log4j-taglib/src/main/java/org/apache/logging/log4j/taglib/package-info.java
+++ 
b/log4j-taglib/src/main/java/org/apache/logging/log4j/taglib/package-info.java
@@ -20,7 +20,7 @@
  * @since 2.0
  */
 @Export
-@Version("2.20.1")
+@Version("2.25.0")
 package org.apache.logging.log4j.taglib;
 
 import org.osgi.annotation.bundle.Export;
diff --git 
a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContext.java
 
b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContext.java
index 7d2c2351c6..3cca8318b8 100644
--- 
a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContext.java
+++ 
b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContext.java
@@ -18,9 +18,11 @@ package org.apache.logging.log4j.tojul;
 
 import java.util.logging.Logger;
 import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.message.ParameterizedMessageFactory;
 import org.apache.logging.log4j.spi.ExtendedLogger;
 import org.apache.logging.log4j.spi.LoggerContext;
 import org.apache.logging.log4j.spi.LoggerRegistry;
+import org.jspecify.annotations.Nullable;
 
 /**
  * Implementation of Log4j {@link LoggerContext} SPI.
@@ -29,8 +31,11 @@ import org.apache.logging.log4j.spi.LoggerRegistry;
  * @author <a href="http://www.vorburger.ch";>Michael Vorburger.ch</a> for 
Google
  */
 class JULLoggerContext implements LoggerContext {
+
     private final LoggerRegistry<ExtendedLogger> loggerRegistry = new 
LoggerRegistry<>();
 
+    private static final MessageFactory DEFAULT_MESSAGE_FACTORY = 
ParameterizedMessageFactory.INSTANCE;
+
     // This implementation is strongly inspired by 
org.apache.logging.slf4j.SLF4JLoggerContext
 
     @Override
@@ -40,29 +45,31 @@ class JULLoggerContext implements LoggerContext {
 
     @Override
     public ExtendedLogger getLogger(final String name) {
-        if (!loggerRegistry.hasLogger(name)) {
-            loggerRegistry.putIfAbsent(name, null, new JULLogger(name, 
Logger.getLogger(name)));
-        }
-        return loggerRegistry.getLogger(name);
+        return getLogger(name, null);
     }
 
     @Override
-    public ExtendedLogger getLogger(final String name, final MessageFactory 
messageFactory) {
-        if (!loggerRegistry.hasLogger(name, messageFactory)) {
-            loggerRegistry.putIfAbsent(
-                    name, messageFactory, new JULLogger(name, messageFactory, 
Logger.getLogger(name)));
-        }
-        return loggerRegistry.getLogger(name, messageFactory);
+    public ExtendedLogger getLogger(final String name, @Nullable final 
MessageFactory messageFactory) {
+        final MessageFactory effectiveMessageFactory =
+                messageFactory != null ? messageFactory : 
DEFAULT_MESSAGE_FACTORY;
+        return loggerRegistry.computeIfAbsent(name, effectiveMessageFactory, 
JULLoggerContext::createLogger);
+    }
+
+    private static ExtendedLogger createLogger(final String name, @Nullable 
final MessageFactory messageFactory) {
+        final Logger logger = Logger.getLogger(name);
+        return new JULLogger(name, messageFactory, logger);
     }
 
     @Override
     public boolean hasLogger(final String name) {
-        return loggerRegistry.hasLogger(name);
+        return loggerRegistry.hasLogger(name, DEFAULT_MESSAGE_FACTORY);
     }
 
     @Override
-    public boolean hasLogger(final String name, final MessageFactory 
messageFactory) {
-        return loggerRegistry.hasLogger(name, messageFactory);
+    public boolean hasLogger(final String name, @Nullable final MessageFactory 
messageFactory) {
+        final MessageFactory effectiveMessageFactory =
+                messageFactory != null ? messageFactory : 
DEFAULT_MESSAGE_FACTORY;
+        return loggerRegistry.hasLogger(name, effectiveMessageFactory);
     }
 
     @Override
diff --git 
a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/package-info.java 
b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/package-info.java
index aa117c7995..68812bb248 100644
--- 
a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/package-info.java
+++ 
b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/package-info.java
@@ -21,7 +21,7 @@
  * @author <a href="http://www.vorburger.ch";>Michael Vorburger.ch</a> for 
Google
  */
 @Export
-@Version("2.24.0")
+@Version("2.25.0")
 package org.apache.logging.log4j.tojul;
 
 import org.osgi.annotation.bundle.Export;
diff --git 
a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContext.java 
b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContext.java
index f0aee0af41..4e4567d1ef 100644
--- 
a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContext.java
+++ 
b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContext.java
@@ -17,14 +17,20 @@
 package org.apache.logging.slf4j;
 
 import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.message.ParameterizedMessageFactory;
 import org.apache.logging.log4j.spi.ExtendedLogger;
 import org.apache.logging.log4j.spi.LoggerContext;
 import org.apache.logging.log4j.spi.LoggerRegistry;
+import org.jspecify.annotations.Nullable;
+import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class SLF4JLoggerContext implements LoggerContext {
+
     private final LoggerRegistry<ExtendedLogger> loggerRegistry = new 
LoggerRegistry<>();
 
+    private static final MessageFactory DEFAULT_MESSAGE_FACTORY = 
ParameterizedMessageFactory.INSTANCE;
+
     @Override
     public Object getExternalContext() {
         return null;
@@ -32,29 +38,31 @@ public class SLF4JLoggerContext implements LoggerContext {
 
     @Override
     public ExtendedLogger getLogger(final String name) {
-        if (!loggerRegistry.hasLogger(name)) {
-            loggerRegistry.putIfAbsent(name, null, new SLF4JLogger(name, 
LoggerFactory.getLogger(name)));
-        }
-        return loggerRegistry.getLogger(name);
+        return getLogger(name, null);
     }
 
     @Override
-    public ExtendedLogger getLogger(final String name, final MessageFactory 
messageFactory) {
-        if (!loggerRegistry.hasLogger(name, messageFactory)) {
-            loggerRegistry.putIfAbsent(
-                    name, messageFactory, new SLF4JLogger(name, 
messageFactory, LoggerFactory.getLogger(name)));
-        }
-        return loggerRegistry.getLogger(name, messageFactory);
+    public ExtendedLogger getLogger(final String name, @Nullable final 
MessageFactory messageFactory) {
+        final MessageFactory effectiveMessageFactory =
+                messageFactory != null ? messageFactory : 
DEFAULT_MESSAGE_FACTORY;
+        return loggerRegistry.computeIfAbsent(name, effectiveMessageFactory, 
SLF4JLoggerContext::createLogger);
+    }
+
+    private static ExtendedLogger createLogger(final String name, @Nullable 
final MessageFactory messageFactory) {
+        final Logger logger = LoggerFactory.getLogger(name);
+        return new SLF4JLogger(name, messageFactory, logger);
     }
 
     @Override
     public boolean hasLogger(final String name) {
-        return loggerRegistry.hasLogger(name);
+        return loggerRegistry.hasLogger(name, DEFAULT_MESSAGE_FACTORY);
     }
 
     @Override
-    public boolean hasLogger(final String name, final MessageFactory 
messageFactory) {
-        return loggerRegistry.hasLogger(name, messageFactory);
+    public boolean hasLogger(final String name, @Nullable final MessageFactory 
messageFactory) {
+        final MessageFactory effectiveMessageFactory =
+                messageFactory != null ? messageFactory : 
DEFAULT_MESSAGE_FACTORY;
+        return loggerRegistry.hasLogger(name, effectiveMessageFactory);
     }
 
     @Override
diff --git 
a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/package-info.java 
b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/package-info.java
index ba4cb130be..8e6a1c3ac8 100644
--- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/package-info.java
+++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/package-info.java
@@ -18,7 +18,7 @@
  * SLF4J support.
  */
 @Export
-@Version("2.24.0")
+@Version("2.25.0")
 package org.apache.logging.slf4j;
 
 import org.osgi.annotation.bundle.Export;
diff --git a/pom.xml b/pom.xml
index 3ac428a3a2..e778a2e640 100644
--- a/pom.xml
+++ b/pom.xml
@@ -963,14 +963,30 @@
 
   <profiles>
 
+    <!-- `java8-tests` profile to force using Java 8 while running tests -->
     <profile>
+
       <id>java8-tests</id>
+
       <activation>
         <property>
           <name>env.CI</name>
           <value>true</value>
         </property>
       </activation>
+
+      <!-- There are certain Java 8 bugs[1] that cause Mockito failures[2].
+           Adding necessary dependencies to workaround them.
+           [1] https://bugs.openjdk.org/browse/JDK-8152174
+           [2] https://github.com/mockito/mockito/issues/1449 -->
+      <dependencies>
+        <dependency>
+          <groupId>org.jspecify</groupId>
+          <artifactId>jspecify</artifactId>
+          <scope>test</scope>
+        </dependency>
+      </dependencies>
+
       <build>
         <plugins>
           <plugin>
@@ -990,6 +1006,7 @@
           </plugin>
         </plugins>
       </build>
+
     </profile>
 
     <profile>
diff --git 
a/src/changelog/.2.x.x/2936_deprecate_AbstractLogger_checkMessageFactory.xml 
b/src/changelog/.2.x.x/2936_deprecate_AbstractLogger_checkMessageFactory.xml
new file mode 100644
index 0000000000..e72a33566a
--- /dev/null
+++ b/src/changelog/.2.x.x/2936_deprecate_AbstractLogger_checkMessageFactory.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns="https://logging.apache.org/xml/ns";
+       xsi:schemaLocation="https://logging.apache.org/xml/ns 
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="deprecated">
+  <issue id="2936" link="https://github.com/apache/logging-log4j2/pull/2936"/>
+  <description format="asciidoc">Deprecate 
`AbstractLogger.checkMessageFactory()`, since all created `Logger`s are already 
`MessageFactory`-namespaced</description>
+</entry>
diff --git 
a/src/changelog/.2.x.x/2936_make_LoggerRegistry_MessageFactory_namespaced.xml 
b/src/changelog/.2.x.x/2936_make_LoggerRegistry_MessageFactory_namespaced.xml
new file mode 100644
index 0000000000..c314f84251
--- /dev/null
+++ 
b/src/changelog/.2.x.x/2936_make_LoggerRegistry_MessageFactory_namespaced.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns="https://logging.apache.org/xml/ns";
+       xsi:schemaLocation="https://logging.apache.org/xml/ns 
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="changed">
+  <issue id="2936" link="https://github.com/apache/logging-log4j2/pull/2936"/>
+  <description format="asciidoc">Rework `LoggerRegistry` to make it 
`MessageFactory`-namespaced.
+This effectively allows loggers of same name, but different message 
factory.</description>
+</entry>

Reply via email to